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

LEGACY - Singularity Handling in the GNC Stack — Inventory & Streamlining

LEGACY - Parameter sprawl has been deleted

Investigation report — generated 2026-06-06 23:58 EDT. Report-only; no code changed.

Answer First

Scope

Runtime evidence (run_PM0848, 50 000 steps)

Signal (npz key) min mean max time below high=0.05 below low=0.01
diagnostics.conditioning.s_min_G 1.4e‑4 0.0204 0.108 96.1% 30.0%
diagnostics.conditioning.gamma_scale 0.25 (floor) 0.4499 1.0

s_min_J, j_plus_scale, and the Tikhonov lam are not logged → mechanisms D/E/F (below) run blind. Tikhonov lam = max(0.0001, 0.05² − s_min_G²) is positive whenever s_min_G < 0.05, i.e. ~96% of the run, so the damped solve is effectively always engaged.

Inventory — the two signals × seven mechanisms

Every row ultimately calls scale_by_svd(signal, threshold) (utils/sampling.py:59) except A (Tikhonov) and G (manual ratio).

# Mechanism Site Signal Config path it reads Enable gate
A Tikhonov damped Γ⁻¹ (reduced→full velocity) BreveController.py:244‑259 s_min_G controller.base.gamma_regularization {damping, floor, v_max, clip_velocity} gamma_regularization.enable
B CoM/base speed derate (scales des v_c, a_c², ω_b) base_guidance.py:147‑154 s_min_G controller.base.conditioning.gamma.sigma controller.com.speed_derating.enable
C Arm gain scale (scales arm D,K in breve RHS) BreveController.py:512‑520 s_min_G controller.base.conditioning.gamma.sigma controller.base.conditioning.gamma.enable
D Base torque scale (scales τ_b⊕) BreveController.py:126‑141 s_min_J controller.base.conditioning.j_plus.sigma controller.base.conditioning.j_plus.enable
E Base-goal blend (rotates base attitude goal inward) base_guidance.py:156‑170 s_min_J controller.base.conditioning.j_plus.sigma controller.base.conditioning.j_plus.enable
F Integral-windup gate (freezes EE integral) BreveController.py:321‑326 s_min_J arm.integral.scale_gate + j_plus.sigma arm.integral.enable
G EE pose blend near singular Γ (toward fallback pose) ee_guidance.py:702‑714 s_min_G controller.trajectory.conditioning.gamma.floor — (floor=0.0 ⇒ off)
H Startup config sampler (picks non-singular q₀) utils/sampling.py:59 both top-level conditioning.{gamma,j_plus} conditioning.*.enable

Config sprawl — every knob feeding the two signals

Config path Value Consumed by Note
controller.com.speed_derating.enable true B (gate) gate only
controller.com.speed_derating.floor 0.25 DEAD — B reads base.conditioning.gamma.sigma.floor instead (base_guidance.py:148)
controller.base.gamma_regularization.{damping,floor,v_max,clip_velocity,enable} 1e‑4 / 0.05 / 50 / true / true A Tikhonov; floor is a σ-floor in lam=max(damping, floor²−σ²)
controller.base.conditioning.gamma.sigma.{low,high,floor} 0.01 / 0.05 / 0.25 B, C the gamma ramp
controller.base.conditioning.j_plus.sigma.{low,high,floor} 0.02 / 0.05 / 0.1 D, E the j_plus ramp
controller.trajectory.conditioning.gamma.floor 0.0 G makes G a no-op (s_min ≥ 0 always)
arm.integral.scale_gate F gates integral on j_plus scale
top-level conditioning.gamma.{floor,step_size,num,enable} 0.25 / 0.35 / 96 / true H (sampler) name-collides with controller.base.conditioning.gamma
top-level conditioning.j_plus.{soft_floor,hard_floor,damping,inv_norm_max,enable,...} 0.02 / 0.005 / 1e‑4 / 1000 / true H (sampler) name-collides with controller.base.conditioning.j_plus

The two ramps (gamma, j_plus) differ only in low (0.01 vs 0.02) and floor (0.25 vs 0.1); high is identical (0.05).

Key functions (call order)

  1. scale_by_svd(s_val, s_opts)utils/sampling.py:59. The one ramp primitive: floor for σ≤low, 1.0 for σ≥high, linear between. Every derating site funnels here.
  2. BaseGuidance.build_*_for_stepderate_desired_by_gamma / blend_base_goal_by_jplusbase_guidance.py:129‑170. Mechanisms B and E; B’s enable and threshold live in different blocks.
  3. BreveController.j_plus_condition_scale / gamma_condition_scaleBreveController.py:126‑132. Thin wrappers over scale_by_svd; feed C, D, F.
  4. BreveController.reconstruct_generalized_velocityBreveController.py:244‑259. Mechanism A (Tikhonov); the only place Γ is actually inverted.
  5. EEGuidance.blend_near_singular_Gammaee_guidance.py:702‑714. Mechanism G; disabled by config.

Findings

F1 — Singularity handling is the operating regime, not a guard. Evidence: s_min_G < 0.05 for 96.1% and < 0.01 for 30% of run_PM0848; gamma_scale mean 0.45, floor 0.25 frequently active. Impact: These knobs shape every step, so their sprawl is a daily tuning/debugging tax, not an emergency-only concern. Streamlining is high-value. Next step: Treat the conditioning ramp as a primary controller gain, not a safety afterthought.

