diff --git a/named_arrays/__init__.py b/named_arrays/__init__.py index 5ab09664..8cf36524 100644 --- a/named_arrays/__init__.py +++ b/named_arrays/__init__.py @@ -13,6 +13,7 @@ from ._vectors.cartesian.vectors_cartesian_nd import * from ._vectors.vectors_spectral import * from ._vectors.vectors_positional import * +from ._vectors.vectors_directional import * from ._vectors.vectors_spectral_positional import * from ._matrices.matrices import * from ._matrices.cartesian.matrices_cartesian import * @@ -21,5 +22,6 @@ from ._matrices.cartesian.matrices_cartesian_nd import * from ._matrices.matrices_spectral import * from ._matrices.matrices_positional import * +from ._matrices.matrices_directional import * from ._matrices.matrices_spectral_positional import * from ._functions.functions import * diff --git a/named_arrays/_matrices/matrices_directional.py b/named_arrays/_matrices/matrices_directional.py new file mode 100644 index 00000000..996450fa --- /dev/null +++ b/named_arrays/_matrices/matrices_directional.py @@ -0,0 +1,51 @@ +from __future__ import annotations +from typing import TypeVar, Generic, Type +import abc +import dataclasses +import named_arrays as na + +__all__ = [ + 'AbstractDirectionalMatrixArray', + 'DirectionalMatrixArray', +] + +DirectionT = TypeVar('DirectionT', bound=na.AbstractVectorArray) + + +@dataclasses.dataclass(eq=False, repr=False) +class AbstractDirectionalMatrixArray( + na.AbstractCartesianMatrixArray, + na.AbstractDirectionalVectorArray, +): + @property + @abc.abstractmethod + def direction(self) -> na.AbstractVectorArray: + """ + The `direction` component of the matrix. + """ + + @property + def type_abstract(self) -> Type[AbstractDirectionalMatrixArray]: + return AbstractDirectionalMatrixArray + + @property + def type_explicit(self) -> Type[DirectionalMatrixArray]: + return DirectionalMatrixArray + + @property + def type_vector(self) -> Type[na.DirectionalVectorArray]: + return na.DirectionalVectorArray + + @property + def determinant(self) -> na.ScalarLike: + raise NotImplementedError + + +@dataclasses.dataclass(eq=False, repr=False) +class DirectionalMatrixArray( + na.DirectionalVectorArray, + AbstractDirectionalMatrixArray, + na.AbstractExplicitMatrixArray, + Generic[DirectionT], +): + pass diff --git a/named_arrays/_vectors/tests/test_vectors_directional.py b/named_arrays/_vectors/tests/test_vectors_directional.py new file mode 100644 index 00000000..430c38b2 --- /dev/null +++ b/named_arrays/_vectors/tests/test_vectors_directional.py @@ -0,0 +1,170 @@ +import pytest +import numpy as np +import astropy.units as u +import named_arrays as na +from . import test_vectors +from ..cartesian.tests import test_vectors_cartesian + +_num_x = test_vectors._num_x +_num_y = test_vectors._num_y +_num_z = test_vectors._num_z +_num_distribution = test_vectors._num_distribution + + +def _directional_arrays() -> list[na.DirectionalVectorArray]: + return [ + na.DirectionalVectorArray(direction=na.Cartesian2dVectorArray(1, 2) * u.deg), + na.DirectionalVectorArray(direction=na.Cartesian2dVectorLinearSpace(1, 2, axis="y", num=_num_y).explicit * u.deg), + ] + + +def _directional_arrays_2() -> list[na.DirectionalVectorArray]: + return [ + na.DirectionalVectorArray(direction=na.Cartesian2dVectorArray(3, 4) * u.deg), + na.DirectionalVectorArray(direction=na.Cartesian2dVectorArray( + x=na.NormalUncertainScalarArray(3, width=1) * u.deg, + y=na.NormalUncertainScalarArray(4, width=1) * u.deg, + )) + ] + + +def _directional_items() -> list[na.DirectionalVectorArray | dict[str, int | slice | na.DirectionalVectorArray]]: + return [ + dict(y=0), + dict(y=slice(0, 1)), + dict(y=na.ScalarArrayRange(0, 2, axis='y')), + ] + + +class AbstractTestAbstractDirectionalVectorArray( + test_vectors_cartesian.AbstractTestAbstractCartesianVectorArray, +): + def test_direction(self, array: na.AbstractDirectionalVectorArray): + assert isinstance(na.as_named_array(array.direction), (na.AbstractScalar, na.AbstractVectorArray)) + + @pytest.mark.parametrize( + argnames='item', + argvalues=_directional_items(), + ) + def test__getitem__( + self, + array: na.AbstractDirectionalVectorArray, + item: dict[str, int | slice | na.AbstractArray] | na.AbstractArray + ): + super().test__getitem__(array=array, item=item) + + @pytest.mark.parametrize('array_2', _directional_arrays_2()) + class TestUfuncBinary( + test_vectors_cartesian.AbstractTestAbstractCartesianVectorArray.TestUfuncBinary + ): + pass + + @pytest.mark.parametrize('array_2', _directional_arrays_2()) + class TestMatmul( + test_vectors_cartesian.AbstractTestAbstractCartesianVectorArray.TestMatmul + ): + pass + + class TestArrayFunctions( + test_vectors_cartesian.AbstractTestAbstractCartesianVectorArray.TestArrayFunctions + ): + + @pytest.mark.parametrize("array_2", _directional_arrays_2()) + class TestAsArrayLikeFunctions( + test_vectors_cartesian.AbstractTestAbstractCartesianVectorArray.TestArrayFunctions.TestAsArrayLikeFunctions + ): + pass + + @pytest.mark.parametrize( + argnames='where', + argvalues=[ + np._NoValue, + ] + ) + class TestReductionFunctions( + test_vectors_cartesian.AbstractTestAbstractCartesianVectorArray.TestArrayFunctions.TestReductionFunctions, + ): + pass + + @pytest.mark.parametrize( + argnames='q', + argvalues=[ + 25 * u.percent, + ] + ) + class TestPercentileLikeFunctions( + test_vectors_cartesian.AbstractTestAbstractCartesianVectorArray.TestArrayFunctions + .TestPercentileLikeFunctions, + ): + pass + + class TestNamedArrayFunctions( + test_vectors_cartesian.AbstractTestAbstractCartesianVectorArray.TestNamedArrayFunctions, + ): + @pytest.mark.skip + class TestPltPlotLikeFunctions( + test_vectors_cartesian.AbstractTestAbstractCartesianVectorArray.TestNamedArrayFunctions.TestPltPlotLikeFunctions, + ): + pass + + +@pytest.mark.parametrize("array", _directional_arrays()) +class TestDirectionalVectorArray( + AbstractTestAbstractDirectionalVectorArray, + test_vectors_cartesian.AbstractTestAbstractExplicitCartesianVectorArray, +): + @pytest.mark.parametrize( + argnames="item", + argvalues=[ + dict(y=0), + dict(y=slice(None)), + ], + ) + @pytest.mark.parametrize( + argnames="value", + argvalues=[ + 700 * u.nm, + ] + ) + def test__setitem__( + self, + array: na.ScalarArray, + item: dict[str, int | slice | na.ScalarArray] | na.ScalarArray, + value: float | na.ScalarArray + ): + super().test__setitem__(array=array, item=item, value=value) + + +class AbstractTestAbstractImplicitDirectionalVectorArray( + AbstractTestAbstractDirectionalVectorArray, + test_vectors_cartesian.AbstractTestAbstractImplicitCartesianVectorArray, +): + pass + + +class AbstractTestAbstractParameterizedDirectionalVectorArray( + AbstractTestAbstractImplicitDirectionalVectorArray, + test_vectors_cartesian.AbstractTestAbstractParameterizedCartesianVectorArray, +): + pass + + +class AbstractTestAbstractDirectionalVectorSpace( + AbstractTestAbstractParameterizedDirectionalVectorArray, + test_vectors_cartesian.AbstractTestAbstractParameterizedCartesianVectorArray, +): + pass + + +def _directional_linear_spaces() -> list[na.DirectionalVectorLinearSpace]: + return [ + na.DirectionalVectorLinearSpace(1 * u.m, 2 * u.m, axis="y", num=_num_y) + ] + + +@pytest.mark.parametrize("array", _directional_linear_spaces()) +class TestDirectionalVectorlinearSpace( + AbstractTestAbstractDirectionalVectorSpace, + test_vectors_cartesian.AbstractTestAbstractCartesianVectorLinearSpace, +): + pass diff --git a/named_arrays/_vectors/vectors_directional.py b/named_arrays/_vectors/vectors_directional.py new file mode 100644 index 00000000..de9ae065 --- /dev/null +++ b/named_arrays/_vectors/vectors_directional.py @@ -0,0 +1,97 @@ +from __future__ import annotations +from typing import Type, Generic, TypeVar +from typing_extensions import Self +import abc +import dataclasses +import named_arrays as na + +__all__ = [ + "AbstractDirectionalVectorArray", + "DirectionalVectorArray", + "AbstractImplicitDirectionalVectorArray", + "AbstractParameterizedDirectionalVectorArray", + "AbstractDirectionalVectorSpace", + "DirectionalVectorLinearSpace", +] + +DirectionT = TypeVar("DirectionT", bound=na.ArrayLike) + + +@dataclasses.dataclass(eq=False, repr=False) +class AbstractDirectionalVectorArray( + na.AbstractCartesianVectorArray, +): + + @property + @abc.abstractmethod + def direction(self) -> na.ArrayLike: + """ + The `direction` component of the vector. + """ + + @property + def type_abstract(self) -> Type[na.AbstractArray]: + return AbstractDirectionalVectorArray + + @property + def type_explicit(self) -> Type[na.AbstractExplicitArray]: + return DirectionalVectorArray + + @property + def type_matrix(self) -> Type[na.DirectionalMatrixArray]: + return na.DirectionalMatrixArray + + +@dataclasses.dataclass(eq=False, repr=False) +class DirectionalVectorArray( + AbstractDirectionalVectorArray, + na.AbstractExplicitCartesianVectorArray, + Generic[DirectionT], +): + direction: DirectionT = 0 + + @classmethod + def from_scalar( + cls: Type[Self], + scalar: na.AbstractScalar, + like: None | na.AbstractExplicitVectorArray = None, + ) -> DirectionalVectorArray: + result = super().from_scalar(scalar, like=like) + if result is not NotImplemented: + return result + return cls(direction=scalar) + + +@dataclasses.dataclass(eq=False, repr=False) +class AbstractImplicitDirectionalVectorArray( + AbstractDirectionalVectorArray, + na.AbstractImplicitCartesianVectorArray, +): + + @property + def direction(self) -> na.ArrayLike: + return self.explicit.direction + + +@dataclasses.dataclass(eq=False, repr=False) +class AbstractParameterizedDirectionalVectorArray( + AbstractImplicitDirectionalVectorArray, + na.AbstractParameterizedVectorArray, +): + pass + + +@dataclasses.dataclass(eq=False, repr=False) +class AbstractDirectionalVectorSpace( + AbstractParameterizedDirectionalVectorArray, + na.AbstractVectorSpace, +): + pass + + +@dataclasses.dataclass(eq=False, repr=False) +class DirectionalVectorLinearSpace( + AbstractDirectionalVectorSpace, + na.AbstractVectorLinearSpace, +): + pass