diff --git a/optika/__init__.py b/optika/__init__.py index 4c12535..734f83c 100644 --- a/optika/__init__.py +++ b/optika/__init__.py @@ -1,3 +1,4 @@ +from . import vectors from . import rays from . import sags from . import materials diff --git a/optika/vectors/__init__.py b/optika/vectors/__init__.py index 6137332..9057ed9 100644 --- a/optika/vectors/__init__.py +++ b/optika/vectors/__init__.py @@ -1,3 +1,4 @@ from ._vectors_field import * from ._vectors_pupil import * from ._vectors_scene import * +from ._vectors_object import * diff --git a/optika/vectors/_tests/test_vectors_object.py b/optika/vectors/_tests/test_vectors_object.py new file mode 100644 index 0000000..dd7033a --- /dev/null +++ b/optika/vectors/_tests/test_vectors_object.py @@ -0,0 +1,130 @@ +import pytest +import numpy as np +import astropy.units as u +import named_arrays as na +from named_arrays._vectors.cartesian.tests import test_vectors_cartesian +import optika + +_num_y = test_vectors_cartesian._num_y + + +vectors_object = [ + optika.vectors.ObjectVectorArray( + wavelength=534 * u.nm, + field=na.Cartesian2dVectorLinearSpace( + start=-5 * u.mm, + stop=5 * u.mm, + axis="y", + num=_num_y, + ).explicit, + ) +] + + +def _items() -> list[na.AbstractArray | dict[str, int | slice | na.AbstractArray]]: + return [ + dict(y=0), + dict(y=slice(0, 1)), + dict(y=na.ScalarArrayRange(0, 2, axis="y")), + ] + + +class AbstractTestAbstractObjectVectorArray( + test_vectors_cartesian.AbstractTestAbstractCartesianVectorArray +): + @pytest.mark.xfail(raises=NotImplementedError) + def test_matrix(self, array): + return super().test_matrix(array=array) + + @pytest.mark.parametrize( + argnames="item", + argvalues=_items(), + ) + def test__getitem__( + self, + array: optika.vectors.AbstractObjectVectorArray, + item: dict[str, int | slice | na.AbstractArray] | na.AbstractArray, + ): + super().test__getitem__(array=array, item=item) + + @pytest.mark.parametrize("array_2", vectors_object) + class TestUfuncBinary( + test_vectors_cartesian.AbstractTestAbstractCartesianVectorArray.TestUfuncBinary + ): + pass + + @pytest.mark.parametrize("array_2", vectors_object) + class TestMatmul( + test_vectors_cartesian.AbstractTestAbstractCartesianVectorArray.TestMatmul + ): + pass + + class TestArrayFunctions( + test_vectors_cartesian.AbstractTestAbstractCartesianVectorArray.TestArrayFunctions + ): + @pytest.mark.parametrize("array_2", vectors_object) + 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", vectors_object) +class TestObjectVectorArray( + AbstractTestAbstractObjectVectorArray, + test_vectors_cartesian.AbstractTestAbstractExplicitCartesianVectorArray, +): + @pytest.mark.parametrize( + argnames="item", + argvalues=[ + dict(y=0), + dict(y=slice(None)), + ], + ) + @pytest.mark.parametrize( + argnames="value", + argvalues=[ + optika.vectors.ObjectVectorArray( + wavelength=500 * u.nm, + field=0 * u.mm, + ), + ], + ) + def test__setitem__( + self, + array: optika.vectors.AbstractObjectVectorArray, + item: dict[str, int | slice | na.ScalarArray] | na.ScalarArray, + value: optika.vectors.AbstractObjectVectorArray, + ): + super().test__setitem__(array=array, item=item, value=value) diff --git a/optika/vectors/_vectors_object.py b/optika/vectors/_vectors_object.py new file mode 100644 index 0000000..82cbfe3 --- /dev/null +++ b/optika/vectors/_vectors_object.py @@ -0,0 +1,56 @@ +from __future__ import annotations +from typing import TypeVar +from typing_extensions import Self +import dataclasses +import named_arrays as na +from . import AbstractPupilVectorArray, PupilVectorArray +from . import AbstractSceneVectorArray, SceneVectorArray + + +WavelengthT = TypeVar("WavelengthT", bound=na.ScalarLike) +FieldT = TypeVar("FieldT", bound=na.AbstractCartesian2dVectorArray) +PupilT = TypeVar("PupilT", bound=na.AbstractCartesian2dVectorArray) + + +@dataclasses.dataclass(eq=False, repr=False) +class AbstractObjectVectorArray( + AbstractPupilVectorArray, + AbstractSceneVectorArray, +): + @property + def type_abstract(self) -> type[AbstractObjectVectorArray]: + return AbstractObjectVectorArray + + @property + def type_explicit(self) -> type[ObjectVectorArray]: + return ObjectVectorArray + + @property + def type_matrix(self) -> type[na.AbstractMatrixArray]: + raise NotImplementedError + + +@dataclasses.dataclass(eq=False, repr=False) +class ObjectVectorArray( + AbstractObjectVectorArray, + PupilVectorArray, + SceneVectorArray, +): + @classmethod + def from_scalar( + cls, + scalar: na.AbstractScalar, + like: None | Self = None, + ) -> Self: + if like is not None: + return type(like)( + wavelength=scalar, + field=scalar, + pupil=scalar, + ) + else: + return cls( + wavelength=scalar, + field=scalar, + pupil=scalar, + )