Control Allocation (gnc-autonomy/control/control-allocation)
Use when a commanded moment or acceleration vector must be distributed
among redundant aerodynamic and propulsive effectors of an aircraft or
spacecraft: this leaf is the static allocation math. It builds the
control effectiveness matrix B that maps effector deflections u to the
moment m through m = B u, solves the minimum-norm pseudoinverse or the
weighted least squares problem, enforces position and rate limits with
the redistributed pseudoinverse and clipping, splits the moment between
effector groups with the daisy chain scheme, scales the commanded
direction for the direct allocation comparison, and returns the
achieved moment, the allocation error norm, and the saturated effector
list. Pure Python, stdlib only. It pairs with pid-control-design (which
produces the moment command) and observer-design; it does not size
control surfaces, model single-surface aerodynamic effectiveness, or
design the loop gains themselves.
Domain quick reference
- Effectiveness model: m = B u, with B the n x m control effectiveness
matrix (n moment axes, typically 3: roll, pitch, yaw; m effectors,
n <= m for redundancy). Column i of B is the moment vector produced
by a unit deflection of effector i.
- Pseudoinverse allocation: u = B^+ m with B^+ = B^T (B B^T)^-1 when B
has full row rank. This is the minimum-norm solution among all
deflections that reproduce m exactly. When B B^T is singular the
solve is regularized with the module constant EPSILON = 1e-9.
- Damped least squares: u = B^T (B B^T + lambda I)^-1 m, the
regularized variant that trades exact moment reproduction against
command magnitude.
- Weighted allocation: minimize u^T W u subject to B u = m with W the
diagonal cost matrix, closed form u = W^-1 B^T (B W^-1 B^T)^-1 m. An
effector with a smaller cost weight w_i is cheaper to deflect and
takes the larger share of the command; raising w_i pushes command off
effector i. Convention note: the module reads the weight as a cost,
so the low-cost effector is the one favored by the allocator.
- Position limits: clip u to [u_min, u_max]; residual redistribution
re-solves the leftover moment m - B u_clipped on the unsaturated
effector set only (pseudoinverse restricted to the free columns),
iterating up to the module constant MAX_ITER = 5. Saturated effectors
stay pinned at their limits.
- Rate limits: u_dot = (u - u_prev) / dt clipped componentwise to
+/-rate_max; the returned command moves at most rate_max * dt.
- Daisy chain: allocate the primary group (aerodynamic surfaces) up to
its limits, then pass the residual moment to the secondary group
(thrust vectoring or RCS).
- Direct allocation: scale the commanded direction m_hat = m / ||m||
through its minimum-norm preimage u_dir = B^+ m_hat up to the
actuator box; the per-axis limits bound the scale linearly, so
s_box = min(||m||, box bound) gives the exact largest feasible
scaling in closed form.
- Report: achieved moment m_ach = B u, error norm ||m - m_ach||, and
the saturated effector list.
- Units are consistent command units (rad or normalized) and moment
units (N m); keep B, m and the limits in one coherent set.
- ARP4754A frames the control law development context; the allocation
relations above are standard engineering methodology, summary-only.
Workflow
- Fix the effector set and geometry: assemble B as the n x m matrix of
per-effector moment coefficients (roll, pitch, yaw per column) and
confirm n <= m.
- Get the commanded moment vector m from the control law, then solve
the unconstrained problem with pseudoinverse_alloc.
- For prioritized effort, solve weighted_alloc with the diagonal cost
weights; use damped_least_squares_alloc when B B^T is ill
conditioned and a regularized command is acceptable.
- Enforce the position limits: clip_to_limits for the plain verdict,
or redistribute_pseudoinverse to re-solve the residual on the
unsaturated effectors.
- Enforce the deflection rate: rate_limit with the previous command,
the time step dt and the per-effector rate_max.
- For mixed effector families, run daisy_chain_alloc with the primary
group (aerodynamic surfaces) and the secondary group (thrust
vectoring, RCS), each with its own limits.
- For the strategy comparison, run direct_alloc and compare its
achieved moment with the pseudoinverse and daisy chain results.
- Close with allocation_verdict on the chosen command: achieved
moment, error norm, saturated effector list.
- Confirm the deterministic checks with the contract test
scripts/test_control_allocation.py.
Worked example
Two-effector roll control with B = [[1, 1]] (both ailerons produce
roll) and commanded roll moment m = 0.8:
- Pseudoinverse allocation: u = [0.4, 0.4], the minimum-norm split.
- With position limits u_min = [0, 0] and u_max = [0.3, 0.6], clipping
gives [0.3, 0.4]; the redistributed pseudoinverse re-solves the
residual 0.1 on the free effector and returns u = [0.3, 0.5] with
zero allocation error. The verdict lists effector 0 as saturated.
- Rate limit from u_prev = [0, 0] with dt = 0.1 s and rate_max =
[2, 3] per second: step 1 gives [0.2, 0.3], step 2 reaches [0.4,
0.4].
Three-axis case with six effectors (two per axis, coefficients [1,
0.5] per axis), effectiveness matrix
B = [[1, 0.5, 0, 0, 0, 0], [0, 0, 1, 0.5, 0, 0], [0, 0, 0, 0, 1,
0.5]], commanded m = [0.9, -0.6, 0.3]:
Pseudoinverse allocation u = [0.72, 0.36, -0.48, -0.24, 0.24, 0.12]
reproduces the moment within 1e-9, and its norm 1.0040 is below the
norm 1.0102 of any nullspace-perturbed feasible alternative, the
minimum-norm property.
Weighted allocation on the roll case with w = [1, 4] (effector 2 is
four times as costly): u = [0.64, 0.16], so the command moves to the
lower-cost effector 1; with w = [4, 1] the split mirrors to [0.16,
0.64].
Daisy chain: primary ailerons B_p = [[1, 1]] limited to 0.3 each and
thrust vectoring B_s = [[2]] for m = 1.0: the primary group saturates
at u_p = [0.3, 0.3] giving 0.6, the residual 0.4 goes to the secondary
group as u_s = [0.2], total achieved moment 1.0, zero error.
Direct allocation for m = 0.8 in the box [0, 0] to [0.3, 0.6]:
the commanded-direction scaling saturates at u = [0.3, 0.3] with
achieved moment 0.6 and error norm 0.2, the box bound case.
Verification
- Confirm pseudoinverse_alloc([[1, 1]], [0.8]) returns [0.4, 0.4] and
redistribute_pseudoinverse with u_max [0.3, 0.6] returns [0.3, 0.5]
with error norm 0.0 and saturated list [0].
- Confirm the three-axis six-effector allocation reproduces m within
1e-9 and beats the nullspace-perturbed alternative on norm.
- Confirm weighted_alloc with w = [1, 4] returns [0.64, 0.16] and
achieves the commanded moment exactly.
- Confirm rate_limit reaches the command after two steps of 0.1 s at
rate_max [2, 3] per second.
- Confirm daisy_chain_alloc closes the loop: primary moment plus
secondary moment equals the command when the secondary is not
saturated.
- Confirm every dimension mismatch, non-finite input, inverted limit
(u_min > u_max), negative rate_max, and non-positive dt or weight
raises ValueError.
- Run the contract test offline: python3
scripts/test_control_allocation.py (35 tests, deterministic).
Pitfalls
- Reading the weight vector backwards: the module treats the diagonal weight
as a cost, so a smaller w_i favors effector i (w = [1, 4] sends command to
effector 1: u = [0.64, 0.16]); raising w_i pushes command off that
effector.
- Mixing units across B, m and the limits: keep deflection units (rad or
normalized), moment units and the limit bounds in one coherent set or the
allocation error norm is meaningless.
- Expecting exact reproduction after clipping: plain clip_to_limits leaves
residual error (the [0.3, 0.6] example clips to [0.3, 0.4] with error
0.1); use redistribute_pseudoinverse when you need the residual re-solved
on the free effectors.
- Calling rate_limit without the previous command: u_dot = (u - u_prev)/dt
clips to +/-rate_max, so the returned command moves at most rate_max*dt
from u_prev; the first call needs a defined u_prev.
- Daisy chain order matters: the primary group allocates first up to its
limits and the residual goes to the secondary group; swapping the groups
changes the achieved allocation.
- Dimension mismatches, non-finite inputs, inverted limits (u_min > u_max),
negative rate_max and non-positive dt or weight raise ValueError.
Related leaves
- gnc-autonomy/control/pid-control-design: produces the moment command
that this leaf distributes to the effectors.
- gnc-autonomy/control/observer-design: state feedback for the
control law upstream of the allocation.
- gnc-autonomy/control/python-control-design: control law margin checks
in the same ARP4754A development context.
Behavior contract (gate 3)
Run the deterministic contract test (stdlib unittest, offline):
python3 scripts/test_control_allocation.py
The test covers the pseudoinverse split and minimum-norm property on
the two-effector roll case and the three-axis six-effector case, the
singular-gram regularization path, damped least squares values and the
lambda = 0 delegation, weighted allocation pushing the command to the
lower-cost effector, clipping masks at both limits, the redistributed
pseudoinverse worked example ([0.3, 0.5], zero error, saturated list),
full-saturation residual retention, the max-iter bound, rate-limit step
progression and reversal, daisy chain primary and secondary saturation
behavior, direct allocation inside the box and at the box bound, the
allocation verdict fields, and ValueError rejection of dimension
mismatch, non-finite input, inverted limits, negative rate_max,
non-positive dt and non-positive weights.
Compliance
- Standards referenced, not reproduced: ARP4754A (Aerospace
Recommended Practice, SAE) frames the control law development and
validation context; the allocation mathematics above is standard
engineering methodology, summary-only per standards-map.yaml.
- compliance: STANDARDS-REF, gated: false.
1---2name: control-allocation3description: Use when you must allocate a commanded roll, pitch, yaw moment vector across redundant aerodynamic and propulsive effectors: assemble the control effectiveness matrix, solve the pseudoinverse allocation or the weighted least squares problem, enforce the position limits with the redistributed pseudoinverse, distribute the moment between the aerodynamic and thrust vectoring groups with the daisy chain scheme, and report the achieved moment, allocation error and saturated effectors. Produces the effector command vector and the saturation verdict for output distribution. Trigger: control allocation, control effectiveness matrix, pseudoinverse allocation, weighted least squares, daisy chain, redistributed pseudoinverse, actuator limits, saturated effectors, redundant effectors.4license: Apache-2.05---67# Control Allocation (gnc-autonomy/control/control-allocation)89Use when a commanded moment or acceleration vector must be distributed10among redundant aerodynamic and propulsive effectors of an aircraft or11spacecraft: this leaf is the static allocation math. It builds the12control effectiveness matrix B that maps effector deflections u to the13moment m through m = B u, solves the minimum-norm pseudoinverse or the14weighted least squares problem, enforces position and rate limits with15the redistributed pseudoinverse and clipping, splits the moment between16effector groups with the daisy chain scheme, scales the commanded17direction for the direct allocation comparison, and returns the18achieved moment, the allocation error norm, and the saturated effector19list. Pure Python, stdlib only. It pairs with pid-control-design (which20produces the moment command) and observer-design; it does not size21control surfaces, model single-surface aerodynamic effectiveness, or22design the loop gains themselves.2324## Domain quick reference2526- Effectiveness model: m = B u, with B the n x m control effectiveness27 matrix (n moment axes, typically 3: roll, pitch, yaw; m effectors,28 n <= m for redundancy). Column i of B is the moment vector produced29 by a unit deflection of effector i.30- Pseudoinverse allocation: u = B^+ m with B^+ = B^T (B B^T)^-1 when B31 has full row rank. This is the minimum-norm solution among all32 deflections that reproduce m exactly. When B B^T is singular the33 solve is regularized with the module constant EPSILON = 1e-9.34- Damped least squares: u = B^T (B B^T + lambda I)^-1 m, the35 regularized variant that trades exact moment reproduction against36 command magnitude.37- Weighted allocation: minimize u^T W u subject to B u = m with W the38 diagonal cost matrix, closed form u = W^-1 B^T (B W^-1 B^T)^-1 m. An39 effector with a smaller cost weight w_i is cheaper to deflect and40 takes the larger share of the command; raising w_i pushes command off41 effector i. Convention note: the module reads the weight as a cost,42 so the low-cost effector is the one favored by the allocator.43- Position limits: clip u to [u_min, u_max]; residual redistribution44 re-solves the leftover moment m - B u_clipped on the unsaturated45 effector set only (pseudoinverse restricted to the free columns),46 iterating up to the module constant MAX_ITER = 5. Saturated effectors47 stay pinned at their limits.48- Rate limits: u_dot = (u - u_prev) / dt clipped componentwise to49 +/-rate_max; the returned command moves at most rate_max * dt.50- Daisy chain: allocate the primary group (aerodynamic surfaces) up to51 its limits, then pass the residual moment to the secondary group52 (thrust vectoring or RCS).53- Direct allocation: scale the commanded direction m_hat = m / ||m||54 through its minimum-norm preimage u_dir = B^+ m_hat up to the55 actuator box; the per-axis limits bound the scale linearly, so56 s_box = min(||m||, box bound) gives the exact largest feasible57 scaling in closed form.58- Report: achieved moment m_ach = B u, error norm ||m - m_ach||, and59 the saturated effector list.60- Units are consistent command units (rad or normalized) and moment61 units (N m); keep B, m and the limits in one coherent set.62- ARP4754A frames the control law development context; the allocation63 relations above are standard engineering methodology, summary-only.6465## Workflow66671. Fix the effector set and geometry: assemble B as the n x m matrix of68 per-effector moment coefficients (roll, pitch, yaw per column) and69 confirm n <= m.702. Get the commanded moment vector m from the control law, then solve71 the unconstrained problem with pseudoinverse_alloc.723. For prioritized effort, solve weighted_alloc with the diagonal cost73 weights; use damped_least_squares_alloc when B B^T is ill74 conditioned and a regularized command is acceptable.754. Enforce the position limits: clip_to_limits for the plain verdict,76 or redistribute_pseudoinverse to re-solve the residual on the77 unsaturated effectors.785. Enforce the deflection rate: rate_limit with the previous command,79 the time step dt and the per-effector rate_max.806. For mixed effector families, run daisy_chain_alloc with the primary81 group (aerodynamic surfaces) and the secondary group (thrust82 vectoring, RCS), each with its own limits.837. For the strategy comparison, run direct_alloc and compare its84 achieved moment with the pseudoinverse and daisy chain results.858. Close with allocation_verdict on the chosen command: achieved86 moment, error norm, saturated effector list.879. Confirm the deterministic checks with the contract test88 scripts/test_control_allocation.py.8990## Worked example9192Two-effector roll control with B = [[1, 1]] (both ailerons produce93roll) and commanded roll moment m = 0.8:9495- Pseudoinverse allocation: u = [0.4, 0.4], the minimum-norm split.96- With position limits u_min = [0, 0] and u_max = [0.3, 0.6], clipping97 gives [0.3, 0.4]; the redistributed pseudoinverse re-solves the98 residual 0.1 on the free effector and returns u = [0.3, 0.5] with99 zero allocation error. The verdict lists effector 0 as saturated.100- Rate limit from u_prev = [0, 0] with dt = 0.1 s and rate_max =101 [2, 3] per second: step 1 gives [0.2, 0.3], step 2 reaches [0.4,102 0.4].103104Three-axis case with six effectors (two per axis, coefficients [1,1050.5] per axis), effectiveness matrix106107- B = [[1, 0.5, 0, 0, 0, 0], [0, 0, 1, 0.5, 0, 0], [0, 0, 0, 0, 1,108 0.5]], commanded m = [0.9, -0.6, 0.3]:109110- Pseudoinverse allocation u = [0.72, 0.36, -0.48, -0.24, 0.24, 0.12]111 reproduces the moment within 1e-9, and its norm 1.0040 is below the112 norm 1.0102 of any nullspace-perturbed feasible alternative, the113 minimum-norm property.114115Weighted allocation on the roll case with w = [1, 4] (effector 2 is116four times as costly): u = [0.64, 0.16], so the command moves to the117lower-cost effector 1; with w = [4, 1] the split mirrors to [0.16,1180.64].119120Daisy chain: primary ailerons B_p = [[1, 1]] limited to 0.3 each and121thrust vectoring B_s = [[2]] for m = 1.0: the primary group saturates122at u_p = [0.3, 0.3] giving 0.6, the residual 0.4 goes to the secondary123group as u_s = [0.2], total achieved moment 1.0, zero error.124125Direct allocation for m = 0.8 in the box [0, 0] to [0.3, 0.6]:126the commanded-direction scaling saturates at u = [0.3, 0.3] with127achieved moment 0.6 and error norm 0.2, the box bound case.128129## Verification130131- Confirm pseudoinverse_alloc([[1, 1]], [0.8]) returns [0.4, 0.4] and132 redistribute_pseudoinverse with u_max [0.3, 0.6] returns [0.3, 0.5]133 with error norm 0.0 and saturated list [0].134- Confirm the three-axis six-effector allocation reproduces m within135 1e-9 and beats the nullspace-perturbed alternative on norm.136- Confirm weighted_alloc with w = [1, 4] returns [0.64, 0.16] and137 achieves the commanded moment exactly.138- Confirm rate_limit reaches the command after two steps of 0.1 s at139 rate_max [2, 3] per second.140- Confirm daisy_chain_alloc closes the loop: primary moment plus141 secondary moment equals the command when the secondary is not142 saturated.143- Confirm every dimension mismatch, non-finite input, inverted limit144 (u_min > u_max), negative rate_max, and non-positive dt or weight145 raises ValueError.146- Run the contract test offline: python3147 scripts/test_control_allocation.py (35 tests, deterministic).148149## Pitfalls150151- Reading the weight vector backwards: the module treats the diagonal weight152 as a cost, so a smaller w_i favors effector i (w = [1, 4] sends command to153 effector 1: u = [0.64, 0.16]); raising w_i pushes command off that154 effector.155- Mixing units across B, m and the limits: keep deflection units (rad or156 normalized), moment units and the limit bounds in one coherent set or the157 allocation error norm is meaningless.158- Expecting exact reproduction after clipping: plain clip_to_limits leaves159 residual error (the [0.3, 0.6] example clips to [0.3, 0.4] with error160 0.1); use redistribute_pseudoinverse when you need the residual re-solved161 on the free effectors.162- Calling rate_limit without the previous command: u_dot = (u - u_prev)/dt163 clips to +/-rate_max, so the returned command moves at most rate_max*dt164 from u_prev; the first call needs a defined u_prev.165- Daisy chain order matters: the primary group allocates first up to its166 limits and the residual goes to the secondary group; swapping the groups167 changes the achieved allocation.168- Dimension mismatches, non-finite inputs, inverted limits (u_min > u_max),169 negative rate_max and non-positive dt or weight raise ValueError.170171## Related leaves172173- gnc-autonomy/control/pid-control-design: produces the moment command174 that this leaf distributes to the effectors.175- gnc-autonomy/control/observer-design: state feedback for the176 control law upstream of the allocation.177- gnc-autonomy/control/python-control-design: control law margin checks178 in the same ARP4754A development context.179180## Behavior contract (gate 3)181182Run the deterministic contract test (stdlib unittest, offline):183184 python3 scripts/test_control_allocation.py185186The test covers the pseudoinverse split and minimum-norm property on187the two-effector roll case and the three-axis six-effector case, the188singular-gram regularization path, damped least squares values and the189lambda = 0 delegation, weighted allocation pushing the command to the190lower-cost effector, clipping masks at both limits, the redistributed191pseudoinverse worked example ([0.3, 0.5], zero error, saturated list),192full-saturation residual retention, the max-iter bound, rate-limit step193progression and reversal, daisy chain primary and secondary saturation194behavior, direct allocation inside the box and at the box bound, the195allocation verdict fields, and ValueError rejection of dimension196mismatch, non-finite input, inverted limits, negative rate_max,197non-positive dt and non-positive weights.198199## Compliance200201- Standards referenced, not reproduced: ARP4754A (Aerospace202 Recommended Practice, SAE) frames the control law development and203 validation context; the allocation mathematics above is standard204 engineering methodology, summary-only per standards-map.yaml.205- compliance: STANDARDS-REF, gated: false.