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,238 @@
|
|
|
1
|
+
"""Orthogonal Mixin classes for Quantity classes."""
|
|
2
|
+
|
|
3
|
+
__all__: tuple[str, ...] = ()
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Sequence
|
|
6
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
7
|
+
|
|
8
|
+
import equinox as eqx
|
|
9
|
+
import numpy as np
|
|
10
|
+
import numpy.typing as npt
|
|
11
|
+
from astropy.units import CompositeUnit
|
|
12
|
+
from jax.typing import ArrayLike
|
|
13
|
+
from jaxtyping import Array
|
|
14
|
+
|
|
15
|
+
from dataclassish import replace
|
|
16
|
+
|
|
17
|
+
import unxt_api as uapi
|
|
18
|
+
from unxt.units import AbstractUnit, unit as parse_unit
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
import unxt.quantity
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AstropyQuantityCompatMixin:
|
|
25
|
+
"""Mixin for compatibility with `astropy.units.Quantity`."""
|
|
26
|
+
|
|
27
|
+
value: eqx.AbstractVar[ArrayLike]
|
|
28
|
+
unit: eqx.AbstractVar[AbstractUnit]
|
|
29
|
+
uconvert: Callable[[Any], "unxt.quantity.AbstractQuantity"]
|
|
30
|
+
ustrip: Callable[[Any], ArrayLike]
|
|
31
|
+
|
|
32
|
+
def to(self, u: Any, /) -> "unxt.quantity.AbstractQuantity":
|
|
33
|
+
"""Convert the quantity to the given units.
|
|
34
|
+
|
|
35
|
+
See `unxt.quantity.AbstractQuantity.uconvert`.
|
|
36
|
+
|
|
37
|
+
Examples
|
|
38
|
+
--------
|
|
39
|
+
>>> from unxt import Quantity
|
|
40
|
+
|
|
41
|
+
>>> q = Quantity(1, "m")
|
|
42
|
+
>>> q.to("cm")
|
|
43
|
+
Quantity(Array(100., dtype=float32, ...), unit='cm')
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
return uapi.uconvert(u, self) # redirect to the standard method
|
|
47
|
+
|
|
48
|
+
def to_value(self, u: Any, /) -> ArrayLike:
|
|
49
|
+
"""Return the value in the given units.
|
|
50
|
+
|
|
51
|
+
See `unxt.AbstractQuantity.ustrip`.
|
|
52
|
+
|
|
53
|
+
Examples
|
|
54
|
+
--------
|
|
55
|
+
>>> from unxt import Quantity
|
|
56
|
+
|
|
57
|
+
>>> q = Quantity(1, "m")
|
|
58
|
+
>>> q.to_value("cm")
|
|
59
|
+
Array(100., dtype=float32, weak_type=True)
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
return uapi.ustrip(u, self) # redirect to the standard method
|
|
63
|
+
|
|
64
|
+
def decompose(
|
|
65
|
+
self, bases: Sequence[AbstractUnit | str], /
|
|
66
|
+
) -> "unxt.quantity.AbstractQuantity":
|
|
67
|
+
"""Decompose the quantity into the given bases.
|
|
68
|
+
|
|
69
|
+
Examples
|
|
70
|
+
--------
|
|
71
|
+
>>> from unxt import Quantity
|
|
72
|
+
|
|
73
|
+
>>> q = Quantity(1, "m")
|
|
74
|
+
>>> q.decompose(["cm", "s"])
|
|
75
|
+
Quantity(Array(100., dtype=float32, ...), unit='cm')
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
bases_ = [parse_unit(b) for b in bases]
|
|
79
|
+
du = self.unit.decompose(bases_) # decomposed units
|
|
80
|
+
base_units = CompositeUnit(scale=1, bases=du.bases, powers=du.powers)
|
|
81
|
+
return replace(self, value=self.value * du.scale, unit=base_units)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
#####################################################################
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
SUPPORTED_IPYTHON_REPR_FORMATS: dict[str, str] = {
|
|
88
|
+
"text/plain": "__repr__",
|
|
89
|
+
"text/html": "_repr_html_",
|
|
90
|
+
"text/latex": "_repr_latex_",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class IPythonReprMixin:
|
|
95
|
+
"""Mixin class for IPython representation of a Quantity."""
|
|
96
|
+
|
|
97
|
+
value: Array
|
|
98
|
+
unit: AbstractUnit
|
|
99
|
+
|
|
100
|
+
def _repr_mimebundle_(
|
|
101
|
+
self,
|
|
102
|
+
*,
|
|
103
|
+
include: Sequence[str] | None = None,
|
|
104
|
+
exclude: Sequence[str] | None = None,
|
|
105
|
+
) -> dict[str, str]:
|
|
106
|
+
r"""Return a MIME bundle representation of the Quantity.
|
|
107
|
+
|
|
108
|
+
:param include: The set of keys to include in the MIME bundle. If not
|
|
109
|
+
provided, all supported formats are included.
|
|
110
|
+
:param exclude: The set of keys to exclude in the MIME bundle. If not
|
|
111
|
+
provided, all supported formats are included. 'include' has
|
|
112
|
+
precedence over 'exclude'.
|
|
113
|
+
|
|
114
|
+
Examples
|
|
115
|
+
--------
|
|
116
|
+
>>> from unxt import Quantity
|
|
117
|
+
|
|
118
|
+
>>> q = Quantity([1.0, 2, 3, 4], "m")
|
|
119
|
+
>>> q._repr_mimebundle_()
|
|
120
|
+
{'text/plain': "Quantity(Array([1., 2., 3., 4.], dtype=float32), unit='m')",
|
|
121
|
+
'text/html': '<span>[1., 2., 3., 4.]</span> * <span>Unit("m")</span>',
|
|
122
|
+
'text/latex': '$[1.,~2.,~3.,~4.] \\; \\mathrm{m}$'}
|
|
123
|
+
|
|
124
|
+
>>> q._repr_mimebundle_(include=["text/plain"])
|
|
125
|
+
{'text/plain': "Quantity(Array([1., 2., 3., 4.], dtype=float32), unit='m')"}
|
|
126
|
+
|
|
127
|
+
>>> q._repr_mimebundle_(exclude=["text/html", "text/latex"])
|
|
128
|
+
{'text/plain': "Quantity(Array([1., 2., 3., 4.], dtype=float32), unit='m')"}
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
# Determine the set of keys to include in the MIME bundle
|
|
132
|
+
keys: Sequence[str]
|
|
133
|
+
if include is None and exclude is None:
|
|
134
|
+
keys = tuple(SUPPORTED_IPYTHON_REPR_FORMATS)
|
|
135
|
+
elif include is not None:
|
|
136
|
+
keys = [key for key in include if key in SUPPORTED_IPYTHON_REPR_FORMATS]
|
|
137
|
+
else:
|
|
138
|
+
keys = [
|
|
139
|
+
k
|
|
140
|
+
for k in SUPPORTED_IPYTHON_REPR_FORMATS
|
|
141
|
+
if k not in cast("str", exclude)
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
# Create the MIME bundle
|
|
145
|
+
return {
|
|
146
|
+
key: getattr(self, SUPPORTED_IPYTHON_REPR_FORMATS[key])() for key in keys
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
def _repr_html_(self) -> str:
|
|
150
|
+
"""Return an HTML representation of the Quantity.
|
|
151
|
+
|
|
152
|
+
Examples
|
|
153
|
+
--------
|
|
154
|
+
>>> from unxt import Quantity
|
|
155
|
+
|
|
156
|
+
>>> q = Quantity([1.0, 2, 3, 4], "m")
|
|
157
|
+
>>> q._repr_html_()
|
|
158
|
+
'<span>[1., 2., 3., 4.]</span> * <span>Unit("m")</span>'
|
|
159
|
+
|
|
160
|
+
"""
|
|
161
|
+
unit_repr = getattr(self.unit, "_repr_html_", self.unit.__repr__)()
|
|
162
|
+
value_repr = np.array2string(self.value, separator=", ") # type: ignore[call-overload]
|
|
163
|
+
|
|
164
|
+
return f"<span>{value_repr}</span> * <span>{unit_repr}</span>"
|
|
165
|
+
|
|
166
|
+
def _repr_latex_(self) -> str:
|
|
167
|
+
r"""Return a LaTeX representation of the Quantity.
|
|
168
|
+
|
|
169
|
+
Examples
|
|
170
|
+
--------
|
|
171
|
+
>>> from unxt import Quantity
|
|
172
|
+
|
|
173
|
+
>>> q = Quantity([1.0, 2, 3, 4], "m")
|
|
174
|
+
>>> q._repr_latex_()
|
|
175
|
+
'$[1.,~2.,~3.,~4.] \\; \\mathrm{m}$'
|
|
176
|
+
|
|
177
|
+
"""
|
|
178
|
+
unit_repr = getattr(self.unit, "_repr_latex_", self.unit.__repr__)()
|
|
179
|
+
value_repr = np.array2string(self.value, separator=",~") # type: ignore[call-overload]
|
|
180
|
+
return f"${value_repr} \\; {unit_repr[1:-1]}$"
|
|
181
|
+
|
|
182
|
+
# TODO: implement:
|
|
183
|
+
# - _repr_markdown_
|
|
184
|
+
# - _repr_json_
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
#####################################################################
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class NumPyCompatMixin:
|
|
191
|
+
"""Mixin for compatibility with numpy arrays."""
|
|
192
|
+
|
|
193
|
+
unit: AbstractUnit
|
|
194
|
+
|
|
195
|
+
__array_namespace__: Callable[[], Any]
|
|
196
|
+
|
|
197
|
+
def __array__(self, *args: object, **kw: object) -> npt.NDArray[Any]:
|
|
198
|
+
"""Return the array as a numpy array, stripping the units.
|
|
199
|
+
|
|
200
|
+
Examples
|
|
201
|
+
--------
|
|
202
|
+
>>> from unxt import Quantity
|
|
203
|
+
>>> import numpy as np
|
|
204
|
+
|
|
205
|
+
>>> q = Quantity(1.01, "m")
|
|
206
|
+
>>> np.array(q)
|
|
207
|
+
array(1.01, dtype=float32)
|
|
208
|
+
|
|
209
|
+
"""
|
|
210
|
+
return np.asarray(uapi.ustrip(self.unit, self), *args, **kw)
|
|
211
|
+
|
|
212
|
+
# TODO: why doesn't `__array_namespace__` supersede this?
|
|
213
|
+
def __array_function__(
|
|
214
|
+
self,
|
|
215
|
+
func: Callable[..., Any],
|
|
216
|
+
types: Any,
|
|
217
|
+
args: tuple[Any, ...],
|
|
218
|
+
kwargs: dict[str, Any],
|
|
219
|
+
) -> Any:
|
|
220
|
+
"""Dispatch to the corresponding jax.numpy function.
|
|
221
|
+
|
|
222
|
+
Examples
|
|
223
|
+
--------
|
|
224
|
+
>>> import numpy as np
|
|
225
|
+
>>> import unxt as u
|
|
226
|
+
|
|
227
|
+
>>> q = u.Quantity([1.0, 2, 3, 4], "m")
|
|
228
|
+
>>> np.sum(q)
|
|
229
|
+
Quantity(Array(10., dtype=float32), unit='m')
|
|
230
|
+
|
|
231
|
+
>>> np.stack([q, q])
|
|
232
|
+
Quantity(Array([[1., 2., 3., 4.],
|
|
233
|
+
[1., 2., 3., 4.]], dtype=float32), unit='m')
|
|
234
|
+
|
|
235
|
+
"""
|
|
236
|
+
xp = self.__array_namespace__()
|
|
237
|
+
xfunc = getattr(xp, func.__name__)
|
|
238
|
+
return xfunc(*args, **kwargs)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# pylint: disable=import-error, no-member, unsubscriptable-object
|
|
2
|
+
# b/c it doesn't understand dataclass fields
|
|
3
|
+
|
|
4
|
+
__all__ = ("Quantity", "Q")
|
|
5
|
+
|
|
6
|
+
from typing import ClassVar, final
|
|
7
|
+
|
|
8
|
+
import equinox as eqx
|
|
9
|
+
from jaxtyping import Array, Shaped
|
|
10
|
+
from plum import parametric
|
|
11
|
+
|
|
12
|
+
from .base_parametric import AbstractParametricQuantity
|
|
13
|
+
from .value import StaticValue, convert_to_quantity_value
|
|
14
|
+
from unxt.units import AbstractUnit, unit as parse_unit
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@final
|
|
18
|
+
@parametric
|
|
19
|
+
class Quantity(AbstractParametricQuantity):
|
|
20
|
+
"""Arrays with associated units.
|
|
21
|
+
|
|
22
|
+
This class is parametrized by the dimensions of the units.
|
|
23
|
+
|
|
24
|
+
Attributes
|
|
25
|
+
----------
|
|
26
|
+
short_name : str
|
|
27
|
+
Short name 'Q' used for compact wadler-lindig printing.
|
|
28
|
+
|
|
29
|
+
Examples
|
|
30
|
+
--------
|
|
31
|
+
>>> import unxt as u
|
|
32
|
+
|
|
33
|
+
From an integer:
|
|
34
|
+
|
|
35
|
+
>>> u.Quantity(1, "m")
|
|
36
|
+
Quantity(Array(1, dtype=int32, ...), unit='m')
|
|
37
|
+
|
|
38
|
+
From a float:
|
|
39
|
+
|
|
40
|
+
>>> u.Q(1.0, "m")
|
|
41
|
+
Quantity(Array(1., dtype=float32, ...), unit='m')
|
|
42
|
+
|
|
43
|
+
From a list:
|
|
44
|
+
|
|
45
|
+
>>> u.Quantity([1, 2, 3], "m")
|
|
46
|
+
Quantity(Array([1, 2, 3], dtype=int32), unit='m')
|
|
47
|
+
|
|
48
|
+
From a tuple:
|
|
49
|
+
|
|
50
|
+
>>> u.Quantity((1, 2, 3), "m")
|
|
51
|
+
Quantity(Array([1, 2, 3], dtype=int32), unit='m')
|
|
52
|
+
|
|
53
|
+
From a `numpy.ndarray`:
|
|
54
|
+
|
|
55
|
+
>>> import numpy as np
|
|
56
|
+
>>> u.Quantity(np.array([1, 2, 3]), "m")
|
|
57
|
+
Quantity(Array([1, 2, 3], dtype=int32), unit='m')
|
|
58
|
+
|
|
59
|
+
From a `jax.Array`:
|
|
60
|
+
|
|
61
|
+
>>> import jax.numpy as jnp
|
|
62
|
+
>>> u.Quantity(jnp.array([1, 2, 3]), "m")
|
|
63
|
+
Quantity(Array([1, 2, 3], dtype=int32), unit='m')
|
|
64
|
+
|
|
65
|
+
The unit can also be given as a units object:
|
|
66
|
+
|
|
67
|
+
>>> u.Quantity(1, u.unit("m"))
|
|
68
|
+
Quantity(Array(1, dtype=int32, ...), unit='m')
|
|
69
|
+
|
|
70
|
+
In the previous examples, the dimension parameter was inferred from the
|
|
71
|
+
values. It can also be given explicitly:
|
|
72
|
+
|
|
73
|
+
>>> u.Quantity["length"](1, "m")
|
|
74
|
+
Quantity(Array(1, dtype=int32, ...), unit='m')
|
|
75
|
+
|
|
76
|
+
This can be used for runtime checking of the input dimension!
|
|
77
|
+
|
|
78
|
+
>>> try:
|
|
79
|
+
... u.Quantity["length"](1, "s")
|
|
80
|
+
... except Exception as e:
|
|
81
|
+
... print(e)
|
|
82
|
+
Physical type mismatch.
|
|
83
|
+
|
|
84
|
+
The dimension can also be given as a dimension object:
|
|
85
|
+
|
|
86
|
+
>>> dims = u.dimension("length")
|
|
87
|
+
>>> dims
|
|
88
|
+
PhysicalType('length')
|
|
89
|
+
>>> u.Quantity[dims](1.0, "m")
|
|
90
|
+
Quantity(Array(1., dtype=float32, ...), unit='m')
|
|
91
|
+
|
|
92
|
+
Or as a unit:
|
|
93
|
+
|
|
94
|
+
>>> u.Quantity[u.unit("m")](1.0, "m")
|
|
95
|
+
Quantity(Array(1., dtype=float32, ...), unit='m')
|
|
96
|
+
|
|
97
|
+
Some tricky cases are when the physical type is unknown:
|
|
98
|
+
|
|
99
|
+
>>> unit = u.unit("m2 / (kg s2)")
|
|
100
|
+
>>> u.dimension_of(unit)
|
|
101
|
+
PhysicalType('unknown')
|
|
102
|
+
|
|
103
|
+
The dimension can be given as a string in all cases, but is necessary when
|
|
104
|
+
the physical type is unknown:
|
|
105
|
+
|
|
106
|
+
>>> print(u.Quantity["m2 kg-1 s-2"](1.0, unit)) # to show the [dim]
|
|
107
|
+
Quantity['m2 kg-1 s-2'](1., unit='m2 / (kg s2)')
|
|
108
|
+
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
short_name: ClassVar[str] = "Q"
|
|
112
|
+
"""Short name for compact printing."""
|
|
113
|
+
|
|
114
|
+
value: Shaped[Array, "*shape"] | StaticValue = eqx.field(
|
|
115
|
+
converter=convert_to_quantity_value
|
|
116
|
+
)
|
|
117
|
+
"""The value of the `AbstractQuantity`."""
|
|
118
|
+
|
|
119
|
+
unit: AbstractUnit = eqx.field(static=True, converter=parse_unit)
|
|
120
|
+
"""The unit associated with this value."""
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
Q = Quantity # convenience alias
|