Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
byrdie committed Sep 9, 2023
2 parents 50658de + 84bc683 commit dec4552
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 42 deletions.
27 changes: 0 additions & 27 deletions named_arrays/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
'named_array_like',
'get_dtype',
'value',
'unit',
'unit_normalized',
'type_array',
'broadcast_shapes',
'shape_broadcasted',
Expand Down Expand Up @@ -148,31 +146,6 @@ def value(a: float | u.Quantity | AbstractArray):
else:
return a

def unit(
value: float | complex | np.ndarray | u.UnitBase | u.Quantity | AbstractArray
) -> None | u.UnitBase:
if isinstance(value, u.UnitBase):
return value
elif isinstance(value, u.Quantity):
return value.unit
elif isinstance(value, AbstractArray):
if isinstance(value, na.AbstractScalar):
return value.unit
else:
raise ValueError("non-scalar instances of `na.AbstractArray` may not be represented by a single unit")
else:
return None


def unit_normalized(
value: float | complex | np.ndarray | u.UnitBase | u.Quantity | AbstractArray
) -> u.UnitBase | dict[str, u.UnitBase]:
result = unit(value)
if result is None:
return u.dimensionless_unscaled
else:
return result


def type_array(
*values: bool | int | float | complex | str | np.ndarray | u.Quantity | AbstractArray,
Expand Down
18 changes: 18 additions & 0 deletions named_arrays/_functions/function_named_array_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
]

ASARRAY_LIKE_FUNCTIONS = named_arrays._scalars.scalar_named_array_functions.ASARRAY_LIKE_FUNCTIONS
HANDLED_FUNCTIONS = dict()

def _implements(function: Callable):
"""Register a __named_array_function__ implementation for AbstractScalarArray objects."""
def decorator(func):
HANDLED_FUNCTIONS[function] = func
return func
return decorator


def asarray_like(
Expand Down Expand Up @@ -64,3 +72,13 @@ def asarray_like(
like=like_outputs,
),
)


@_implements(na.unit)
def unit(a: na.AbstractFunctionArray) -> None | u.UnitBase | na.AbstractArray:
return na.unit(a.outputs)


@_implements(na.unit_normalized)
def unit_normalized(a: na.AbstractFunctionArray) -> u.UnitBase | na.AbstractArray:
return na.unit_normalized(a.outputs)
3 changes: 3 additions & 0 deletions named_arrays/_functions/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ def __named_array_function__(self, func, *args, **kwargs):
if func in function_named_array_functions.ASARRAY_LIKE_FUNCTIONS:
return function_named_array_functions.asarray_like(func=func, *args, **kwargs)

if func in function_named_array_functions.HANDLED_FUNCTIONS:
return function_named_array_functions.HANDLED_FUNCTIONS[func](*args, **kwargs)

return NotImplemented

