data_classes — core sim records & dataclass philosophy

Purpose

The per-step record types the control loop passes around: State (live q, v + circumcentroidal
views), Desired (reference targets), the actual-kinematics KinematicsCache, the ForceCache
control effort, Gains, and the grouped StepLog. Each carries its own field-normalization so
call-sites stay thin.

Role in the system

  • Consumed across the control chain: com_controller / breve_controller build a Desired and
    read State each tick, then emit a ForceCache of control effort.
  • The guidance chain (com_guidancebase_guidanceee_guidance, guidance_rollout) builds
    the Desired targets.
  • State.update_views calls into robot (GiordanoRobot.Gamma) to refresh the cached reduced views.
  • All records subclass Package from infra; field defaults come from data_class_helpers
    (default_flat_zero, default_z, default_eye).
  • StepLog is the per-step serialization that feeds the logger → data_analyzer pipeline.
  • enum_from_value coerces YAML config strings into enums for orchestrator / analysis/statistics.py.

Inputs / Outputs

  • In: raw q, v arrays, motion records from robot, YAML gain specs, reference targets.
  • Out: normalized column/unit/matrix fields; stacked views (v_breve, K_breve, D_breve);
    grouped log packages.

Key classes & methods

  • State — live (q, v) + cached v_c / omega_b / nu_e_oplusutils/data_classes.py:103
    • update_views — refresh the circumcentroidal views via Γ(q)·v:124
    • v_breve — reduced velocity (base rate over reduced EE twist) — :134
  • Desired — reference targets, normalized to col3 / unit / mat3 in __post_init__:144
  • KinematicsCache — cached actual kinematics for the (q, v) it was built from; matches/update:180
  • ForceCache — control-effort record (f_c, τ_b⊕, ω_e⊕, f_b, τ_b, τ) — :66
  • Gains — gain specs → 3x3 matrices, with stacked K_e/K_breve (etc.) views — :315
  • StepLog — one step’s grouped record (state, tracking pairs, effort, diagnostics) — :246
  • enum_from_value — coerce a raw value/list into an enum (lowercased) — :54
  • EEKinematicsCache = KinematicsCache — back-compat alias still imported by ee_guidance:311

Footguns

Prefer MUTABLE (non-frozen) dataclasses

Frozen dataclasses force object.__setattr__ in every __post_init__; reserve frozen for true
value objects. Freeze decisions are a Codex-era sensitivity — always explain the choice and expect
questions. (utils/INSIGHTS.md [philosophy])

One owner per field — never hand-repeat field-name strings

If a record gains or loses a field, only its owner or its named normalizer changes. A long,
dict-like constructor is a smell: make a named normalizer owned by the relevant module, and iterate
__dataclass_fields__ rather than listing names by hand. (utils/INSIGHTS.md [convention])

Normalization belongs in __post_init__, not at the call site

Records carry their own field-shaping (flat→col3, unit vectors, 3x3 matrices) so consumers stay
thin. Desired and KinematicsCache reshape every field by name prefix; don’t re-normalize
upstream. (utils/INSIGHTS.md [philosophy])

Pseudocode (a control step’s record flow)

des = Desired(p_c, z_b, p_e, z_e, ...)   # __post_init__ → col3 / unit / mat3
st  = State(q, v)                        # flattened; views None until refreshed
st.update_views(robot)                   # Γ(q)·v → v_c, omega_b, nu_e_oplus
... controller produces ForceCache ...
log = StepLog(desired=des, actual=cache) # groups (actual, desired) pairs + effort by subsystem

infra · data_class_helpers · robot · breve_controller · com_controller · ee_guidance · data_analyzer · terminology