✨ feat(transforms): kinematic acts for Scale/Reflect/Shear without a base point - #584
✨ feat(transforms): kinematic acts for Scale/Reflect/Shear without a base point#584nstarman wants to merge 11 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends kinematic (velocity/acceleration) transform support to the remaining constant linear transforms (Scale, Reflect, Shear) by adding a shared pushforward implementation on AbstractLinearTransform that applies the constant Jacobian matrix directly in Cartesian coordinates, avoiding the unnecessary requirement for a base point (at) on Cartesian charts.
Changes:
- Added a shared
pushforward(AbstractLinearTransform, ...)path for tangent-geometry CDicts that appliesM·vwithout requiringaton Cartesian charts, and uses chart Jacobians (requiringat) for non-Cartesian charts. - Added/updated unit tests asserting that constant linear transforms act on Cartesian velocities/accelerations without
at, and that non-Cartesian charts still requireat. - Updated a previously “requires at” test to reflect the new, intended behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/coordinax/transforms/_src/actions/linear.py |
Adds shared tangent-geometry pushforward for constant linear transforms, enabling act(..., coord_vel/coord_acc) without at on Cartesian charts. |
tests/unit/transforms/test_spatial_linear_transforms.py |
Adds new tests covering velocity/acceleration actions for Scale/Reflect/Shear without at, plus a “fast-path equals generic-with-at” keystone check. |
tests/unit/transforms/test_prolongation.py |
Updates the static-scale velocity behavior test and adds a non-Cartesian “still requires at” assertion. |
0d2f2c9 to
633fae3
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #584 +/- ##
==========================================
+ Coverage 94.75% 95.02% +0.26%
==========================================
Files 248 242 -6
Lines 7958 7887 -71
==========================================
- Hits 7541 7495 -46
+ Misses 417 392 -25 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/coordinax/transforms/_src/actions/linear.py:380
- The doctest example in this docstring mixes acceleration units (m/s2) into a
coord_velcall. That’s semantically inconsistent and can mislead readers (and any doctest-based docs checks). Make the example purely velocity (all m/s) or switch the representation tocoord_accand use all m/s2.
>>> v = {"q.x": u.Q(1.0, "m/s"), "q.y": u.Q(1.0, "m/s"), "q.z": u.Q(1.0, "m/s"),
... "p.x": u.Q(1.0, "m/s2"), "p.y": u.Q(1.0, "m/s2"), "p.z": u.Q(1.0, "m/s2")}
>>> out = cxfm.act(op, None, v, ps, cxr.tangent_geom, cxr.coord_vel)
>>> [out[k].value.round(3) for k in ("q.x", "q.y", "q.z", "p.x", "p.y", "p.z")]
…base point Only Boost and Rotate had TangentGeometry (velocity/acceleration) act paths; the other linear transforms fell through to the generic jet prolongation, which requires a base point `at` even for a constant Cartesian map where none is needed (`act(Scale, None, velocity, cart3d, coord_vel)` raised TypeError). A linear map's Jacobian is its (constant) matrix, so a Cartesian velocity or acceleration transforms as `v -> M v` with no anchor. Register a shared `pushforward` on `AbstractLinearTransform`: Cartesian charts apply `M` directly (no `at`); non-Cartesian charts push through the chart Jacobian (which does need `at`); time-dependent linear maps still route through the generic prolongation for the `dot(M)` term. Rotate keeps its own (more specific) dispatch, so there is no ambiguity. A clear TypeError now reports tangent/anchor components that do not match the chart. Verified: fast path equals the generic prolongation when `at` is supplied (the suite's keystone property); Scale/Reflect/Shear velocity + acceleration; non-Cartesian still requires `at`. New unit tests + doctest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…uct charts The new AbstractLinearTransform `pushforward` matched every chart, so on a Cartesian-product chart it applied `op._matrix(chart.cartesian, tau)` against the full product dimension and errored (e.g. a 3x3 Scale on a 6D phase-space cart3d x cart3d velocity). Add a product-chart dispatch mirroring the point action: split per factor, apply the matrix only to factors whose Cartesian dimension matches the operator, pass the rest through. Tests: factorwise velocity on cart3d x cart3d; non-Cartesian (spherical) pushforward round-trip via the `at` path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The keystone test compared `act(at=None)` vs `act(at=at)`, but for a static linear map both go through the identical pushforward path, so it validated nothing. Compare the no-`at` fast path to `cxfm.prolong` (generic autodiff) with `at` as jet slot 0, per Copilot review feedback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The single test mixed m/s (q.*) with m/s2 (p.*) under coord_vel, which is semantically ambiguous and left the order-2 coord_acc path unexercised. Split into a pure-velocity test (all m/s, coord_vel) and a pure-acceleration test (all m/s2, coord_acc), per Copilot review feedback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…orward Lines 295/313 used `chart is cart` while the same file uses `chart != cart` at 98/128 for the identical "is this already its Cartesian chart?" test. The `is` form works only because `.cartesian` returns `self`; `==` is consistent with the rest of the file and robust to any chart whose `.cartesian` returns an equal-but-distinct instance. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…doctest The doctest mixed m/s (q.*) with m/s2 (p.*) under coord_vel, contradicting the now-split velocity/acceleration tests. Use all-m/s velocity data so the example is unit-consistent with its coord_vel semantics, per Copilot review feedback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two patterns were copied verbatim across the linear/prolong actions: - the "components do not match ...; missing ...; unexpected ..." TypeError appeared 3x (linear pushforward, prolong pushforward, prolong jet). Extract `require_matching_keys(actual, expected, message)` into actions/utils. - the pack-to-common-unit -> einsum -> cdict Cartesian matrix-apply appeared 3x in linear.py. Extract a local `_matmul_cdict(matrix, d, comps)` helper. Behavior and error messages are unchanged (the message-assertion tests still pass). Also drops a now-redundant cast and a stale `ty: ignore`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`_linear_pushforward_cdict` was a private helper with two callers: the public `pushforward(AbstractChart)` dispatch (a 1-line pass-through) and the factorwise `pushforward(AbstractCartesianProductChart)` path. Every other factorwise/ composed/add action recurses through the *public* `cxfmapi.act`/`pushforward`; the private indirection was the odd one out. Inline the body into the public `pushforward` dispatch and have the factorwise path re-dispatch through `cxfmapi.pushforward` per factor — mirroring the point-action factorwise's `cxfmapi.act` (and correctly recursing on nested product factors, which the private call could not). Net -22 lines, no behavior change (full transforms suite + doctests pass). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… charts Guards the re-dispatch in the product-chart `pushforward`: a factor that is itself a product chart routes back through the public `pushforward` and is handled recursively. A 3x3 Scale on a chart whose factors are a 6D `cart3d x cart3d`, a 3D `cart1d x cart1d x cart1d`, and a plain `Cart3D` passes the two nested factors through and scales only the Cart3D — matching the point action. The 3D nested factor is the keystone: a non-recursive direct-helper call would flatten its three 1D sub-factors into one 3-vector and scale them (2, 3, 4); recursion correctly leaves them (1, 1, 1). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- product-chart pushforward: validate v/at keys against chart.components up front (clear TypeError via require_matching_keys instead of a raw KeyError from split_components), mirroring the non-product overload - non-Cartesian 'at'-required error: reference the actual `rep` rather than a hard-coded "TangentGeometry" that the signature doesn't take - tests: strip units via `_to_np`/`u.ustrip` instead of reading `.value` directly in the product-chart factorwise/nested tests Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rozen-τ - Detect a Cartesian/flat chart with `is_flat_chart(chart)` (isinstance-based) instead of exact `chart == cart` equality, so a flat chart that isn't the exact canonical instance still takes the at-free fast path. - Reword the docstring: this overload is the frozen-τ rule; the act-level router (`prolong`, branching on `is_time_dependent`) sends time-dependent order≥1 acts to the generic prolongation (the `dot(M)` term), so they don't reach this frozen-τ path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4097abd to
ff5684e
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/coordinax/transforms/_src/actions/linear.py:377
- In the Cartesian-product
pushforwardoverload,_maybeunconditionally accessesfactor_chart.cartesianbefore checking whether the factor should be acted on. This can raiseNoGlobalCartesianChartErrorfor factors without a global Cartesian chart even when the factor’s dimension doesn’t match the operator matrix and should simply pass through unchanged (per the docstring). Consider first checkingfactor_chart.ndim/factor_chart.componentsagainstnbefore touching.cartesian, so dimension-mismatched factors can short-circuit safely.
cart = factor_chart.cartesian
if cart.ndim != n or len(cart.components) != n:
return part
return cast(
Summary
Only
BoostandRotatehadTangentGeometry(velocity/acceleration) act paths. The other linear transforms (Scale,Reflect,Shear) fell through to the generic jet prolongation, which requires a base pointat— even for a constant Cartesian map where none is mathematically needed. So:while the same call on
Rotateworks. Surfaced by the v0.24 pre-release audit.Fix
A linear map's Jacobian is its (constant) matrix, so a Cartesian velocity/acceleration transforms as
v → M·vwith no anchor. This registers a sharedpushforwardonAbstractLinearTransform:Mdirectly, noatneeded (matchesRotate).at), applyMin Cartesian, pull back.Ṁterm genuinely needs the jet anchors).Rotatekeeps its own, more-specific dispatch, so there is no plum ambiguity. The new path also reports mismatched tangent/anchor components with a clearTypeErrorinstead of a rawKeyError.Verification
atfast path equals the generic prolongation whenatis supplied.at.test_spatial_linear_transforms.py,test_prolongation.py) + a doctest; fulltests/unit/transformssuite passes (265).One pre-existing test (
test_static_scale_vel_requires_at) encoded the old "requires at" behavior and is inverted to assert the new, correct behavior.🤖 Generated with Claude Code