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 error x̃_e = [position ; pointing] (axis-only roll),
its leaky integral with anti-windup, and the JᵀK integral 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, the K⁻¹ I x_int fold, 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 x stiffness slot, so the error itself is pre-augmented to x̃ + 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_controllerBreveController: defines x_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 through Jᵀ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-windupupdate_x_e_integral: zeroed unless arm.integral.enable; attitude
    dropped unless include_attitude; roll forced to 0 in axis-only mode; integration SKIPPED when
    conditioning_scale(s_min_G) < scale_gate; then x_int ← (1 − leak·dt)·x_int + x̃·dt with a saturating
    clamp limit (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 x slot 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-diagonal I_e, computed only when enabled) — GNC/breve_controller.py:276
  • The error-rate Jacobian that multiplies all three blocks — J_xeGNC/breve_controller.py:315

Two consumers, one integral — keep them consistent

The integral appears in two places that must agree: the EE wrench ω_e⊕ (term I in
compute_omega_e_oplus, GNC/breve_controller.py:303) and the dynamics RHS (the K⁻¹ I x_int fold in
x_e_rhs, GNC/breve_controller.py:221). The K⁻¹ in the fold converts integral-gain units back into
pose-error units so the net integral gain is exactly I in 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 , 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ᵀK and is OFF during hard derate

The integral enters as x̃ + K⁻¹ I x_intone K⊕ 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 through JᵀK”.)

Roll is never integrated, and the pointing part is a quaternion vector — not an angle

x_e_tilde returns 2·ε_ee_d for the pointing block (GNC/breve_controller.py:170), the quaternion
vector part — not a 1 − 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),
and update_x_e_integral forces the roll slot to 0 (GNC/breve_controller.py:189). For any measured
pointing number, go through validation/signal_measurement (the catalog versine), never re-read x̃_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 Jacobian J_{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.

circumcentroidal_control · speed_gain_derate · breve_controller · current_sota · terminology