unxt 0.1.dev463__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of unxt might be problematic. Click here for more details.
- unxt/__init__.py +79 -0
- unxt/_interop/__init__.py +18 -0
- unxt/_interop/optional_deps.py +13 -0
- unxt/_interop/unxt_interop_astropy/__init__.py +5 -0
- unxt/_interop/unxt_interop_astropy/custom_types.py +12 -0
- unxt/_interop/unxt_interop_astropy/dimensions.py +53 -0
- unxt/_interop/unxt_interop_astropy/quantity.py +308 -0
- unxt/_interop/unxt_interop_astropy/units.py +111 -0
- unxt/_interop/unxt_interop_gala/__init__.py +5 -0
- unxt/_interop/unxt_interop_gala/unitsystems.py +108 -0
- unxt/_interop/unxt_interop_mpl/__init__.py +81 -0
- unxt/_src/__init__.py +6 -0
- unxt/_src/dimensions.py +400 -0
- unxt/_src/experimental.py +234 -0
- unxt/_src/quantity/__init__.py +24 -0
- unxt/_src/quantity/angle.py +58 -0
- unxt/_src/quantity/base.py +1160 -0
- unxt/_src/quantity/base_angle.py +89 -0
- unxt/_src/quantity/base_parametric.py +290 -0
- unxt/_src/quantity/flag.py +137 -0
- unxt/_src/quantity/mixins.py +238 -0
- unxt/_src/quantity/quantity.py +123 -0
- unxt/_src/quantity/register_api.py +383 -0
- unxt/_src/quantity/register_conversions.py +95 -0
- unxt/_src/quantity/register_dispatches.py +246 -0
- unxt/_src/quantity/register_primitives.py +5075 -0
- unxt/_src/quantity/static_quantity.py +90 -0
- unxt/_src/quantity/unchecked.py +45 -0
- unxt/_src/quantity/value.py +289 -0
- unxt/_src/units.py +103 -0
- unxt/_src/unitsystems/__init__.py +40 -0
- unxt/_src/unitsystems/base.py +258 -0
- unxt/_src/unitsystems/builtin.py +190 -0
- unxt/_src/unitsystems/builtin_dimensions.py +21 -0
- unxt/_src/unitsystems/compare.py +13 -0
- unxt/_src/unitsystems/core.py +307 -0
- unxt/_src/unitsystems/flags.py +80 -0
- unxt/_src/unitsystems/realizations.py +73 -0
- unxt/_src/unitsystems/utils.py +95 -0
- unxt/_src/utils.py +112 -0
- unxt/_version.py +34 -0
- unxt/_version.pyi +2 -0
- unxt/dims.py +19 -0
- unxt/py.typed +0 -0
- unxt/quantity.py +172 -0
- unxt/setup_package.py +60 -0
- unxt/units.py +19 -0
- unxt/unitsystems.py +67 -0
- unxt-0.1.dev463.dist-info/METADATA +533 -0
- unxt-0.1.dev463.dist-info/RECORD +52 -0
- unxt-0.1.dev463.dist-info/WHEEL +4 -0
- unxt-0.1.dev463.dist-info/licenses/LICENSE +29 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Static Quantity Class."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=import-error, no-member, unsubscriptable-object
|
|
4
|
+
# b/c it doesn't understand dataclass fields
|
|
5
|
+
|
|
6
|
+
__all__ = ("StaticQuantity",)
|
|
7
|
+
|
|
8
|
+
from typing import Any, final
|
|
9
|
+
|
|
10
|
+
import equinox as eqx
|
|
11
|
+
import wadler_lindig as wl
|
|
12
|
+
from numpy.typing import NDArray
|
|
13
|
+
from plum import add_promotion_rule, parametric
|
|
14
|
+
|
|
15
|
+
from .base_parametric import AbstractParametricQuantity
|
|
16
|
+
from .quantity import Quantity
|
|
17
|
+
from .unchecked import BareQuantity
|
|
18
|
+
from .value import StaticValue
|
|
19
|
+
from unxt.units import AbstractUnit, unit as parse_unit
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@final
|
|
23
|
+
@parametric
|
|
24
|
+
class StaticQuantity(AbstractParametricQuantity):
|
|
25
|
+
"""Static quantities with associated units.
|
|
26
|
+
|
|
27
|
+
This class is parametrized by the dimensions of the units, just like
|
|
28
|
+
{class}`~unxt.quantity.Quantity`, but its value is always stored as a static
|
|
29
|
+
NumPy array. It accepts Python scalars and array-like inputs that can be
|
|
30
|
+
converted to NumPy arrays, and it rejects JAX arrays.
|
|
31
|
+
|
|
32
|
+
Examples
|
|
33
|
+
--------
|
|
34
|
+
>>> import numpy as np
|
|
35
|
+
>>> import unxt as u
|
|
36
|
+
|
|
37
|
+
Basic construction:
|
|
38
|
+
|
|
39
|
+
>>> q = u.StaticQuantity(np.array([1.0, 2.0]), "m")
|
|
40
|
+
>>> q
|
|
41
|
+
StaticQuantity(array([1., 2.]), unit='m')
|
|
42
|
+
|
|
43
|
+
Values are static and hashable:
|
|
44
|
+
|
|
45
|
+
>>> isinstance(hash(q), int)
|
|
46
|
+
True
|
|
47
|
+
|
|
48
|
+
JAX arrays are rejected:
|
|
49
|
+
|
|
50
|
+
>>> import jax.numpy as jnp
|
|
51
|
+
>>> try:
|
|
52
|
+
... u.StaticQuantity(jnp.array([1.0, 2.0]), "m")
|
|
53
|
+
... except TypeError as e:
|
|
54
|
+
... print(e)
|
|
55
|
+
StaticQuantity does not accept JAX arrays. Use Quantity for traced values.
|
|
56
|
+
|
|
57
|
+
The Wadler-Lindig representation hides the internal static wrapper:
|
|
58
|
+
|
|
59
|
+
>>> import wadler_lindig as wl
|
|
60
|
+
>>> wl.pprint(q, short_arrays=False)
|
|
61
|
+
StaticQuantity(array([1., 2.]), unit='m')
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
value: StaticValue = eqx.field( # type: ignore[assignment]
|
|
66
|
+
static=True, converter=StaticValue.from_
|
|
67
|
+
)
|
|
68
|
+
"""The static value of the `AbstractQuantity`."""
|
|
69
|
+
|
|
70
|
+
unit: AbstractUnit = eqx.field(static=True, converter=parse_unit)
|
|
71
|
+
"""The unit associated with this value."""
|
|
72
|
+
|
|
73
|
+
def __hash__(self) -> int:
|
|
74
|
+
"""Return the hash of the quantity."""
|
|
75
|
+
return hash((self.value, self.unit))
|
|
76
|
+
|
|
77
|
+
def __eq__(self, other: Any, /) -> bool | NDArray[bool]: # type: ignore[override]
|
|
78
|
+
"""Return structural equality for static quantities."""
|
|
79
|
+
if isinstance(other, StaticQuantity):
|
|
80
|
+
return self.unit == other.unit and self.value == other.value
|
|
81
|
+
return super().__eq__(other)
|
|
82
|
+
|
|
83
|
+
def __pdoc__(self, *, show_wrapper: bool = False, **kwargs: Any) -> wl.AbstractDoc:
|
|
84
|
+
"""Return the Wadler-Lindig representation of this class."""
|
|
85
|
+
return super().__pdoc__(show_wrapper=False, **kwargs)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
add_promotion_rule(StaticQuantity, StaticQuantity, StaticQuantity)
|
|
89
|
+
add_promotion_rule(StaticQuantity, Quantity, Quantity)
|
|
90
|
+
add_promotion_rule(StaticQuantity, BareQuantity, BareQuantity)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# pylint: disable=import-error, no-member, unsubscriptable-object
|
|
2
|
+
# b/c it doesn't understand dataclass fields
|
|
3
|
+
|
|
4
|
+
__all__ = ("BareQuantity",)
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import equinox as eqx
|
|
9
|
+
from jaxtyping import Array, Shaped
|
|
10
|
+
from plum import add_promotion_rule
|
|
11
|
+
|
|
12
|
+
from .base import AbstractQuantity
|
|
13
|
+
from .quantity import Quantity
|
|
14
|
+
from .value import StaticValue, convert_to_quantity_value
|
|
15
|
+
from unxt.units import AbstractUnit, unit as parse_unit
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BareQuantity(AbstractQuantity):
|
|
19
|
+
"""A fast implementation of the Quantity class.
|
|
20
|
+
|
|
21
|
+
This class is not parametrized by its dimensionality.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
value: Shaped[Array | StaticValue, "*shape"] = eqx.field(
|
|
25
|
+
converter=convert_to_quantity_value
|
|
26
|
+
)
|
|
27
|
+
"""The value of the `AbstractQuantity`."""
|
|
28
|
+
|
|
29
|
+
unit: AbstractUnit = eqx.field(static=True, converter=parse_unit)
|
|
30
|
+
"""The unit associated with this value."""
|
|
31
|
+
|
|
32
|
+
def __class_getitem__(cls: "type[BareQuantity]", item: Any) -> "type[BareQuantity]":
|
|
33
|
+
"""No-op support for `BareQuantity[...]` syntax.
|
|
34
|
+
|
|
35
|
+
This method is called when the class is subscripted, e.g.:
|
|
36
|
+
|
|
37
|
+
>>> from unxt.quantity import BareQuantity
|
|
38
|
+
>>> BareQuantity["length"]
|
|
39
|
+
<class 'unxt...quantity...BareQuantity'>
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
return cls
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
add_promotion_rule(BareQuantity, Quantity, Quantity)
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""Conversion to quantity value types."""
|
|
2
|
+
|
|
3
|
+
__all__ = ("StaticValue", "convert_to_quantity_value")
|
|
4
|
+
|
|
5
|
+
import operator
|
|
6
|
+
import warnings
|
|
7
|
+
from typing import Any, final
|
|
8
|
+
from typing_extensions import override
|
|
9
|
+
|
|
10
|
+
import jax
|
|
11
|
+
import numpy as np
|
|
12
|
+
import quax
|
|
13
|
+
import wadler_lindig as wl
|
|
14
|
+
from jaxtyping import Array, ArrayLike
|
|
15
|
+
from numpy.typing import NDArray
|
|
16
|
+
from plum import dispatch
|
|
17
|
+
|
|
18
|
+
import quaxed.numpy as jnp
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@final
|
|
22
|
+
class StaticValue:
|
|
23
|
+
"""Immutable static value wrapper for `StaticQuantity`.
|
|
24
|
+
|
|
25
|
+
This stores a read-only NumPy array and is used to keep the value static
|
|
26
|
+
while avoiding Equinox's static-array warnings. Arithmetic operations
|
|
27
|
+
degrade to the wrapped array unless both operands are `StaticValue`, in
|
|
28
|
+
which case a `StaticValue` is returned.
|
|
29
|
+
|
|
30
|
+
Note that since the array is immutable, hashing is supported.
|
|
31
|
+
The hash is computed from the array's dtype, shape, and bytes.
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
__slots__ = ("_array",)
|
|
36
|
+
|
|
37
|
+
def __init__(self, array: np.ndarray, /) -> None:
|
|
38
|
+
value = np.asarray(array).copy()
|
|
39
|
+
value.setflags(write=False)
|
|
40
|
+
self._array = value
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def array(self) -> np.ndarray:
|
|
44
|
+
"""Return the contained NumPy array."""
|
|
45
|
+
return self._array
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def _jnparray(self) -> Array:
|
|
49
|
+
"""Return the contained array as a JAX array."""
|
|
50
|
+
return jnp.asarray(self._array)
|
|
51
|
+
|
|
52
|
+
# Constructor
|
|
53
|
+
@classmethod
|
|
54
|
+
@dispatch.abstract
|
|
55
|
+
def from_(cls: type["StaticValue"], *args: Any, **kwargs: Any) -> "StaticValue":
|
|
56
|
+
"""Create a `StaticValue` from given arguments."""
|
|
57
|
+
raise NotImplementedError # pragma: no cover
|
|
58
|
+
|
|
59
|
+
# ==================================================
|
|
60
|
+
# NumPy API
|
|
61
|
+
|
|
62
|
+
def __array__(self, dtype: Any = None) -> np.ndarray:
|
|
63
|
+
if dtype is None:
|
|
64
|
+
return np.asarray(self._array)
|
|
65
|
+
return np.asarray(self._array, dtype=dtype)
|
|
66
|
+
|
|
67
|
+
# ==================================================
|
|
68
|
+
# Wadler-Lindig API
|
|
69
|
+
|
|
70
|
+
def __pdoc__(self, *, show_wrapper: bool = True, **kwargs: Any) -> wl.AbstractDoc:
|
|
71
|
+
"""Return the Wadler-Lindig representation of this class."""
|
|
72
|
+
if not show_wrapper:
|
|
73
|
+
return wl.pdoc(self._array, **kwargs)
|
|
74
|
+
return (
|
|
75
|
+
wl.TextDoc("StaticValue(")
|
|
76
|
+
+ wl.pdoc(self._array, **kwargs)
|
|
77
|
+
+ wl.TextDoc(")")
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# ==================================================
|
|
81
|
+
# Python container API
|
|
82
|
+
|
|
83
|
+
def __len__(self) -> int:
|
|
84
|
+
return len(self._array)
|
|
85
|
+
|
|
86
|
+
def __iter__(self) -> Any:
|
|
87
|
+
return iter(self._array)
|
|
88
|
+
|
|
89
|
+
def __getitem__(self, key: Any) -> Any:
|
|
90
|
+
return self._array[key]
|
|
91
|
+
|
|
92
|
+
def __getattr__(self, name: str) -> Any:
|
|
93
|
+
return getattr(jnp.asarray(self._array), name)
|
|
94
|
+
|
|
95
|
+
def __repr__(self) -> str:
|
|
96
|
+
return wl.pformat(self, short_arrays=False, max_width=80)
|
|
97
|
+
|
|
98
|
+
@override
|
|
99
|
+
def __eq__(self, other: object, /) -> bool | NDArray[np.bool_]: # type: ignore[override]
|
|
100
|
+
if isinstance(other, StaticValue):
|
|
101
|
+
return bool(np.array_equal(self._array, other._array))
|
|
102
|
+
if isinstance(other, (np.ndarray, Array, list, tuple)):
|
|
103
|
+
return self._array == other
|
|
104
|
+
return NotImplemented
|
|
105
|
+
|
|
106
|
+
def __ne__(self, other: object, /) -> bool | NDArray[np.bool_]: # type: ignore[override]
|
|
107
|
+
if isinstance(other, StaticValue):
|
|
108
|
+
return not bool(np.array_equal(self._array, other._array))
|
|
109
|
+
if isinstance(other, (np.ndarray, Array, list, tuple)):
|
|
110
|
+
return self._array != other
|
|
111
|
+
return NotImplemented
|
|
112
|
+
|
|
113
|
+
def __lt__(self, other: Any, /) -> NDArray[np.bool_]:
|
|
114
|
+
if isinstance(other, StaticValue):
|
|
115
|
+
other = other._array
|
|
116
|
+
return self._array < other
|
|
117
|
+
|
|
118
|
+
def __le__(self, other: Any, /) -> NDArray[np.bool_]:
|
|
119
|
+
if isinstance(other, StaticValue):
|
|
120
|
+
other = other._array
|
|
121
|
+
return self._array <= other
|
|
122
|
+
|
|
123
|
+
def __gt__(self, other: Any, /) -> NDArray[np.bool_]:
|
|
124
|
+
if isinstance(other, StaticValue):
|
|
125
|
+
other = other._array
|
|
126
|
+
return self._array > other
|
|
127
|
+
|
|
128
|
+
def __ge__(self, other: Any, /) -> NDArray[np.bool_]:
|
|
129
|
+
if isinstance(other, StaticValue):
|
|
130
|
+
other = other._array
|
|
131
|
+
return self._array >= other
|
|
132
|
+
|
|
133
|
+
def __hash__(self) -> int:
|
|
134
|
+
return hash((self._array.dtype.str, self._array.shape, self._array.tobytes()))
|
|
135
|
+
|
|
136
|
+
def _binary_op(self, other: Any, op: Any) -> Any:
|
|
137
|
+
if isinstance(other, StaticValue):
|
|
138
|
+
result = op(self._jnparray, jnp.asarray(other.array))
|
|
139
|
+
return StaticValue(np.asarray(result))
|
|
140
|
+
return op(self._jnparray, other)
|
|
141
|
+
|
|
142
|
+
def _rbinary_op(self, other: Any, op: Any) -> Any:
|
|
143
|
+
if isinstance(other, StaticValue):
|
|
144
|
+
result = op(jnp.asarray(other.array), self._jnparray)
|
|
145
|
+
return StaticValue(np.asarray(result))
|
|
146
|
+
return op(other, self._jnparray)
|
|
147
|
+
|
|
148
|
+
def __add__(self, other: Any) -> Any:
|
|
149
|
+
return self._binary_op(other, operator.add)
|
|
150
|
+
|
|
151
|
+
def __radd__(self, other: Any) -> Any:
|
|
152
|
+
return self._rbinary_op(other, operator.add)
|
|
153
|
+
|
|
154
|
+
def __sub__(self, other: Any) -> Any:
|
|
155
|
+
return self._binary_op(other, operator.sub)
|
|
156
|
+
|
|
157
|
+
def __rsub__(self, other: Any) -> Any:
|
|
158
|
+
return self._rbinary_op(other, operator.sub)
|
|
159
|
+
|
|
160
|
+
def __mul__(self, other: Any) -> Any:
|
|
161
|
+
return self._binary_op(other, operator.mul)
|
|
162
|
+
|
|
163
|
+
def __rmul__(self, other: Any) -> Any:
|
|
164
|
+
return self._rbinary_op(other, operator.mul)
|
|
165
|
+
|
|
166
|
+
def __truediv__(self, other: Any) -> Any:
|
|
167
|
+
return self._binary_op(other, operator.truediv)
|
|
168
|
+
|
|
169
|
+
def __rtruediv__(self, other: Any) -> Any:
|
|
170
|
+
return self._rbinary_op(other, operator.truediv)
|
|
171
|
+
|
|
172
|
+
def __floordiv__(self, other: Any) -> Any:
|
|
173
|
+
return self._binary_op(other, operator.floordiv)
|
|
174
|
+
|
|
175
|
+
def __rfloordiv__(self, other: Any) -> Any:
|
|
176
|
+
return self._rbinary_op(other, operator.floordiv)
|
|
177
|
+
|
|
178
|
+
def __pow__(self, other: Any) -> Any:
|
|
179
|
+
return self._binary_op(other, operator.pow)
|
|
180
|
+
|
|
181
|
+
def __rpow__(self, other: Any) -> Any:
|
|
182
|
+
return self._rbinary_op(other, operator.pow)
|
|
183
|
+
|
|
184
|
+
def __mod__(self, other: Any) -> Any:
|
|
185
|
+
return self._binary_op(other, operator.mod)
|
|
186
|
+
|
|
187
|
+
def __rmod__(self, other: Any) -> Any:
|
|
188
|
+
return self._rbinary_op(other, operator.mod)
|
|
189
|
+
|
|
190
|
+
def __matmul__(self, other: Any) -> Any:
|
|
191
|
+
return self._binary_op(other, operator.matmul)
|
|
192
|
+
|
|
193
|
+
def __rmatmul__(self, other: Any) -> Any:
|
|
194
|
+
return self._rbinary_op(other, operator.matmul)
|
|
195
|
+
|
|
196
|
+
def __neg__(self) -> Any:
|
|
197
|
+
return -self._jnparray
|
|
198
|
+
|
|
199
|
+
def __pos__(self) -> Any:
|
|
200
|
+
return +self._jnparray
|
|
201
|
+
|
|
202
|
+
def __abs__(self) -> Any:
|
|
203
|
+
return abs(self._jnparray)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@StaticValue.from_.dispatch
|
|
207
|
+
def from_(cls: type[StaticValue], value: object, /) -> StaticValue:
|
|
208
|
+
"""Convert a value for `StaticQuantity`."""
|
|
209
|
+
return cls(np.asarray(value))
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@StaticValue.from_.dispatch
|
|
213
|
+
def from_(cls: type[StaticValue], value: StaticValue, /) -> StaticValue:
|
|
214
|
+
"""Convert a value for `StaticQuantity`."""
|
|
215
|
+
return value
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@StaticValue.from_.dispatch
|
|
219
|
+
def from_(cls: type[StaticValue], value: jax.Array | jax.core.Tracer, /) -> StaticValue:
|
|
220
|
+
"""Reject JAX arrays for `StaticQuantity`."""
|
|
221
|
+
msg = "StaticQuantity does not accept JAX arrays. Use Quantity for traced values."
|
|
222
|
+
raise TypeError(msg)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# ==================================================================
|
|
226
|
+
# Converters for quantity value field
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@dispatch.abstract
|
|
230
|
+
def convert_to_quantity_value(obj: Any, /) -> Any:
|
|
231
|
+
"""Convert for the value field of an `AbstractQuantity` subclass."""
|
|
232
|
+
raise NotImplementedError # pragma: no cover
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@dispatch
|
|
236
|
+
def convert_to_quantity_value(obj: StaticValue, /) -> StaticValue:
|
|
237
|
+
"""Allow static values in `Quantity`-like classes."""
|
|
238
|
+
return obj
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@dispatch
|
|
242
|
+
def convert_to_quantity_value(obj: quax.ArrayValue, /) -> Any:
|
|
243
|
+
"""Convert a `quax.ArrayValue` for the value field.
|
|
244
|
+
|
|
245
|
+
>>> import warnings
|
|
246
|
+
>>> import jax
|
|
247
|
+
>>> import jax.numpy as jnp
|
|
248
|
+
>>> from jaxtyping import Array
|
|
249
|
+
>>> from quax import ArrayValue
|
|
250
|
+
|
|
251
|
+
>>> class MyArray(ArrayValue):
|
|
252
|
+
... value: Array
|
|
253
|
+
...
|
|
254
|
+
... def aval(self):
|
|
255
|
+
... return jax.core.ShapedArray(self.value.shape, self.value.dtype)
|
|
256
|
+
...
|
|
257
|
+
... def materialise(self):
|
|
258
|
+
... return self.value
|
|
259
|
+
|
|
260
|
+
>>> x = MyArray(jnp.array([1, 2, 3]))
|
|
261
|
+
>>> with warnings.catch_warnings(record=True, action="always") as w:
|
|
262
|
+
... y = convert_to_quantity_value(x)
|
|
263
|
+
>>> y
|
|
264
|
+
MyArray(value=i32[3])
|
|
265
|
+
>>> print(f"Warning caught: {w[-1].message}")
|
|
266
|
+
Warning caught: 'quax.ArrayValue' subclass 'MyArray' ...
|
|
267
|
+
|
|
268
|
+
"""
|
|
269
|
+
warnings.warn(
|
|
270
|
+
f"'quax.ArrayValue' subclass {type(obj).__name__!r} does not have a registered "
|
|
271
|
+
"converter. Returning the object as is.",
|
|
272
|
+
category=UserWarning,
|
|
273
|
+
stacklevel=2,
|
|
274
|
+
)
|
|
275
|
+
return obj
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@dispatch
|
|
279
|
+
def convert_to_quantity_value(obj: ArrayLike | list[Any] | tuple[Any, ...], /) -> Array:
|
|
280
|
+
"""Convert an array-like object to a `jax.numpy.ndarray`.
|
|
281
|
+
|
|
282
|
+
>>> import jax.numpy as jnp
|
|
283
|
+
>>> from unxt.quantity import convert_to_quantity_value
|
|
284
|
+
|
|
285
|
+
>>> convert_to_quantity_value([1, 2, 3])
|
|
286
|
+
Array([1, 2, 3], dtype=int32)
|
|
287
|
+
|
|
288
|
+
"""
|
|
289
|
+
return jnp.asarray(obj)
|
unxt/_src/units.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Units objects in JAX.
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Galactic Dynamics. All rights reserved.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__all__ = ("unit", "unit_of", "AbstractUnit")
|
|
7
|
+
|
|
8
|
+
from typing import Any, TypeAlias
|
|
9
|
+
|
|
10
|
+
import astropy.units as apyu
|
|
11
|
+
from plum import dispatch
|
|
12
|
+
|
|
13
|
+
import unxt_api as uapi
|
|
14
|
+
from unxt.dims import AbstractDimension
|
|
15
|
+
|
|
16
|
+
AbstractUnit: TypeAlias = apyu.Unit | apyu.UnitBase | apyu.CompositeUnit
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ===================================================================
|
|
20
|
+
# Construct units
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dispatch
|
|
24
|
+
def unit(obj: AbstractUnit, /) -> AbstractUnit:
|
|
25
|
+
"""Construct the units from a units object.
|
|
26
|
+
|
|
27
|
+
Examples
|
|
28
|
+
--------
|
|
29
|
+
>>> import unxt as u
|
|
30
|
+
>>> m = u.unit("m")
|
|
31
|
+
|
|
32
|
+
>>> u.unit(m) is m
|
|
33
|
+
True
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
return obj
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dispatch
|
|
40
|
+
def unit(obj: str, /) -> AbstractUnit:
|
|
41
|
+
"""Construct units from a string.
|
|
42
|
+
|
|
43
|
+
Examples
|
|
44
|
+
--------
|
|
45
|
+
>>> import unxt as u
|
|
46
|
+
>>> m = u.unit("m")
|
|
47
|
+
>>> m
|
|
48
|
+
Unit("m")
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
return apyu.Unit(obj)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ===================================================================
|
|
55
|
+
# Get units
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dispatch
|
|
59
|
+
def unit_of(obj: Any, /) -> None:
|
|
60
|
+
"""Return the units of an object.
|
|
61
|
+
|
|
62
|
+
Examples
|
|
63
|
+
--------
|
|
64
|
+
>>> import unxt as u
|
|
65
|
+
>>> print(u.unit_of(1))
|
|
66
|
+
None
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
return None # noqa: RET501
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dispatch
|
|
73
|
+
def unit_of(obj: AbstractUnit, /) -> AbstractUnit:
|
|
74
|
+
"""Return the units of an unit.
|
|
75
|
+
|
|
76
|
+
Examples
|
|
77
|
+
--------
|
|
78
|
+
>>> import unxt as u
|
|
79
|
+
>>> m = u.unit("m")
|
|
80
|
+
|
|
81
|
+
>>> u.unit_of(m)
|
|
82
|
+
Unit("m")
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
return obj
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# ===================================================================
|
|
89
|
+
# Get dimensions
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dispatch
|
|
93
|
+
def dimension_of(obj: AbstractUnit, /) -> AbstractDimension:
|
|
94
|
+
"""Return the dimensions of the given units.
|
|
95
|
+
|
|
96
|
+
Examples
|
|
97
|
+
--------
|
|
98
|
+
>>> import unxt as u
|
|
99
|
+
>>> u.dimension_of(u.unit("km"))
|
|
100
|
+
PhysicalType('length')
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
return uapi.dimension(obj.physical_type)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Tools for representing systems of units using ``astropy.units``."""
|
|
2
|
+
# pylint: disable=duplicate-code
|
|
3
|
+
|
|
4
|
+
__all__ = (
|
|
5
|
+
"unitsystem",
|
|
6
|
+
"unitsystem_of",
|
|
7
|
+
# classes
|
|
8
|
+
"AbstractUnitSystem",
|
|
9
|
+
"UNITSYSTEMS_REGISTRY",
|
|
10
|
+
"DimensionlessUnitSystem",
|
|
11
|
+
"LTMAUnitSystem",
|
|
12
|
+
# unit system instance
|
|
13
|
+
"galactic",
|
|
14
|
+
"dimensionless",
|
|
15
|
+
"solarsystem",
|
|
16
|
+
"si",
|
|
17
|
+
"cgs",
|
|
18
|
+
# unit system alias
|
|
19
|
+
"NAMED_UNIT_SYSTEMS",
|
|
20
|
+
# unit system flags
|
|
21
|
+
"AbstractUSysFlag",
|
|
22
|
+
"StandardUSysFlag",
|
|
23
|
+
"DynamicalSimUSysFlag",
|
|
24
|
+
# functions
|
|
25
|
+
"equivalent",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
from .base import UNITSYSTEMS_REGISTRY, AbstractUnitSystem
|
|
29
|
+
from .builtin import DimensionlessUnitSystem, LTMAUnitSystem
|
|
30
|
+
from .compare import equivalent
|
|
31
|
+
from .core import unitsystem, unitsystem_of
|
|
32
|
+
from .flags import AbstractUSysFlag, DynamicalSimUSysFlag, StandardUSysFlag
|
|
33
|
+
from .realizations import (
|
|
34
|
+
NAMED_UNIT_SYSTEMS,
|
|
35
|
+
cgs,
|
|
36
|
+
dimensionless,
|
|
37
|
+
galactic,
|
|
38
|
+
si,
|
|
39
|
+
solarsystem,
|
|
40
|
+
)
|