Doctoral Research · Space Robotics Inspection with a Free-Flying Space Manipulator
A Doctoral Research Journal Aerospace Engineering

SOTA Control — 6-DOF Singularity Handling: how the controller changes as \(\sigma_{\min}\) falls

Investigation report (report-only; no code modified). Generated 2026-06-16, ~15:45 GMT-4. Companion to docs/week2/section5_singularities_7dof.md; this note answers one narrow question for the 6-DOF arm only, verified against the live code.

Answer first

As the smallest singular value falls, the controller degrades gracefully through a fixed four-stage cascade, each stage triggered at a different \(\sigma\) threshold and each with a distinct cost:

Two signals drive this. The derate and the \(\Gamma\)-solve key on \(s_{\text{min\_G}}=\sigma_{\min}(\Gamma)\); the \(J^{\oplus}\)-inverse tiers key on \(s_{\text{min\_J}}=\sigma_{\min}(J^{\oplus})\) directly (utils/robot.py:184). Because \(\Gamma\) is singular iff \(J^{\oplus}\) is (F4, comment GNC/breve_controller.py:311-312), as \(s_{\text{min\_J}}\) falls \(s_{\text{min\_G}}\) falls ~1:1, so the question “what happens as \(s_{\text{min\_J}}\) decreases” has one clean answer for both.

Scope

Sequential flowchart — the \(\sigma_{\min}\) cascade (6-DOF)

flowchart TD
    H["HEALTHY  s_min >= 0.05<br/>exact inverse np.linalg.inv<br/>gamma = 1.0, Gamma-solve undamped"]
    A["1. s_min ~ 0.049 — Gamma Tikhonov ON<br/>lambda = max of 1e-4 and 0.05^2 - s_minG^2<br/>damps velocity-reconstruction DIRECTION<br/>cost: demand-direction corruption"]
    B["2. s_min = 0.025 — derate gamma leaves 1.0<br/>scales arm gains D,K and base wrench tau_b<br/>vehicle slows as 2nd-order impedance effect<br/>cost: time / schedule lag"]
    C["3. s_min = 0.02 — J-plus SOFT floor<br/>exact inverse to damped least-squares<br/>lambda = max of 1e-4 and 0.02 - s_min<br/>cost: bounded but inexact gain"]
    D["4. s_min = 0.005 — BOTH floors hit<br/>gamma pinned 0.25 AND J-plus HARD floor<br/>hold last_J_plus_inv, norm guard > 1000 reverts<br/>cost: stale-but-valid map, not exact kinematics"]
    E["s_min < 0.005<br/>gamma = 0.25 saturated, J-plus map frozen"]
    X["EXCLUDED 6-DOF: no kernel freeze / null-space<br/>z_a is None, guarded by if n > 6"]

    H --> A --> B --> C --> D --> E
    B -.->|"7-DOF only: kernel freeze also engages here"| X

    classDef external fill:#fff3cd,stroke:#d39e00,color:#3d2b00;
    class X external;

Compact zone table

\(\sigma_{\min}\) band Derate \(\gamma\) \(\Gamma\)-solve \(J^{\oplus}\) inverse
\(\ge 0.05\) 1.0 undamped (\(\lambda=10^{-4}\)) exact inv
\([0.025,\ 0.05)\) 1.0 Tikhonov damped exact inv
\([0.02,\ 0.025)\) ramping \(<1\) Tikhonov damped exact inv
\([0.005,\ 0.02)\) ramping \(\to 0.25\) Tikhonov damped damped least-squares
\(< 0.005\) 0.25 (floor) Tikhonov damped + clip hold last-healthy

The \([0.025,\ 0.05)\) row is the ordering inversion (Finding F2): direction is already damped while speed is still full.

