Skip to content

Commit c9ef62d

Browse files
nstarmanclaude
andcommitted
🩹 fix(v0.24): validate chart component count; drop dead normalize_vector
Two small robustness fixes surfaced by the v0.24 pre-release audit: * charts: concrete `AbstractFixedComponentsChart` subclasses carrying an `AbstractDimensionalFlag` (e.g. `Abstract3D`, n=3) now assert their component count matches the declared dimension at class-creation time, replacing the six `# TODO: add a check it's ND` placeholders in d0-d6 with one central check (the flag records `_chart_ndim`; `__init_subclass__` compares it to the component tuple and raises a clear TypeError). Abstract / variable-n charts are unaffected. * vectors: remove the orphaned `normalize_vector` stub — a `@plum.dispatch.abstract` with no concrete registration, no public export, no callers and no tests. (The audit's third item — a 1D@2D QMatrix matmul path — is dropped: #571 removed the whole `quantity_matrix` module in favor of `unxts.linalg`, so it no longer belongs in coordinax.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b6aa5dd commit c9ef62d

10 files changed

Lines changed: 60 additions & 26 deletions

File tree

‎src/coordinax/_src/base/charts.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,20 @@ def __init_subclass__(cls, **kw: Any) -> None:
364364
cls._coord_dimensions = _get_tuple(args[2])
365365
break
366366

367+
# Check the component count matches the declared dimension flag (if
368+
# this chart mixes in an `AbstractDimensionalFlag` with a fixed `n`).
369+
ndim = getattr(cls, "_chart_ndim", None)
370+
if (
371+
isinstance(ndim, int)
372+
and hasattr(cls, "_components")
373+
and len(cls._components) != ndim
374+
):
375+
msg = (
376+
f"{cls.__name__} is declared {ndim}D but has "
377+
f"{len(cls._components)} components {cls._components}"
378+
)
379+
raise TypeError(msg)
380+
367381
super().__init_subclass__(**kw) # AbstractChart has.
368382

369383
@property
@@ -389,9 +403,15 @@ class AbstractDimensionalFlag:
389403
390404
"""
391405

406+
#: Declared coordinate dimension of the flag (set when ``n`` is given).
407+
_chart_ndim: ClassVar[int | L["N"]]
408+
392409
def __init_subclass__(cls, n: int | L["N"] | None = None, **kw: Any) -> None:
393410
if n is not None:
394411
DIMENSIONAL_FLAGS[n] = cls
412+
# Record the declared dimension so concrete fixed-component charts
413+
# can validate their component count against it.
414+
cls._chart_ndim = n
395415

396416
# Enforce that this is a subclass of AbstractChart unless it's an
397417
# abstract base class (name starts with "Abstract")

‎src/coordinax/_src/charts/d0.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ class Abstract0D(AbstractDimensionalFlag, n=0):
3131
A 0D representation has no coordinate component.
3232
"""
3333

34-
# TODO: add a check it's 0D
35-
3634
@override
3735
def __init_subclass__(cls, n: int | L["N"] | None = None, **kw: Any) -> None:
3836
# Enforce that this is a subclass of AbstractChart

‎src/coordinax/_src/charts/d1.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ class Abstract1D(AbstractDimensionalFlag, n=1):
4949
Cartesian $(x)$ or radial $(r)$ coordinates.
5050
"""
5151

52-
# TODO: add a check it's 1D
53-
5452
@override
5553
def __init_subclass__(cls, n: int | L["N"] | None = None, **kw: Any) -> None:
5654
# Enforce that this is a subclass of AbstractChart

‎src/coordinax/_src/charts/d2.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ class Abstract2D(AbstractDimensionalFlag, n=2):
3535
two angular coordinates but represents a curved surface.
3636
"""
3737

38-
# TODO: add a check it's 2D
39-
4038
@override
4139
def __init_subclass__(cls, n: int | L["N"] | None = None, **kw: Any) -> None:
4240
# Enforce that this is a subclass of AbstractChart

‎src/coordinax/_src/charts/d3.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ class Abstract3D(AbstractDimensionalFlag, n=3):
5454
(cylindrical, spherical), or other three-dimensional manifolds.
5555
"""
5656

57-
# TODO: add a check it's 3D
58-
5957
@override
6058
def __init_subclass__(cls, n: int | L["N"] | None = None, **kw: Any) -> None:
6159
# Enforce that this is a subclass of AbstractChart

‎src/coordinax/_src/charts/d4.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ class Abstract4D(AbstractDimensionalFlag, n=4):
1717
example is the Minkowski spacetime chart ``(ct, x, y, z)``.
1818
"""
1919

20-
# TODO: add a check it's 4D
21-
2220
@override
2321
def __init_subclass__(cls, n: int | L["N"] | None = None, **kw: Any) -> None:
2422
# Enforce that this is a subclass of AbstractChart

‎src/coordinax/_src/charts/d6.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ class Abstract6D(AbstractDimensionalFlag, n=6):
2828
Examples include Cartesian representations in arbitrary dimensions.
2929
"""
3030

31-
# TODO: add a check it's 6D
32-
3331
@override
3432
def __init_subclass__(cls, n: int | L["N"] | None = None, **kw: Any) -> None:
3533
# Enforce that this is a subclass of AbstractChart

‎src/coordinax/vectors/_src/__init__.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Vectors."""
22

3-
from .api import *
43
from .base import *
54
from .bundle import *
65
from .constants import *

‎src/coordinax/vectors/_src/api.py‎

Lines changed: 0 additions & 13 deletions
This file was deleted.

‎tests/unit/charts/test_base.py‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,43 @@ def test_flags_must_subclass_chart(self) -> None:
181181
for _flag_cls in cxc.DIMENSIONAL_FLAGS.values():
182182
# All registered flags should have chart subclasses
183183
pass # Registration enforces this
184+
185+
def test_component_count_must_match_declared_dimension(self) -> None:
186+
"""A concrete chart's component count must match its dimension flag."""
187+
import dataclasses
188+
189+
from typing import Literal
190+
191+
import jax.tree_util as jtu
192+
193+
from coordinax._src.base import (
194+
MT,
195+
AbstractFixedComponentsChart,
196+
chart_dataclass_decorator,
197+
)
198+
from coordinax._src.charts.d3 import Abstract3D, Cart3D
199+
from coordinax._src.custom_types import Len
200+
from coordinax._src.euclidean.manifold import R3
201+
202+
two_keys = tuple[Literal["a"], Literal["b"]]
203+
two_dims = tuple[Len, Len]
204+
205+
# A 2-component chart declared 3D (via Abstract3D) must be rejected.
206+
with pytest.raises(TypeError, match="declared 3D but has 2 components"):
207+
208+
@jtu.register_static
209+
@chart_dataclass_decorator
210+
class _Bad3D(
211+
AbstractFixedComponentsChart[MT, two_keys, two_dims], Abstract3D
212+
):
213+
_: dataclasses.KW_ONLY
214+
M: MT = R3
215+
216+
@property
217+
def cartesian(self):
218+
return Cart3D(M=self.M)
219+
220+
def test_component_count_matches_for_predefined_charts(self) -> None:
221+
"""Every predefined chart's ndim matches its component count."""
222+
assert cxc.cart3d.ndim == len(cxc.cart3d.components) == 3
223+
assert cxc.polar2d.ndim == len(cxc.polar2d.components) == 2

0 commit comments

Comments
 (0)