def pcolormesh(
Expand Down
61 changes: 61 additions & 0 deletions named_arrays/_named_array_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
'geomspace',
'ndim',
'shape',
'unit',
'unit_normalized',
'broadcast_to',
'stack',
'concatenate',
Expand Down Expand Up @@ -439,6 +441,65 @@ def shape(a: na.ArrayLike) -> dict[str, int]:
return np.shape(a)


def unit(a: Any) -> None | u.UnitBase | na.AbstractArray:
"""
Isolate the physical units associated with the given object.
If the array has no physical units, this function returns :obj:`None`.
Parameters
----------
a
object to isolate the units of
See Also
--------
:func:`unit_normalized` : version of this function that returns :obj:`astropy.units.dimensionless_unscaled`
instead of :obj:`None` if there is no unit associated with the given object
"""
if isinstance(a, u.UnitBase):
return a
elif isinstance(a, u.Quantity):
return a.unit
elif isinstance(a, na.AbstractArray):
return na._named_array_function(
func=unit,
a=a,
)
else:
return None


def unit_normalized(a: Any) -> u.UnitBase | na.AbstractArray:
"""
Isolate the physical units associated with a given object, normalizing to dimensionless units
if the object does not have associated units.
Parameters
----------
a
object to isolate the units of
See Also
--------
:func:`unit` : version of this function that returns :obj:`None` instead of
:obj:`astropy.units.dimensionless_unscaled` if there is no unit associated with the given object.
"""
if isinstance(a, u.UnitBase):
return a
elif isinstance(a, u.Quantity):
return a.unit
elif isinstance(a, na.AbstractArray):
return na._named_array_function(
func=unit_normalized,
a=a,
)
else:
return u.dimensionless_unscaled


@overload
def broadcast_to(array: float | complex | np.ndarray | u.Quantity, shape: dict[str, int]) -> na.ScalarArray:
...
Expand Down
13 changes: 13 additions & 0 deletions named_arrays/_scalars/scalar_named_array_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,19 @@ def arange(
)


@_implements(na.unit)
def unit(a: na.AbstractScalarArray) -> None | u.UnitBase:
return na.unit(a.ndarray)


@_implements(na.unit_normalized)
def unit_normalized(a: na.AbstractScalarArray) -> u.UnitBase:
result = na.unit(a)
if result is None:
result = u.dimensionless_unscaled
return result


def random(
func: Callable,
*args: float | u.Quantity | na.AbstractScalarArray,
Expand Down
14 changes: 3 additions & 11 deletions named_arrays/_scalars/scalars.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,17 @@ def dtype(self: Self) -> np.dtype:
"""

@property
@abc.abstractmethod
def unit(self: Self) -> None | u.UnitBase | dict[str, None | u.UnitBase]:
def unit(self: Self) -> None | u.UnitBase:
"""
Unit associated with the array.
If :attr:`ndarray` is an instance of :class:`astropy.units.Quantity`, return :attr:`astropy.units.Quantity.unit`,
otherwise return :class:`None`.
"""
return na.unit(self)

@property
def unit_normalized(self: Self) -> u.UnitBase | dict[str, u.UnitBase]:
def unit_normalized(self: Self) -> u.UnitBase:
"""
Similar to :attr:`unit` but returns :attr:`astropy.units.dimensionless_unscaled` if :attr:`ndarray` is not an
instance of :class:`astropy.units.Quantity`.
Expand Down Expand Up @@ -865,10 +865,6 @@ def size(self: Self) -> int:
def dtype(self: Self) -> np.dtype:
return na.get_dtype(self.ndarray)

@property
def unit(self: Self) -> None | u.UnitBase:
return na.unit(self.ndarray)

@property
def explicit(self: Self) -> Self:
return self
Expand Down Expand Up @@ -973,10 +969,6 @@ def ndarray(self: Self) -> na.QuantityLike:
def dtype(self: Self) -> np.dtype:
return self.explicit.dtype

@property
def unit(self: Self) -> None | u.Unit:
return self.explicit.unit

def _attr_normalized(self, name: str) -> ScalarArray:

attr = getattr(self, name)
Expand Down
4 changes: 0 additions & 4 deletions named_arrays/_scalars/uncertainties/uncertainties.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,6 @@ def value(self) -> UncertainScalarArray:
distribution=na.value(self.distribution),
)

@property
def unit(self: Self) -> None | u.Unit:
return na.unit(self.nominal)

def astype(
self,
dtype: str | np.dtype | Type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ def asarray_like(
)


@_implements(na.unit)
def unit(a: na.AbstractUncertainScalarArray) -> None | u.UnitBase:
return na.unit(a.nominal)


@_implements(na.unit_normalized)
def unit_normalized(a: na.AbstractUncertainScalarArray) -> u.UnitBase:
return na.unit_normalized(a.nominal)


def random(
func: Callable,
*args: float | u.Quantity | na.AbstractScalar,
Expand Down
25 changes: 25 additions & 0 deletions named_arrays/_vectors/vector_named_array_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,31 @@ def arange(
return prototype.type_explicit.from_components(components)


@_implements(na.unit)
def unit(a: na.AbstractVectorArray) -> None | u.UnitBase | na.AbstractVectorArray:
components = a.components
components = {c: na.unit(components[c]) for c in components}
iter_components = iter(components)
component_0 = components[next(iter_components)]
if all(component_0 == components[c] for c in components):
return component_0
else:
components = {c: 1 if components[c] is None else 1 * components[c] for c in components}
return a.type_explicit.from_components(components,)


@_implements(na.unit_normalized)
def unit_normalized(a: na.AbstractVectorArray) -> u.UnitBase | na.AbstractVectorArray:
components = a.components
components = {c: na.unit_normalized(components[c]) for c in components}
iter_components = iter(components)
component_0 = components[next(iter_components)]
if all(component_0 == components[c] for c in components):
return component_0
else:
return a.type_explicit.from_components(components)


def random(
func: Callable,
*args: float | u.Quantity |na.AbstractScalar | na.AbstractVectorArray,
Expand Down
9 changes: 9 additions & 0 deletions named_arrays/tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,15 @@ def test_transpose(

class TestNamedArrayFunctions(abc.ABC):

def test_unit(self, array: na.AbstractArray):
result = na.unit(array)
if result is not None:
assert isinstance(result, (u.UnitBase, na.AbstractArray))

def test_unit_normalized(self, array: na.AbstractArray):
result = na.unit_normalized(array)
assert isinstance(result, (u.UnitBase, na.AbstractArray))

@pytest.mark.parametrize(
argnames="func",
argvalues=[
Expand Down

0 comments on commit dec4552

Please sign in to comment.