Key functions (call order as \(\sigma\) falls)

  1. \(\Gamma\) Tikhonov velocity reconstructionreconstruct_generalized_velocity (GNC/breve_controller.py:541-559). Damping \(\lambda=\max(10^{-4},\ \varepsilon_\Gamma^2-s_{\text{min\_G}}^2)\) with \(\varepsilon_\Gamma=0.05\) (parameters.yaml:84); solves \((\Gamma^{\mathsf T}\Gamma+\lambda I)^{-1}\Gamma^{\mathsf T}y\), then a constant clip \(|v|\le v_{\max}=50\) (:554-556, parameters.yaml:82).
  2. Derate ramp \(\gamma\)conditioning_scale (GNC/breve_controller.py:313-316) → scale_by_svd (utils/sampling.py:70-78): floor \(0.25\), knees \([0.005,\ 0.025]\) (parameters.yaml:86-91). Applied to the arm gains via arm_gain_scale (:319-322) and the base wrench \(\tau_b\) in compute_tau_b_oplus (:333).
  3. Damped \(J^{\oplus}\) inversedamped_inverse + regularized_svd (utils/robot.py:173-210): soft floor \(0.02\), hard floor \(0.005\), DLS \(\lambda=\max(10^{-4},\ 0.02-\sigma)\), square-\(6\times6\) exact np.linalg.inv path (:189-190), norm guard inv_norm_max=1000 (:196-199).
  4. Signal source\(s_{\text{min\_J}}\) and \(s_{\text{min\_G}}\) are computed once per step in all_dynamics_terms (utils/robot.py:243, 255); both are logged (GNC/breve_controller.py:564-583), \(s_{\text{min\_G}}\) is the live derate driver.

Findings (vs. section5_singularities_7dof.md)

F1 — The derate scales impedance, not velocity. Evidence: \(\gamma\) multiplies the arm gain blocks (breve_controller.py:319-322) and the base wrench \(\tau_b\) (:333); no site multiplies \(v_c\), \(\omega_b\), or \(\nu_e^{\oplus}\) by \(\gamma\). The only hard velocity bound is the constant \(v_{\max}=50\) clip in the \(\Gamma\)-solve (:554-556), which is not \(\gamma\)-scaled. Impact: section5 Delta-1 reads as if \(\gamma\) scales the velocities directly; in the live code the vehicle slows as a second-order consequence of softened impedance. The descriptive claim should be corrected, the numbers are unaffected. Next step: reword section5 Delta-1 (“scales the impedance gains and base wrench” not “the velocities”).

F2 — Ordering inversion in \([0.025,\ 0.05)\) is live. Evidence: \(\Gamma\)-Tikhonov engages at \(\sigma\approx0.049\) (:551) but the derate does not leave \(\gamma=1\) until \(\sigma=0.025\) (parameters.yaml:90). Impact: in that band the controller corrupts the demand direction (the more lossy tool) while speed is still full — the opposite of the published cost hierarchy (speed-scaling before damping). Next step: the candidate fix (section5 §6) re-harmonizes \(\varepsilon_\Gamma\) from \(0.05\) to \(0.025\) so the two onsets coincide; investigate-only, no change made here.

F3 — The integral-freeze gate is dormant in the nominal config. Evidence: update_x_e_integral freezes the EE integral when \(\gamma<\text{scale\_gate}=0.03\) (breve_controller.py:243-245), but arm.integral.enable=false by default (parameters.yaml:111). Impact: a real but inert code path for 6-DOF as shipped; a footnote, not a cascade stage. Next step: none unless the integral is enabled.

(Line numbers in section5 have drifted — e.g. conditioning_scale 302-305 → 313-316, reconstruct_generalized_velocity 507-516 → 541-559 — but every threshold value in section5 still matches the live code: floor 0.25, knees 0.005/0.025, \(\varepsilon_\Gamma\) 0.05, soft 0.02, hard 0.005.)

Proposed next tasks

  1. TITLE: Re-harmonize the \(\Gamma\) Tikhonov onset to remove the ordering inversion. BODY: Set \(\varepsilon_\Gamma=0.025\) so \(\Gamma\)-damping and the derate engage together; re-run a near-singular A/B and check in-window pointing error. Modify: none yet (investigate first). Inspect-not-modify: reconstruct_generalized_velocity, threshold_derivation.md §6. Validation: matched A/B at \(\varepsilon_\Gamma\in\{0.05,\ 0.025\}\), compare \(p_e\) in \([0.025,0.05)\).
  2. TITLE: Correct section5 Delta-1 wording (impedance, not velocity). BODY: Update the prose to say \(\gamma\) scales the arm gains and base wrench; note the separate constant \(v_{\max}\) clip. Modify: section5_singularities_7dof.md (user-owned). Validation: re-read against breve_controller.py:319-333.