ee_pose_error_antiwindup — EE pose error & integral anti-windup
Concept
How the end-effector task error is formed and how its integral is folded into the same stiffness slot
without winding up near a singularity. The pose errorx̃_e = [position ; pointing](axis-only roll),
its leaky integral with anti-windup, and theJᵀKintegral fold all live in one file —
breve_controller — but they form one closed loop that the per-file page documents only piece by
piece. This page documents the loop. (Math: current_sota > 4, the error law in §4.1 and the fold in §4.4.)
What this spans
GNC/breve_controller.py(breve_controller) — the whole loop: pose-error formation, the leaky
integral + anti-windup, theK⁻¹ I x_intfold, and the EE wrench (P+D+I) that consumes them.- Thesis: there is no separate integral-gain matrix in the reduced RHS — the RHS exposes a single
-JᵀK xstiffness slot, so the error itself is pre-augmented tox̃ + K⁻¹ I x_int. The whole reason the
integral exists as a fold (not a parallel term) is to stay consistent between the EE wrench and the
dynamics RHS, and the whole reason it has a conditioning gate is to keep that fold from winding up when the
arm is derated against the singular wall.
Constituent pages
Down-links to the per-file page that holds each part’s contract:
- breve_controller —
BreveController: definesx_e_tilde,update_x_e_integral,x_e_rhs,
compute_omega_e_oplus, and the axis-only roll projection. See its Key methods map and its
“EE integral folds throughJᵀK” footgun.
Mechanism (where it lives in code)
One EE control step, error → integral → fold → wrench:
- Pose error
x̃_e(6×1,[p_ee_d ; 2 ε_ee_d], the pointing part is the quaternion vector ×2, roll
not yet removed) —GNC/breve_controller.py:163 - Leaky integral + anti-windup —
update_x_e_integral: zeroed unlessarm.integral.enable; attitude
dropped unlessinclude_attitude; roll forced to 0 in axis-only mode; integration SKIPPED when
conditioning_scale(s_min_G) < scale_gate; thenx_int ← (1 − leak·dt)·x_int + x̃·dtwith a saturating
clamplimit(units m·s / rad·s) —GNC/breve_controller.py:174 - The fold (
K⁻¹ I x_int) —x_e_rhs: axis-only roll projection, then pre-augments the error by
np.linalg.solve(K, I @ x_int)so the single-JᵀK xslot delivers both P and I —GNC/breve_controller.py:207 - EE wrench (P+D+I) —
compute_omega_e_oplus:P = -Jᵀ K x̃,D = -D ν̃,I = -Jᵀ I_e x_int
(the integral term built from the block-diagonalI_e, computed only when enabled) —GNC/breve_controller.py:276 - The error-rate Jacobian that multiplies all three blocks —
J_xe—GNC/breve_controller.py:315
Two consumers, one integral — keep them consistent
The integral appears in two places that must agree: the EE wrench
ω_e⊕(termIin
compute_omega_e_oplus,GNC/breve_controller.py:303) and the dynamics RHS (theK⁻¹ I x_intfold in
x_e_rhs,GNC/breve_controller.py:221). TheK⁻¹in the fold converts integral-gain units back into
pose-error units so the net integral gain is exactlyIin both paths. (current_sota > 4 §4.4)
Evidence
This is a structural topic — the load-bearing claim is the single-slot fold contract, established at the
committed homes:
- Eq ↔ code for the error law and the integral fold (Giordano eqs 24–25 for
x̃, the §4.4 fold derivation):
generated_reports/GNC/cross_check.md. - The why of the conditioning gate and the impedance-derate stack the gate keys off:
GNC/INSIGHTS.md([derate],[control]). - The operational-regime context for the EE loop (the EE actually tracks in HOLD; the integral stays OFF in
the nominal regime):validation/INSIGHTS.md/ MEMORY (operational-regime-is-hold-velocity-lag).
Footguns
EE integral folds through
JᵀKand is OFF during hard derateThe integral enters as
x̃ + K⁻¹ I x_int— oneK⊕slot for both P and I; there is no separate
integral gain matrix in the RHS (GNC/breve_controller.py:216). Integration is skipped when
conditioning_scale(s_min_G) < scale_gate(GNC/breve_controller.py:195) to avoid windup while the EE
task is derated against the reach singular wall — the accumulator is held, not reset, so it does not
dump on recovery. (Lifted from the breve_controller footgun “EE integral folds throughJᵀK”.)
Roll is never integrated, and the pointing part is a quaternion vector — not an angle
x_e_tildereturns2·ε_ee_dfor the pointing block (GNC/breve_controller.py:170), the quaternion
vector part — not a1 − cos θversine and not a chord. In axis-only mode the local EE roll error
(row 6) is projected out before the fold and zeroed again after it (GNC/breve_controller.py:213,232),
andupdate_x_e_integralforces the roll slot to 0 (GNC/breve_controller.py:189). For any measured
pointing number, go throughvalidation/signal_measurement(the catalog versine), never re-readx̃_e.
Equations & references
Key equations mirrored from current_sota — the math source of truth; see there for derivations.
EE pose error + error-rate Jacobian — §4.1, eqs (4.1–4.2):
Integral fold (one slot carries P+I) — §4.4, eq (4.13):
References:
- Error coordinates
x̃_e = [p_ee_d ; 2 ε_ee_d]and the error-rate JacobianJ_{x̃_e}(§4.1):
current_sota > 4 ·GNC/breve_controller.py:163. - Integral fold
-JᵀK(x + K⁻¹ I x_int) = -JᵀK x - Jᵀ I x_int(§4.4): current_sota > 4 ·
GNC/breve_controller.py:207. - Eq ↔ code cross-check (both Giordano-2019 typos fixed):
generated_reports/GNC/cross_check.md.
Related
circumcentroidal_control · speed_gain_derate · breve_controller · current_sota · terminology