F2 — Name collision: two conditioning.{gamma,j_plus} trees. Evidence: runtime block at parameters.yaml:81‑93 vs sampler block at parameters.yaml:138‑155; disjoint consumers (controller/guidance vs robot.sampling). Impact: Editing conditioning.gamma.floor changes the startup sampler, not runtime derating — the single biggest “impossible to tune” trap. Next step: Rename the top-level sampler block to config_sampling (or nest it under robot:).

F3 — Dead and triplicated knobs. Evidence: com.speed_derating.floor=0.25 is never read (base_guidance.py:148 uses base.conditioning.gamma.sigma); the value 0.25 also appears at parameters.yaml:87 and :155. Impact: Edits to the dead floor silently do nothing; duplicated values drift apart. Next step: Delete speed_derating.floor; make the floor live in one place.

F4 — One physical singularity, two signal families. Evidence: Appendix B of the corrected reference — Γ⁻¹’s only inverse factor is (J_νe⊕)⁻¹, in three of its blocks; so σ_min(Γ)→0 ⇔ σ_min(J_νe⊕)→0. Impact: The gamma (s_min_G) and j_plus (s_min_J) ramps fire on the same event. Maintaining two threshold sets is largely redundant. Next step: Drive all mechanisms off the single instrumented signal s_min_G, unless a measured reason to split survives.

F5 — j_plus path is uninstrumented. Evidence: npz logs only s_min_G and gamma_scale; no s_min_J / j_plus_scale (controller_diagnostics, BreveController.py:528‑535 logs only gamma). Impact: Mechanisms D/E/F (base torque, base-goal blend, integral gate) cannot be observed or tuned from logs. Next step: Log s_min_J and j_plus_scale alongside the gamma pair.

F6 — A mechanism is silently disabled by a floor value. Evidence: trajectory.conditioning.gamma.floor = 0.0 (parameters.yaml:247‑249); blend_near_singular_Gamma returns raw_pose whenever s_min ≥ floor (ee_guidance.py:706‑707), and σ ≥ 0 always. Impact: The EE pose-blend mechanism is off, but nothing says so; a reader assumes it runs. Next step: Decide if pose-blend is wanted; if not, delete the mechanism + config; if yes, set a real floor.

F7 — Inconsistent enable ownership for the gamma family. Evidence: B is gated by com.speed_derating.enable (base_guidance.py:129); C by base.conditioning.gamma.enable (BreveController.py:514) — two flags for the same signal. Impact: Turning “gamma conditioning off” requires flipping flags in two blocks; easy to half-disable. Next step: One master enable per conditioning family.

F8 — Thresholds may be mis-scaled to this robot’s σ range. Evidence: s_min_G exceeds high=0.05 only 3.9% of the run, so the ramp almost never reaches the “healthy = 1.0” end; the UR3-on-cube neutral config is itself singular (full_paper.tex:398). Impact: The ramp effectively runs in floor/linear region permanently — the high knob barely matters, and floor (0.25) is doing the real work. Tuning high feels inert. Next step: Re-fit {low, high} to the observed σ distribution (e.g. percentiles of s_min_G), or confirm the robot is meant to live near-singular and treat floor as the primary gain.

Proposed streamlining

Justified by F4 (Γ ⇔ J_νe⊕ singular together) and F1/F5 (only s_min_G is instrumented and it already governs the dominant mechanisms). Replace all six blocks with:

controller:
  conditioning:           # ONE block; signal = s_min_G
    enable:        true
    sigma:  { low: 0.01, high: 0.05 }   # the single ramp (F8: re-fit to σ distribution)
    floor:         0.25                  # single derating cap, shared by B/C/D/E
    gamma_damping: 0.0001                # Tikhonov (mechanism A) only extra knob

Net: ~4 runtime knobs (low, high, floor, gamma_damping) + one enable, replacing ~15.

Option B — two ramps, if base-attitude needs its own cap

Keep the gamma/j_plus split only as the conditioning consumer, one ramp each, but: rename the sampler block (F2), delete speed_derating.floor (F3), unify enables (F7), and instrument s_min_J (F5). ~8 knobs. Choose this only if a log-backed reason to treat base-attitude derating differently from velocity/gain derating emerges.

Proposed Next Tasks

TITLE: Collapse conditioning config to one ramp BODY: Implement Option A: introduce controller.conditioning, point mechanisms A–F at it, delete the redundant blocks. Modify: YAMLs_by_domain/parameters.yaml (via owning loader), GNC/BreveController.py (126‑132, 244‑259, 512‑520), GNC/base_guidance.py (129‑170). Inspect-not-modify: utils/sampling.py, the dyn module computing s_min_G. Create: none. Validation: A/B a short run vs run_PM0848; gamma_scale, tau_b, EE error within tolerance; assert no parameters.yaml key reads removed paths (grep).

TITLE: Resolve the conditioning name collision BODY: Rename the top-level sampler conditioning: block to config_sampling: (or nest under robot:); update robot.sampling access. Modify: YAMLs_by_domain/parameters.yaml:138‑155, the sampler loader/consumer. Validation: startup config search still selects the same q₀ on a fixed seed.

TITLE: Instrument the j_plus path BODY: Add s_min_J and j_plus_scale to controller_diagnostics so D/E/F are observable. Modify: GNC/BreveController.py:528‑535. Validation: new keys appear in the npz; finite over a run.

TITLE: Decide the fate of EE pose-blend (mechanism G) BODY: It is disabled by trajectory.conditioning.gamma.floor = 0.0. Either remove blend_near_singular_Gamma + config, or set a real floor and validate. Modify / Create: none yet — decision first. Validation: if removed, coverage/tracking unchanged on a full run.