analytic_feedforward — closed-form EE desired-twist feedforward
Purpose
Pure trajectory kinematics: from the CoM-to-surface vector and its time derivatives, build the
desired EE twist and its derivative in closed form (its_tex.md §3.4 / current_sota > 5),
replacing the finite-difference feedforward. Every function is a function of the reference
alone, never of the measured state.
Role in the system
- A stateless helper module (module-level functions, no class) supporting ee_guidance — the leaf
of the deep guidance chain com_guidance → base_guidance → ee_guidance. - The adopted default since 2026-06-21 (
controller.arm.desired_twist.analytic_ff = true,
parameters.yaml): the FF closed form replaced the FD path mission-wide (pe_median −28%, pe_p99 −11%,
coverage up) at a bounded conditioning cost (s_min_Gmedian 0.032→0.027). The 4 EE baselines + gold were
re-pinned. (logs/logs_Jun21_26/CLAIMS.md~16:00) reduced_desired_twistis called by ee_guidance (analytic_desired_nu_e_feedforward) and folds the
world twist into the circumcentroidal (⊕) frame the controller consumes.cruise_lag_flooris the same math the breve_controller runs asx_ss_floor— the controller’s own
prediction of the standing pose error the stiffness must hold once the feedforward has cancelled the
velocity/inertial demand.- Siblings supporting ee_guidance: target_finder, guidance_classes.
Inputs / Outputs
- In: the standoff reference
r, r_dot, r_ddot(CoM→surface vector + derivatives), CoM velocity/accel
v_cd, a_cd, standoff distancer_cam, reference framesR_ed, R_bd, reference positionsp_cd, and base
spinomega_bd. For the floor: the reduced matrices (J_x, K_breve, C_breve, C_c, D_breve, G_vc_breve) and
reference ratesv_breve_des, x_c_dot_err. - Out: the reduced (⊕) EE twist
nu_oplusand its derivativenu_oplus_dot(the feedforward the RHS adds);
and the quasi-steady reduced pose errorx_ssplus its two forcing components (Coriolis, CoM-coupling).
Key methods
reduced_desired_twist— closed-form reduced (⊕) EE twist + derivative from the reference —GNC/guidance/analytic_feedforward.py:110unit_vector_derivatives— direction-only derivatives ofu = r/|r|via the projectorP = I − uuᵀ—:7desired_ee_twist_world— world-frame desired twist; roll-freeomega_ed = u × u_dot—:27world_twist_to_local— rotate the world twist into the desired EE frame (carries one Coriolis term) —:39g_vc_desired/g_omega_b_desired— the COM→EE and base-spin→EE ⊕ maps on the REFERENCE frames —:60/:76cruise_lag_floor— quasi-steady reduced pose errorx_ssfrom static stiffness balance —:144
Footguns
Pure reference, POSE mode only
The closed form assumes
v_des = const,s_ddot = 0, andomega_bd_dot ≈ 0(base carries a slowly-varying
inward-pointing reference). It is POSE-mode only — in ANCHOR the scorer picks a different aim point, so the
closed form wouldn’t match the selected target and ee_guidance falls back to FD. (GNC/INSIGHTS.md[math])
axis_onlyzeroes roll BEFORE the ⊕ foldWith
axis_only, the local roll rate (component 5) and its derivative are zeroed before the circumcentroidal
fold, mirroring the FD path’snu_des[5] = 0. Folding first would leak roll into the reduced twist.
(GNC/INSIGHTS.md[math])
The lone Coriolis term in the linear block
Differentiating a body-frame quantity carries
d/dt(R_ed^T w) = R_ed^T(w_dot − omega_ed × w). The angular block
is clean (omega_ed × omega_ed = 0); the linear block keeps one Coriolis term−omega_ed × p_ed_dot. Dropping
it is a frame-convention sign footgun. (GNC/INSIGHTS.md[frame])
cruise_lag_flooruseslstsq— filter derated steps downstreamThe static balance
(J_xᵀ K_breve) x_ss = −forcingis solved withlstsq, so a near-singularJ_xᵀ K_breve
yields the minimum-norm balance rather than blowing up. Such derated steps must be filtered out of the
cruise comparison downstream, or the floor is meaningless there. (GNC/INSIGHTS.md[footgun] [derate])
Pseudocode (reduced_desired_twist)
u, u_dot, u_ddot = unit_vector_derivatives(r, r_dot, r_ddot) # direction-only, via P = I − uuᵀ
p_ed_dot, p_ed_ddot, omega_ed, omega_ed_dot = desired_ee_twist_world(...) # z_ed = u ; omega_ed = u × u_dot
nu_local, nu_local_dot = world_twist_to_local(R_ed, ...) # rotate in; linear block keeps −omega_ed × p_ed_dot
if axis_only: nu_local[5] = nu_local_dot[5] = 0.0 # zero roll BEFORE the fold
G_vc, G_wb (+ dots) on REFERENCE frames (R_ed, R_bd, p_cd, p_ed)
nu_oplus = nu_local − G_vc · v_cd + G_wb · omega_bd # circumcentroidal fold
nu_oplus_dot = nu_local_dot − (G_vc_dot·v_cd + G_vc·a_cd) + G_wb_dot·omega_bd
Equations & references
Key equations mirrored from current_sota — the math source of truth; see there for derivations.
Aim-direction rate (radial part projected out) — §5.5, eqs (5.8–5.9):
One pair feeds the full 6-DOF twist — §5.5, eq (5.14):
Acceleration feedforward into the RHS ( frame) — §5.6, eq (5.18):
References:
- Analytic desired twist (translational + rotational FF): current_sota > 5 §5.5.
- Conversion to the circumcentroidal (⊕) frame: current_sota > 5 §5.6.
- Cruise-lag (steady-state) error floor: current_sota > 4 §4.5 · eq↔code in
generated_reports/GNC/cross_check.md.
Related
ee_guidance · target_finder · guidance_classes · com_guidance · base_guidance · breve_controller · current_sota · terminology · ee_feedforward