Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/coordinax/_src/base/charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,22 @@ def __init_subclass__(cls, **kw: Any) -> None:
cls._coord_dimensions = _get_tuple(args[2])
break

# Check the component count matches the declared dimension flag,
# but only when the chart mixes in an `AbstractDimensionalFlag` with
# a fixed integer `n` (skip the variable-`n` flag, e.g. `CartND`
# whose `_chart_ndim` is `"N"`, and charts with no flag at all).
ndim = getattr(cls, "_chart_ndim", None)
if (
isinstance(ndim, int)
and hasattr(cls, "_components")
and len(cls._components) != ndim
):
msg = (
f"{cls.__name__} is declared {ndim}D but has "
f"{len(cls._components)} components {cls._components}"
)
raise TypeError(msg)

super().__init_subclass__(**kw) # AbstractChart has.

@property
Expand All @@ -389,9 +405,15 @@ class AbstractDimensionalFlag:

"""

#: Declared coordinate dimension of the flag (set when ``n`` is given).
_chart_ndim: ClassVar[int | L["N"]]

def __init_subclass__(cls, n: int | L["N"] | None = None, **kw: Any) -> None:
if n is not None:
DIMENSIONAL_FLAGS[n] = cls
# Record the declared dimension so concrete fixed-component charts
# can validate their component count against it.
cls._chart_ndim = n

# Enforce that this is a subclass of AbstractChart unless it's an
# abstract base class (name starts with "Abstract")
Expand Down
2 changes: 0 additions & 2 deletions src/coordinax/_src/charts/d0.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ class Abstract0D(AbstractDimensionalFlag, n=0):
A 0D representation has no coordinate component.
"""

# TODO: add a check it's 0D

@override
def __init_subclass__(cls, n: int | L["N"] | None = None, **kw: Any) -> None:
# Enforce that this is a subclass of AbstractChart
Expand Down
2 changes: 0 additions & 2 deletions src/coordinax/_src/charts/d1.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ class Abstract1D(AbstractDimensionalFlag, n=1):
Cartesian $(x)$ or radial $(r)$ coordinates.
"""

# TODO: add a check it's 1D

@override
def __init_subclass__(cls, n: int | L["N"] | None = None, **kw: Any) -> None:
# Enforce that this is a subclass of AbstractChart
Expand Down
2 changes: 0 additions & 2 deletions src/coordinax/_src/charts/d2.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ class Abstract2D(AbstractDimensionalFlag, n=2):
two angular coordinates but represents a curved surface.
"""

# TODO: add a check it's 2D

@override
def __init_subclass__(cls, n: int | L["N"] | None = None, **kw: Any) -> None:
# Enforce that this is a subclass of AbstractChart
Expand Down
2 changes: 0 additions & 2 deletions src/coordinax/_src/charts/d3.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ class Abstract3D(AbstractDimensionalFlag, n=3):
(cylindrical, spherical), or other three-dimensional manifolds.
"""

# TODO: add a check it's 3D

@override
def __init_subclass__(cls, n: int | L["N"] | None = None, **kw: Any) -> None:
# Enforce that this is a subclass of AbstractChart
Expand Down
2 changes: 0 additions & 2 deletions src/coordinax/_src/charts/d4.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ class Abstract4D(AbstractDimensionalFlag, n=4):
example is the Minkowski spacetime chart ``(ct, x, y, z)``.
"""

# TODO: add a check it's 4D

@override
def __init_subclass__(cls, n: int | L["N"] | None = None, **kw: Any) -> None:
# Enforce that this is a subclass of AbstractChart
Expand Down
2 changes: 0 additions & 2 deletions src/coordinax/_src/charts/d6.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ class Abstract6D(AbstractDimensionalFlag, n=6):
Examples include Cartesian representations in arbitrary dimensions.
"""

# TODO: add a check it's 6D

@override
def __init_subclass__(cls, n: int | L["N"] | None = None, **kw: Any) -> None:
# Enforce that this is a subclass of AbstractChart
Expand Down
1 change: 0 additions & 1 deletion src/coordinax/vectors/_src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Vectors."""

from .api import *
from .base import *
from .bundle import *
from .constants import *
Expand Down
13 changes: 0 additions & 13 deletions src/coordinax/vectors/_src/api.py

This file was deleted.

47 changes: 47 additions & 0 deletions tests/unit/charts/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,50 @@ def test_flags_must_subclass_chart(self) -> None:
for _flag_cls in cxc.DIMENSIONAL_FLAGS.values():
# All registered flags should have chart subclasses
pass # Registration enforces this

def test_component_count_must_match_declared_dimension(self) -> None:
"""A concrete chart's component count must match its dimension flag."""
import dataclasses

from typing import Literal

import jax.tree_util as jtu

from coordinax._src.base import (
MT,
AbstractFixedComponentsChart,
chart_dataclass_decorator,
)
from coordinax._src.charts.d3 import Abstract3D, Cart3D
from coordinax._src.custom_types import Len
from coordinax._src.euclidean.manifold import R3

two_keys = tuple[Literal["a"], Literal["b"]]
two_dims = tuple[Len, Len]

# A 2-component chart declared 3D (via Abstract3D) must be rejected.
with pytest.raises(TypeError, match="declared 3D but has 2 components"):

@jtu.register_static
@chart_dataclass_decorator
class _Bad3D(
AbstractFixedComponentsChart[MT, two_keys, two_dims], Abstract3D
):
_: dataclasses.KW_ONLY
M: MT = R3

@property
def cartesian(self):
return Cart3D(M=self.M)

def test_component_count_matches_for_predefined_charts(self) -> None:
"""Every predefined chart's ndim matches its component count."""
charts = [
obj
for name in dir(cxc)
if not name.startswith("_")
and isinstance(obj := getattr(cxc, name), cxc.AbstractChart)
]
assert charts, "no predefined chart instances discovered"
for chart in charts:
assert chart.ndim == len(chart.components), chart