Skip to content

feat(actuators): Magic_Actuator class + planner integration - #80

Open
patrickmckeen wants to merge 1 commit into
mainfrom
feat/magic-actuator
Open

feat(actuators): Magic_Actuator class + planner integration#80
patrickmckeen wants to merge 1 commit into
mainfrom
feat/magic-actuator

Conversation

@patrickmckeen

Copy link
Copy Markdown
Collaborator

Adds the Magic actuator type to Generalized_ADCS. The OldPlanner C++ (`pysat.Satellite::add_magic`) has always supported magic actuators; only the Python class was missing.

What

File Change
`ADCS/satellite_hardware/actuators/magic.py` New `Magic_Actuator` class — direct body torque `τ = a·(u+b) + n` with no state/environment coupling
`ADCS/satellite_hardware/actuators/init.py` Export `Magic_Actuator`
`ADCS/controller/plan_and_track/build_csat.py` `add_actuator` dispatches to `csat.add_magic(...)`; `get_cpp_to_python_control_permutation` handles the third actuator class (C++ ordering is `[MTQs, RWs, magics]`); fixed existing minor bug (`act.name` → `type(act).name`)

Tests

  • `testing/test_actuators/test_actuator_magic.py` — 19 unit tests (torque is `a·u` exactly, axis/rate/attitude independence, bias adds to command, derivatives, jacobians/hessians API, FD sanity)
  • `testing/test_controllers/test_planner_magic_actuator.py` — 5 integration tests (routes through `build_csat`, parametrized LQR-Riccati bit-exact match, mixed actuator ordering)

Local: 24 passed, 0 new failures in the controller + actuator suites.

Why this matters

The Magic actuator gives a clean body-torque commander with no MTQ rank-deficiency, no RW Newton-3rd back-reaction, and no wheel-momentum state. The closed-form Riccati test in this PR uses `J` directly (no `J_eff = J − J_RW` parallel-axis correction), giving a strictly simpler equivalence proof than the RW-based version in `test_planner_riccati_closed_form.py`.

The user's "magic actuators for test robustness" suggestion delivered. Future tests can now use this as the default direct-body-torque commander.

Related

  • OldPlanner (`trajectory_planner/`) already supports magic actuators at the C++ level — this PR just adds the Python class to drive it.
  • SALTRO (separate repo) has `magic_control_weight` as a leftover cost setting but the actuator class was lost in the port. Restoring SALTRO's magic-actuator C++ class is a separate, larger task tracked for follow-up.

🤖 Generated with Claude Code

Adds the Magic actuator type back to Generalized_ADCS as a Python
``Magic_Actuator`` class wired through ``build_csat.py`` to the
existing C++ ``Satellite::add_magic`` API in OldPlanner.

The OldPlanner has had full magic-actuator support since forever
(``pysat.add_magic(axis, max_torq, cost)``, the ``magic_torq`` field
in ``Satellite::dist_torque``, the magic-cost block in ``stepcost``),
but Generalized_ADCS had no Python actuator class to drive it. The
companion regression in SALTRO -- where ``magic_control_weight`` is
a leftover cost setting but the actuator class itself was lost in
the OldPlanner -> SALTRO port -- is tracked separately and not
addressed by this PR.

Files changed
-------------

* ``ADCS/satellite_hardware/actuators/magic.py`` -- new
  ``Magic_Actuator(axis, max_torque, bias=None, noise=None,
  estimate_bias=False)`` class. Direct body torque
  ``tau = a * (u + b) + n`` with no state or environment dependence.
  Overrides ``torque``, ``dtorq__du``, ``dtorq__dbias``,
  ``jacobians``, ``hessians`` for the obvious-nonzero terms.
  All other derivatives use the base Actuator zero defaults
  (state-independent, momentum-storage-free torque -> almost
  everything is zero).

* ``ADCS/satellite_hardware/actuators/__init__.py`` -- export
  ``Magic_Actuator``.

* ``ADCS/controller/plan_and_track/build_csat.py``:
  - Updated ``get_cpp_to_python_control_permutation`` to handle the
    third actuator class. C++ control vector ordering is
    ``[MTQs, RWs, magics]`` (from ``Satellite::stepcost``); the
    permutation logic now mirrors that.
  - Updated ``add_actuator`` to dispatch ``Magic_Actuator`` to
    ``csat.add_magic(axis, max_torq*scale, magic_control_weight)``.
  - Fixed a small existing bug along the way: the error message in
    the fallback ``raise ValueError`` used ``act.__name__`` (instances
    don't have ``__name__``) instead of ``type(act).__name__``.

Tests
-----

* ``testing/test_actuators/test_actuator_magic.py`` -- 19 unit tests:
  ``torque`` returns ``a * u`` exactly (parametrized over axes);
  independent of attitude and rate; bias adds to command; saturation
  warning; ``storage_torque`` empty; ``dtorq__du = axis``;
  ``dtorq__dbasestate`` and ``dtorq__dh`` are zero; all 2nd
  derivatives zero; ``jacobians()``/``hessians()`` API bundling
  matches MTQ/RW; FD check on ``dtorq__du``.

* ``testing/test_controllers/test_planner_magic_actuator.py`` -- 5
  integration tests:
  - ``test_magic_actuator_routes_through_build_csat`` -- planner
    plans successfully with 3 magic actuators + 3 dummy MTQs, control
    rows in expected positions, MTQs unused (huge weight) and the
    y-axis magic carries the slew.
  - ``test_magic_actuator_eigenaxis_lqr_matches_riccati`` --
    parametrized over omega0 in {0.005, 0.01, 0.02}: with a SINGLE
    y-axis magic actuator plus 3 dummy MTQs, q0=identity, single-y
    omega perturbation, cost-weight conversion (8x, 2x, 2x), the
    planner provably reduces to the 2-state LQR Riccati to ~1e-5
    absolute. Cleaner than the RW-based version of this test in
    ``test_planner_riccati_closed_form.py`` -- no ``J_eff = J - J_RW``
    parallel-axis correction needed; the magic actuator applies
    torque directly.
  - ``test_magic_actuator_actuator_ordering`` -- mixed MTQ/RW/magic
    fleet (interleaved in Python list) produces the expected control
    layout after reordering back to Python ordering. Confirms the
    permutation logic handles all three actuator types.

Local results
-------------

* ``test_actuator_magic.py``: 19 passed.
* ``test_planner_magic_actuator.py``: 5 passed.
* Fast subset of ``test_actuators/`` + ``test_controllers/``: 63
  passed, no new failures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant