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,1160 @@
|
|
|
1
|
+
# pylint: disable=import-error, no-member, unsubscriptable-object
|
|
2
|
+
# b/c it doesn't understand dataclass fields
|
|
3
|
+
|
|
4
|
+
__all__ = ("AbstractQuantity", "is_any_quantity")
|
|
5
|
+
|
|
6
|
+
import functools as ft
|
|
7
|
+
import warnings
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from types import ModuleType
|
|
10
|
+
from typing import (
|
|
11
|
+
TYPE_CHECKING,
|
|
12
|
+
Any,
|
|
13
|
+
ClassVar,
|
|
14
|
+
Literal,
|
|
15
|
+
NoReturn,
|
|
16
|
+
TypeAlias,
|
|
17
|
+
TypeGuard,
|
|
18
|
+
cast,
|
|
19
|
+
)
|
|
20
|
+
from typing_extensions import override
|
|
21
|
+
|
|
22
|
+
import equinox as eqx
|
|
23
|
+
import jax
|
|
24
|
+
import jax.core
|
|
25
|
+
import numpy as np
|
|
26
|
+
import quax_blocks
|
|
27
|
+
import wadler_lindig as wl
|
|
28
|
+
from jax._src.numpy.array_methods import _IndexUpdateHelper, _IndexUpdateRef
|
|
29
|
+
from jaxtyping import Array, ArrayLike, Bool, ScalarLike, Shaped
|
|
30
|
+
from plum import add_promotion_rule, dispatch, type_nonparametric
|
|
31
|
+
from quax import ArrayValue
|
|
32
|
+
|
|
33
|
+
import quaxed.numpy as jnp
|
|
34
|
+
from dataclassish import field_items, replace
|
|
35
|
+
|
|
36
|
+
import unxt_api as uapi
|
|
37
|
+
from .mixins import AstropyQuantityCompatMixin, IPythonReprMixin, NumPyCompatMixin
|
|
38
|
+
from .value import StaticValue
|
|
39
|
+
from unxt.units import AbstractUnit
|
|
40
|
+
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
from typing import Self
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
ArrayLikeSequence: TypeAlias = list[ScalarLike] | tuple[ScalarLike, ...]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class AbstractQuantity(
|
|
49
|
+
AstropyQuantityCompatMixin,
|
|
50
|
+
NumPyCompatMixin,
|
|
51
|
+
IPythonReprMixin,
|
|
52
|
+
ArrayValue,
|
|
53
|
+
quax_blocks.NumpyBinaryOpsMixin[Any, "AbstractQuantity"],
|
|
54
|
+
quax_blocks.NumpyComparisonMixin[Any, Bool[Array, "*shape"]], # TODO: shape hint
|
|
55
|
+
quax_blocks.NumpyUnaryMixin["AbstractQuantity"],
|
|
56
|
+
quax_blocks.NumpyRoundMixin["AbstractQuantity"],
|
|
57
|
+
quax_blocks.NumpyTruncMixin["AbstractQuantity"],
|
|
58
|
+
quax_blocks.NumpyFloorMixin["AbstractQuantity"],
|
|
59
|
+
quax_blocks.NumpyCeilMixin["AbstractQuantity"],
|
|
60
|
+
quax_blocks.LaxLenMixin,
|
|
61
|
+
quax_blocks.LaxLengthHintMixin,
|
|
62
|
+
):
|
|
63
|
+
"""Represents a Quantity with a unit.
|
|
64
|
+
|
|
65
|
+
Attributes
|
|
66
|
+
----------
|
|
67
|
+
short_name : str | None
|
|
68
|
+
Optional short name for the class used in wadler-lindig printing when
|
|
69
|
+
``use_short_name=True``. Defaults to ``None``.
|
|
70
|
+
|
|
71
|
+
Examples
|
|
72
|
+
--------
|
|
73
|
+
>>> import unxt as u
|
|
74
|
+
|
|
75
|
+
From an integer:
|
|
76
|
+
|
|
77
|
+
>>> u.Quantity(1, "m")
|
|
78
|
+
Quantity(Array(1, dtype=int32, ...), unit='m')
|
|
79
|
+
|
|
80
|
+
From a float:
|
|
81
|
+
|
|
82
|
+
>>> u.Quantity(1.0, "m")
|
|
83
|
+
Quantity(Array(1., dtype=float32, ...), unit='m')
|
|
84
|
+
|
|
85
|
+
From a list:
|
|
86
|
+
|
|
87
|
+
>>> u.Quantity([1, 2, 3], "m")
|
|
88
|
+
Quantity(Array([1, 2, 3], dtype=int32), unit='m')
|
|
89
|
+
|
|
90
|
+
From a tuple:
|
|
91
|
+
|
|
92
|
+
>>> u.Quantity((1, 2, 3), "m")
|
|
93
|
+
Quantity(Array([1, 2, 3], dtype=int32), unit='m')
|
|
94
|
+
|
|
95
|
+
From a `numpy.ndarray`:
|
|
96
|
+
|
|
97
|
+
>>> import numpy as np
|
|
98
|
+
>>> u.Quantity(np.array([1, 2, 3]), "m")
|
|
99
|
+
Quantity(Array([1, 2, 3], dtype=int32), unit='m')
|
|
100
|
+
|
|
101
|
+
From a `jax.Array`:
|
|
102
|
+
|
|
103
|
+
>>> import jax.numpy as jnp
|
|
104
|
+
>>> u.Quantity(jnp.array([1, 2, 3]), "m")
|
|
105
|
+
Quantity(Array([1, 2, 3], dtype=int32), unit='m')
|
|
106
|
+
|
|
107
|
+
The unit can also be given as a `astropy.units.Unit`:
|
|
108
|
+
|
|
109
|
+
>>> import astropy.units as apyu
|
|
110
|
+
>>> u.Quantity(1, apyu.m)
|
|
111
|
+
Quantity(Array(1, dtype=int32, ...), unit='m')
|
|
112
|
+
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
value: eqx.AbstractVar[Shaped[Array | StaticValue, "*shape"]]
|
|
116
|
+
"""The value of the `AbstractQuantity`."""
|
|
117
|
+
|
|
118
|
+
unit: eqx.AbstractVar[AbstractUnit]
|
|
119
|
+
"""The unit associated with this value."""
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------
|
|
122
|
+
# Constructors
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
@dispatch.abstract
|
|
126
|
+
def from_(
|
|
127
|
+
cls: "type[AbstractQuantity]",
|
|
128
|
+
*args: Any,
|
|
129
|
+
**kwargs: Any,
|
|
130
|
+
) -> "AbstractQuantity":
|
|
131
|
+
raise NotImplementedError # pragma: no cover
|
|
132
|
+
|
|
133
|
+
# See below for additional constructors
|
|
134
|
+
|
|
135
|
+
# ===============================================================
|
|
136
|
+
# Quantity API
|
|
137
|
+
|
|
138
|
+
def uconvert(self, u: Any, /) -> "AbstractQuantity":
|
|
139
|
+
"""Convert the quantity to the given units.
|
|
140
|
+
|
|
141
|
+
See Also
|
|
142
|
+
--------
|
|
143
|
+
`unxt.uconvert` : convert a quantity to a new unit.
|
|
144
|
+
|
|
145
|
+
Examples
|
|
146
|
+
--------
|
|
147
|
+
>>> import unxt as u
|
|
148
|
+
|
|
149
|
+
>>> q = u.Quantity(1, "m")
|
|
150
|
+
>>> q.uconvert("cm")
|
|
151
|
+
Quantity(Array(100., dtype=float32, ...), unit='cm')
|
|
152
|
+
|
|
153
|
+
"""
|
|
154
|
+
return uapi.uconvert(u, self)
|
|
155
|
+
|
|
156
|
+
def ustrip(self, u: Any, /) -> Array:
|
|
157
|
+
"""Return the value in the given units.
|
|
158
|
+
|
|
159
|
+
See Also
|
|
160
|
+
--------
|
|
161
|
+
`unxt.ustrip` : strip the units from a quantity.
|
|
162
|
+
|
|
163
|
+
Examples
|
|
164
|
+
--------
|
|
165
|
+
>>> import unxt as u
|
|
166
|
+
|
|
167
|
+
>>> q = u.Quantity(1, "m")
|
|
168
|
+
>>> q.ustrip("cm")
|
|
169
|
+
Array(100., dtype=float32, weak_type=True)
|
|
170
|
+
|
|
171
|
+
"""
|
|
172
|
+
return uapi.ustrip(u, self)
|
|
173
|
+
|
|
174
|
+
# ===============================================================
|
|
175
|
+
# Quax API
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def shape(self) -> tuple[int, ...]:
|
|
179
|
+
"""Shape of the array."""
|
|
180
|
+
return self.value.shape
|
|
181
|
+
|
|
182
|
+
def materialise(self) -> NoReturn:
|
|
183
|
+
msg = f"Refusing to materialise `{type(self).__name__}`."
|
|
184
|
+
raise RuntimeError(msg)
|
|
185
|
+
|
|
186
|
+
def aval(self) -> jax.core.ShapedArray:
|
|
187
|
+
value = self.value
|
|
188
|
+
if isinstance(value, StaticValue):
|
|
189
|
+
value = jnp.asarray(value.array)
|
|
190
|
+
return jax.typeof(value)
|
|
191
|
+
|
|
192
|
+
def enable_materialise(self, _: bool = True) -> "Self": # noqa: FBT001, FBT002
|
|
193
|
+
return replace(self, value=self.value, unit=self.unit)
|
|
194
|
+
|
|
195
|
+
# ===============================================================
|
|
196
|
+
# Plum API
|
|
197
|
+
|
|
198
|
+
#: This tells `plum` that this type can be efficiently cached.
|
|
199
|
+
__faithful__: ClassVar[bool] = True
|
|
200
|
+
|
|
201
|
+
# ===============================================================
|
|
202
|
+
# Array API
|
|
203
|
+
|
|
204
|
+
def __array_namespace__(self, *, api_version: Any = None) -> ModuleType:
|
|
205
|
+
"""Return the namespace for the array API.
|
|
206
|
+
|
|
207
|
+
Here we return the `quaxed.numpy` module, which is a drop-in replacement
|
|
208
|
+
for `jax.numpy`, but allows for array-ish objects to be used in place of
|
|
209
|
+
`jax` arrays. See `quax` for more information.
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
return jnp # quaxed.numpy
|
|
213
|
+
|
|
214
|
+
# ---------------------------------------------------------------
|
|
215
|
+
# attributes
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def dtype(self) -> jax.numpy.dtype:
|
|
219
|
+
"""Data type of the array.
|
|
220
|
+
|
|
221
|
+
Examples
|
|
222
|
+
--------
|
|
223
|
+
>>> import unxt as u
|
|
224
|
+
>>> u.Quantity(1, "m").dtype
|
|
225
|
+
dtype('int32')
|
|
226
|
+
|
|
227
|
+
"""
|
|
228
|
+
return self.value.dtype
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def device(self) -> jax.Device:
|
|
232
|
+
"""Device where the array is located.
|
|
233
|
+
|
|
234
|
+
Examples
|
|
235
|
+
--------
|
|
236
|
+
>>> import unxt as u
|
|
237
|
+
>>> u.Quantity(1, "m").device
|
|
238
|
+
CpuDevice(id=0)
|
|
239
|
+
|
|
240
|
+
"""
|
|
241
|
+
return self.value.devices().pop()
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def mT(self) -> "AbstractQuantity": # noqa: N802
|
|
245
|
+
"""Matrix transpose of the array.
|
|
246
|
+
|
|
247
|
+
Examples
|
|
248
|
+
--------
|
|
249
|
+
>>> import unxt as u
|
|
250
|
+
>>> q = u.Quantity([[0, 1], [1, 2]], "m")
|
|
251
|
+
>>> q.mT
|
|
252
|
+
Quantity(Array([[0, 1],
|
|
253
|
+
[1, 2]], dtype=int32), unit='m')
|
|
254
|
+
|
|
255
|
+
"""
|
|
256
|
+
return replace(self, value=jnp.matrix_transpose(self.value))
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def ndim(self) -> int:
|
|
260
|
+
"""Number of dimensions.
|
|
261
|
+
|
|
262
|
+
Examples
|
|
263
|
+
--------
|
|
264
|
+
>>> import unxt as u
|
|
265
|
+
>>> q = u.Quantity([[1]], "m")
|
|
266
|
+
>>> q.ndim
|
|
267
|
+
2
|
|
268
|
+
|
|
269
|
+
"""
|
|
270
|
+
return self.value.ndim
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def size(self) -> int:
|
|
274
|
+
"""Total number of elements.
|
|
275
|
+
|
|
276
|
+
Examples
|
|
277
|
+
--------
|
|
278
|
+
>>> import unxt as u
|
|
279
|
+
>>> q = u.Quantity([1, 2, 3], "m")
|
|
280
|
+
>>> q.size
|
|
281
|
+
3
|
|
282
|
+
|
|
283
|
+
"""
|
|
284
|
+
return self.value.size
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def T(self) -> "AbstractQuantity": # noqa: N802
|
|
288
|
+
"""Transpose of the array.
|
|
289
|
+
|
|
290
|
+
Examples
|
|
291
|
+
--------
|
|
292
|
+
>>> import unxt as u
|
|
293
|
+
>>> q = u.Quantity([[0, 1], [1, 2]], "m")
|
|
294
|
+
>>> q.T
|
|
295
|
+
Quantity(Array([[0, 1],
|
|
296
|
+
[1, 2]], dtype=int32), unit='m')
|
|
297
|
+
|
|
298
|
+
"""
|
|
299
|
+
return replace(self, value=self.value.T)
|
|
300
|
+
|
|
301
|
+
# required to override mixin methods
|
|
302
|
+
__eq__ = quax_blocks.NumpyEqMixin.__eq__ # type: ignore[assignment,unused-ignore]
|
|
303
|
+
|
|
304
|
+
# ---------------------------------------------------------------
|
|
305
|
+
# methods
|
|
306
|
+
|
|
307
|
+
def __bool__(self) -> bool:
|
|
308
|
+
"""Convert a zero-dimensional array to a Python bool object.
|
|
309
|
+
|
|
310
|
+
Examples
|
|
311
|
+
--------
|
|
312
|
+
>>> import unxt as u
|
|
313
|
+
>>> q = u.Quantity([0, 1], "m")
|
|
314
|
+
|
|
315
|
+
>>> bool(q[0])
|
|
316
|
+
False
|
|
317
|
+
|
|
318
|
+
>>> bool(q[1])
|
|
319
|
+
True
|
|
320
|
+
|
|
321
|
+
"""
|
|
322
|
+
return bool(self.value)
|
|
323
|
+
|
|
324
|
+
def __complex__(self) -> complex:
|
|
325
|
+
"""Convert the array to a Python complex object.
|
|
326
|
+
|
|
327
|
+
Examples
|
|
328
|
+
--------
|
|
329
|
+
>>> import unxt as u
|
|
330
|
+
>>> q = u.Quantity(1, "")
|
|
331
|
+
>>> complex(q)
|
|
332
|
+
(1+0j)
|
|
333
|
+
|
|
334
|
+
"""
|
|
335
|
+
return complex(uapi.ustrip("", self))
|
|
336
|
+
|
|
337
|
+
def __dlpack__(self, *args: Any, **kwargs: Any) -> Any:
|
|
338
|
+
"""Export the array for consumption as a DLPack capsule."""
|
|
339
|
+
raise NotImplementedError
|
|
340
|
+
|
|
341
|
+
def __dlpack_device__(self, *args: Any, **kwargs: Any) -> Any:
|
|
342
|
+
"""Return device type and device ID in DLPack format."""
|
|
343
|
+
raise NotImplementedError
|
|
344
|
+
|
|
345
|
+
def __float__(self) -> float:
|
|
346
|
+
"""Convert the array to a Python float object.
|
|
347
|
+
|
|
348
|
+
Examples
|
|
349
|
+
--------
|
|
350
|
+
>>> import unxt as u
|
|
351
|
+
>>> q = u.Quantity(1, "")
|
|
352
|
+
>>> float(q)
|
|
353
|
+
1.0
|
|
354
|
+
|
|
355
|
+
"""
|
|
356
|
+
return float(uapi.ustrip("", self))
|
|
357
|
+
|
|
358
|
+
def __getitem__(self, key: Any) -> "AbstractQuantity":
|
|
359
|
+
"""Get an item from the array.
|
|
360
|
+
|
|
361
|
+
This is a simple wrapper around the `__getitem__` method of the array,
|
|
362
|
+
calling `replace` to only update the value.
|
|
363
|
+
|
|
364
|
+
"""
|
|
365
|
+
return replace(self, value=self.value[key])
|
|
366
|
+
|
|
367
|
+
def __index__(self) -> int:
|
|
368
|
+
"""Convert a zero-dimensional integer array to a Python int object.
|
|
369
|
+
|
|
370
|
+
Examples
|
|
371
|
+
--------
|
|
372
|
+
>>> import unxt as u
|
|
373
|
+
>>> q = u.Quantity(1, "")
|
|
374
|
+
>>> q.__index__()
|
|
375
|
+
1
|
|
376
|
+
|
|
377
|
+
"""
|
|
378
|
+
return uapi.ustrip("", self).__index__()
|
|
379
|
+
|
|
380
|
+
def __int__(self) -> int:
|
|
381
|
+
"""Convert a zero-dimensional array to a Python int object.
|
|
382
|
+
|
|
383
|
+
Examples
|
|
384
|
+
--------
|
|
385
|
+
>>> import unxt as u
|
|
386
|
+
>>> q = u.Quantity(1, "")
|
|
387
|
+
>>> int(q)
|
|
388
|
+
1
|
|
389
|
+
|
|
390
|
+
"""
|
|
391
|
+
return int(uapi.ustrip("", self))
|
|
392
|
+
|
|
393
|
+
def __setitem__(self, key: Any, value: Any) -> None:
|
|
394
|
+
"""Set an item in the array.
|
|
395
|
+
|
|
396
|
+
This is a simple wrapper around the `__setitem__` method of the array.
|
|
397
|
+
|
|
398
|
+
Examples
|
|
399
|
+
--------
|
|
400
|
+
>>> import unxt as u
|
|
401
|
+
>>> q = u.Quantity([1, 2, 3], "m")
|
|
402
|
+
>>> try:
|
|
403
|
+
... q[0] = 2
|
|
404
|
+
... except Exception as e:
|
|
405
|
+
... print("jax arrays do not support in-place item assignment")
|
|
406
|
+
jax arrays do not support in-place item assignment
|
|
407
|
+
|
|
408
|
+
"""
|
|
409
|
+
self.value[key] = value
|
|
410
|
+
|
|
411
|
+
def to_device(self, device: None | jax.Device = None) -> "AbstractQuantity":
|
|
412
|
+
"""Move the array to a new device.
|
|
413
|
+
|
|
414
|
+
Examples
|
|
415
|
+
--------
|
|
416
|
+
>>> import unxt as u
|
|
417
|
+
>>> q = u.Quantity(1, "m")
|
|
418
|
+
>>> q.to_device(None)
|
|
419
|
+
Quantity(Array(1, dtype=int32, weak_type=True), unit='m')
|
|
420
|
+
|
|
421
|
+
"""
|
|
422
|
+
return replace(self, value=self.value.to_device(device))
|
|
423
|
+
|
|
424
|
+
# ===============================================================
|
|
425
|
+
# JAX API
|
|
426
|
+
|
|
427
|
+
def __iter__(self) -> Any:
|
|
428
|
+
"""Iterate over the Quantity's value.
|
|
429
|
+
|
|
430
|
+
Examples
|
|
431
|
+
--------
|
|
432
|
+
>>> import unxt as u
|
|
433
|
+
>>> q = u.Quantity([1, 2, 3], "m")
|
|
434
|
+
>>> [x for x in q]
|
|
435
|
+
[Quantity(Array(1, dtype=int32), unit='m'),
|
|
436
|
+
Quantity(Array(2, dtype=int32), unit='m'),
|
|
437
|
+
Quantity(Array(3, dtype=int32), unit='m')]
|
|
438
|
+
|
|
439
|
+
"""
|
|
440
|
+
yield from (self[i] for i in range(len(self.value)))
|
|
441
|
+
|
|
442
|
+
def argmax(self, *args: Any, **kwargs: Any) -> Array:
|
|
443
|
+
"""Return the indices of the maximum value.
|
|
444
|
+
|
|
445
|
+
Examples
|
|
446
|
+
--------
|
|
447
|
+
>>> import unxt as u
|
|
448
|
+
>>> q = u.Quantity([1, 2, 3], "m")
|
|
449
|
+
>>> q.argmax()
|
|
450
|
+
Array(2, dtype=int32)
|
|
451
|
+
|
|
452
|
+
"""
|
|
453
|
+
return self.value.argmax(*args, **kwargs)
|
|
454
|
+
|
|
455
|
+
def argmin(self, *args: Any, **kwargs: Any) -> Array:
|
|
456
|
+
"""Return the indices of the minimum value.
|
|
457
|
+
|
|
458
|
+
Examples
|
|
459
|
+
--------
|
|
460
|
+
>>> import unxt as u
|
|
461
|
+
>>> q = u.Quantity([1, 2, 3], "m")
|
|
462
|
+
>>> q.argmin()
|
|
463
|
+
Array(0, dtype=int32)
|
|
464
|
+
|
|
465
|
+
"""
|
|
466
|
+
return self.value.argmin(*args, **kwargs)
|
|
467
|
+
|
|
468
|
+
def astype(self, *args: Any, **kwargs: Any) -> "AbstractQuantity":
|
|
469
|
+
"""Copy the array and cast to a specified dtype.
|
|
470
|
+
|
|
471
|
+
Examples
|
|
472
|
+
--------
|
|
473
|
+
>>> import unxt as u
|
|
474
|
+
>>> q = u.Quantity([1, 2, 3], "m")
|
|
475
|
+
>>> q.dtype
|
|
476
|
+
dtype('int32')
|
|
477
|
+
|
|
478
|
+
>>> q.astype(float)
|
|
479
|
+
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
|
|
480
|
+
|
|
481
|
+
"""
|
|
482
|
+
return replace(self, value=self.value.astype(*args, **kwargs))
|
|
483
|
+
|
|
484
|
+
@ft.partial(property, doc=jax.Array.at.__doc__)
|
|
485
|
+
def at(self) -> "_QuantityIndexUpdateHelper":
|
|
486
|
+
return _QuantityIndexUpdateHelper(self) # type: ignore[arg-type]
|
|
487
|
+
|
|
488
|
+
def block_until_ready(self) -> "AbstractQuantity":
|
|
489
|
+
"""Block until the array is ready.
|
|
490
|
+
|
|
491
|
+
Examples
|
|
492
|
+
--------
|
|
493
|
+
>>> import unxt as u
|
|
494
|
+
>>> q = u.Quantity(1, "m")
|
|
495
|
+
>>> q.block_until_ready() is q
|
|
496
|
+
True
|
|
497
|
+
|
|
498
|
+
"""
|
|
499
|
+
_ = self.value.block_until_ready()
|
|
500
|
+
return self
|
|
501
|
+
|
|
502
|
+
def devices(self) -> set[jax.Device]:
|
|
503
|
+
"""Return the devices where the array is located.
|
|
504
|
+
|
|
505
|
+
Examples
|
|
506
|
+
--------
|
|
507
|
+
>>> import unxt as u
|
|
508
|
+
>>> q = u.Quantity(1, "m")
|
|
509
|
+
>>> q.devices()
|
|
510
|
+
{CpuDevice(id=0)}
|
|
511
|
+
|
|
512
|
+
"""
|
|
513
|
+
return self.value.devices()
|
|
514
|
+
|
|
515
|
+
def flatten(self) -> "AbstractQuantity":
|
|
516
|
+
"""Return a flattened version of the array.
|
|
517
|
+
|
|
518
|
+
Examples
|
|
519
|
+
--------
|
|
520
|
+
>>> import unxt as u
|
|
521
|
+
>>> q = u.Quantity([[1, 2], [3, 4]], "m")
|
|
522
|
+
>>> q.flatten()
|
|
523
|
+
Quantity(Array([1, 2, 3, 4], dtype=int32), unit='m')
|
|
524
|
+
|
|
525
|
+
"""
|
|
526
|
+
return replace(self, value=self.value.flatten())
|
|
527
|
+
|
|
528
|
+
def max(self, *args: Any, **kwargs: Any) -> "AbstractQuantity":
|
|
529
|
+
"""Return the maximum value.
|
|
530
|
+
|
|
531
|
+
Examples
|
|
532
|
+
--------
|
|
533
|
+
>>> import unxt as u
|
|
534
|
+
>>> q = u.Quantity([1, 2, 3], "m")
|
|
535
|
+
>>> q.max()
|
|
536
|
+
Quantity(Array(3, dtype=int32), unit='m')
|
|
537
|
+
|
|
538
|
+
"""
|
|
539
|
+
return replace(self, value=self.value.max(*args, **kwargs))
|
|
540
|
+
|
|
541
|
+
def mean(self, *args: Any, **kwargs: Any) -> "AbstractQuantity":
|
|
542
|
+
"""Return the mean value.
|
|
543
|
+
|
|
544
|
+
Examples
|
|
545
|
+
--------
|
|
546
|
+
>>> import unxt as u
|
|
547
|
+
>>> q = u.Quantity([1, 2, 3], "m")
|
|
548
|
+
>>> q.mean()
|
|
549
|
+
Quantity(Array(2., dtype=float32), unit='m')
|
|
550
|
+
|
|
551
|
+
"""
|
|
552
|
+
return replace(self, value=self.value.mean(*args, **kwargs))
|
|
553
|
+
|
|
554
|
+
def min(self, *args: Any, **kwargs: Any) -> "AbstractQuantity":
|
|
555
|
+
"""Return the minimum value.
|
|
556
|
+
|
|
557
|
+
Examples
|
|
558
|
+
--------
|
|
559
|
+
>>> import unxt as u
|
|
560
|
+
>>> q = u.Quantity([1, 2, 3], "m")
|
|
561
|
+
>>> q.min()
|
|
562
|
+
Quantity(Array(1, dtype=int32), unit='m')
|
|
563
|
+
|
|
564
|
+
"""
|
|
565
|
+
return replace(self, value=self.value.min(*args, **kwargs))
|
|
566
|
+
|
|
567
|
+
def ravel(self) -> "AbstractQuantity":
|
|
568
|
+
"""Return a flattened version of the array.
|
|
569
|
+
|
|
570
|
+
Examples
|
|
571
|
+
--------
|
|
572
|
+
>>> import unxt as u
|
|
573
|
+
>>> q = u.Quantity([[1, 2], [3, 4]], "m")
|
|
574
|
+
>>> q.ravel()
|
|
575
|
+
Quantity(Array([1, 2, 3, 4], dtype=int32), unit='m')
|
|
576
|
+
|
|
577
|
+
"""
|
|
578
|
+
return replace(self, value=self.value.ravel())
|
|
579
|
+
|
|
580
|
+
def reshape(self, *args: Any, order: str = "C") -> "AbstractQuantity":
|
|
581
|
+
"""Return a reshaped version of the array.
|
|
582
|
+
|
|
583
|
+
Examples
|
|
584
|
+
--------
|
|
585
|
+
>>> import unxt as u
|
|
586
|
+
>>> q = u.Quantity([1, 2, 3, 4], "m")
|
|
587
|
+
>>> q.reshape(2, 2)
|
|
588
|
+
Quantity(Array([[1, 2],
|
|
589
|
+
[3, 4]], dtype=int32), unit='m')
|
|
590
|
+
|
|
591
|
+
"""
|
|
592
|
+
__tracebackhide__ = True # pylint: disable=unused-variable
|
|
593
|
+
return replace(self, value=self.value.reshape(*args, order=order))
|
|
594
|
+
|
|
595
|
+
def round(self, *args: Any, **kwargs: Any) -> "AbstractQuantity":
|
|
596
|
+
"""Round the array to the given number of decimals.
|
|
597
|
+
|
|
598
|
+
Examples
|
|
599
|
+
--------
|
|
600
|
+
>>> import unxt as u
|
|
601
|
+
>>> q = u.Quantity([1.1, 2.2, 3.3], "m")
|
|
602
|
+
>>> q.round(0)
|
|
603
|
+
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
|
|
604
|
+
|
|
605
|
+
"""
|
|
606
|
+
return replace(self, value=self.value.round(*args, **kwargs))
|
|
607
|
+
|
|
608
|
+
@property
|
|
609
|
+
def sharding(self) -> Any:
|
|
610
|
+
"""Return the sharding configuration of the array.
|
|
611
|
+
|
|
612
|
+
Examples
|
|
613
|
+
--------
|
|
614
|
+
>>> import unxt as u
|
|
615
|
+
>>> q = u.Quantity([1, 2, 3], "m")
|
|
616
|
+
>>> q.sharding
|
|
617
|
+
SingleDeviceSharding(device=..., memory_kind=...)
|
|
618
|
+
|
|
619
|
+
"""
|
|
620
|
+
return self.value.sharding
|
|
621
|
+
|
|
622
|
+
def squeeze(self, *args: Any, **kwargs: Any) -> "AbstractQuantity":
|
|
623
|
+
"""Return the array with all single-dimensional entries removed.
|
|
624
|
+
|
|
625
|
+
Examples
|
|
626
|
+
--------
|
|
627
|
+
>>> import unxt as u
|
|
628
|
+
>>> q = u.Quantity([[[1], [2], [3]]], "m")
|
|
629
|
+
>>> q.squeeze()
|
|
630
|
+
Quantity(Array([1, 2, 3], dtype=int32), unit='m')
|
|
631
|
+
|
|
632
|
+
"""
|
|
633
|
+
return replace(self, value=self.value.squeeze(*args, **kwargs))
|
|
634
|
+
|
|
635
|
+
# ===============================================================
|
|
636
|
+
# Python stuff
|
|
637
|
+
|
|
638
|
+
def __hash__(self) -> int:
|
|
639
|
+
"""Return the hash of the quantity.
|
|
640
|
+
|
|
641
|
+
This is the hash of the value and the unit, however since the value is
|
|
642
|
+
generally not hashable this will generally raise an exception.
|
|
643
|
+
Defining the `__hash__` method is required for the `AbstractQuantity` to
|
|
644
|
+
be considered immutable, e.g. by `dataclasses.dataclass`.
|
|
645
|
+
|
|
646
|
+
Examples
|
|
647
|
+
--------
|
|
648
|
+
>>> import unxt as u
|
|
649
|
+
>>> q = u.Quantity(1, "m")
|
|
650
|
+
>>> try:
|
|
651
|
+
... hash(q)
|
|
652
|
+
... except TypeError as e:
|
|
653
|
+
... print(e)
|
|
654
|
+
unhashable type: 'jaxlib...ArrayImpl'
|
|
655
|
+
|
|
656
|
+
"""
|
|
657
|
+
return hash((self.value, self.unit))
|
|
658
|
+
|
|
659
|
+
def __pdoc__(
|
|
660
|
+
self,
|
|
661
|
+
*,
|
|
662
|
+
named_unit: bool = True,
|
|
663
|
+
short_arrays: bool | Literal["compact"] = True,
|
|
664
|
+
use_short_name: bool = False,
|
|
665
|
+
**kwargs: Any,
|
|
666
|
+
) -> wl.AbstractDoc:
|
|
667
|
+
"""Return the Wadler-Lindig representation of this class.
|
|
668
|
+
|
|
669
|
+
This is used for the `__repr__` and `__str__` methods or when using the
|
|
670
|
+
`wadler_lindig` library.
|
|
671
|
+
|
|
672
|
+
Parameters
|
|
673
|
+
----------
|
|
674
|
+
named_unit
|
|
675
|
+
If `True`, the unit is included in the representation as a named
|
|
676
|
+
argument. If `False`, the unit is included as a positional argument.
|
|
677
|
+
For example, ``Quantity(<array>, unit='m')`` versus
|
|
678
|
+
``Quantity(<array>, 'm')``.
|
|
679
|
+
short_arrays
|
|
680
|
+
If 'compact', the array is printed in a compact form,
|
|
681
|
+
``Quantity(Array([1., 2.], dtype=float32), unit='')`` to
|
|
682
|
+
``Quantity([1., 2.], unit='')``. If `True` or `False` it uses the
|
|
683
|
+
default `wadler_lindig` behavior.
|
|
684
|
+
use_short_name
|
|
685
|
+
If `True` and the class has a ``short_name`` class variable, use the
|
|
686
|
+
short name instead of the full class name. For example, ``Q(...)``
|
|
687
|
+
instead of ``Quantity(...)``.
|
|
688
|
+
kwargs
|
|
689
|
+
Additional keyword arguments ``wadler_lindig.pdoc`` method for
|
|
690
|
+
formatting the value, stringified unit, and other fields.
|
|
691
|
+
|
|
692
|
+
Examples
|
|
693
|
+
--------
|
|
694
|
+
>>> import unxt as u
|
|
695
|
+
>>> import wadler_lindig as wl
|
|
696
|
+
|
|
697
|
+
>>> q = u.quantity.BareQuantity([1, 2, 3], "m")
|
|
698
|
+
|
|
699
|
+
The default pretty printing:
|
|
700
|
+
|
|
701
|
+
>>> wl.pprint(q)
|
|
702
|
+
BareQuantity(i32[3], unit='m')
|
|
703
|
+
|
|
704
|
+
The `str` method uses this as well:
|
|
705
|
+
|
|
706
|
+
>>> print(q)
|
|
707
|
+
BareQuantity(i32[3], unit='m')
|
|
708
|
+
|
|
709
|
+
Arrays can be printed in full:
|
|
710
|
+
|
|
711
|
+
>>> wl.pprint(q, short_arrays=False)
|
|
712
|
+
BareQuantity(Array([1, 2, 3], dtype=int32), unit='m')
|
|
713
|
+
|
|
714
|
+
The `repr` method uses this setting:
|
|
715
|
+
|
|
716
|
+
>>> print(repr(q))
|
|
717
|
+
BareQuantity(Array([1, 2, 3], dtype=int32), unit='m')
|
|
718
|
+
|
|
719
|
+
The units can be turned from a named argument to a positional argument
|
|
720
|
+
by setting `named_unit=False`:
|
|
721
|
+
|
|
722
|
+
>>> wl.pprint(q, named_unit=False)
|
|
723
|
+
BareQuantity(i32[3], 'm')
|
|
724
|
+
|
|
725
|
+
The arrays can be printed in a compact but informative form by setting
|
|
726
|
+
``short_arrays="compact"``:
|
|
727
|
+
|
|
728
|
+
>>> wl.pprint(q, short_arrays="compact")
|
|
729
|
+
BareQuantity([1, 2, 3], unit='m')
|
|
730
|
+
|
|
731
|
+
The class short name can be used if available:
|
|
732
|
+
|
|
733
|
+
>>> wl.pprint(q, use_short_name=True)
|
|
734
|
+
BareQuantity(i32[3], unit='m')
|
|
735
|
+
|
|
736
|
+
"""
|
|
737
|
+
# Class Name
|
|
738
|
+
if use_short_name and getattr(self, "short_name", None) is not None:
|
|
739
|
+
cls_name = wl.TextDoc(self.short_name)
|
|
740
|
+
else:
|
|
741
|
+
cls_name = wl.TextDoc(type_nonparametric(self).__name__)
|
|
742
|
+
|
|
743
|
+
# Object fields
|
|
744
|
+
fs = dict(field_items(self))
|
|
745
|
+
del fs["value"]
|
|
746
|
+
del fs["unit"]
|
|
747
|
+
|
|
748
|
+
# Customize value representation
|
|
749
|
+
# (backward compatibility for `compact_arrays` argument)
|
|
750
|
+
# TODO: remove in v2.0
|
|
751
|
+
if "compact_arrays" in kwargs:
|
|
752
|
+
short_arrays = kwargs.pop("compact_arrays")
|
|
753
|
+
warnings.warn(
|
|
754
|
+
"`compact_arrays` argument is deprecated; use "
|
|
755
|
+
"`short_arrays='compact'` instead.",
|
|
756
|
+
DeprecationWarning,
|
|
757
|
+
stacklevel=2,
|
|
758
|
+
)
|
|
759
|
+
match short_arrays:
|
|
760
|
+
case "compact":
|
|
761
|
+
kwargs["custom"] = custom_pdoc_noarray
|
|
762
|
+
case True:
|
|
763
|
+
kwargs["custom"] = custom_pdoc_no_kind
|
|
764
|
+
kwargs["short_arrays"] = True
|
|
765
|
+
case False:
|
|
766
|
+
kwargs["short_arrays"] = False
|
|
767
|
+
|
|
768
|
+
# Make the pdocs for the base and extra fields.
|
|
769
|
+
base_fields = [
|
|
770
|
+
wl.pdoc(self.value, **kwargs),
|
|
771
|
+
wl.TextDoc("unit=" if named_unit else "")
|
|
772
|
+
+ wl.pdoc(self.unit.to_string(), **kwargs),
|
|
773
|
+
]
|
|
774
|
+
extra_fields = wl.named_objs(tuple(fs.items()), **kwargs)
|
|
775
|
+
|
|
776
|
+
# Construct and return the Wadler-Lindig document.
|
|
777
|
+
return (
|
|
778
|
+
cls_name
|
|
779
|
+
+ wl.TextDoc("(")
|
|
780
|
+
+ wl.join(wl.comma, base_fields + extra_fields)
|
|
781
|
+
.group()
|
|
782
|
+
.nest(kwargs.get("indent", 4))
|
|
783
|
+
+ wl.TextDoc(")")
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
def __repr__(self) -> str:
|
|
787
|
+
# TODO: make named_unit False?
|
|
788
|
+
return wl.pformat(self, short_arrays=False, named_unit=True, indent=4)
|
|
789
|
+
|
|
790
|
+
def __str__(self) -> str:
|
|
791
|
+
# TODO: make named_unit False?
|
|
792
|
+
return wl.pformat(self, short_arrays=True, named_unit=True, indent=4)
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
# -----------------------------------------------
|
|
796
|
+
# Register additional constructors
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
@AbstractQuantity.from_.dispatch
|
|
800
|
+
def from_(
|
|
801
|
+
cls: type[AbstractQuantity],
|
|
802
|
+
value: ArrayLike | ArrayLikeSequence,
|
|
803
|
+
unit: Any,
|
|
804
|
+
/,
|
|
805
|
+
*,
|
|
806
|
+
dtype: Any = None,
|
|
807
|
+
) -> AbstractQuantity:
|
|
808
|
+
"""Construct a `unxt.Quantity` from an array-like value and a unit.
|
|
809
|
+
|
|
810
|
+
:param value: The array-like value.
|
|
811
|
+
:param unit: The unit of the value.
|
|
812
|
+
:param dtype: The data type of the array (keyword-only).
|
|
813
|
+
|
|
814
|
+
Examples
|
|
815
|
+
--------
|
|
816
|
+
For this example we'll use the `Quantity` class. The same applies to
|
|
817
|
+
any subclass of `AbstractQuantity`.
|
|
818
|
+
|
|
819
|
+
>>> import jax.numpy as jnp
|
|
820
|
+
>>> import unxt as u
|
|
821
|
+
|
|
822
|
+
>>> x = jnp.array([1.0, 2, 3])
|
|
823
|
+
>>> u.Quantity.from_(x, "m")
|
|
824
|
+
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
|
|
825
|
+
|
|
826
|
+
>>> u.Quantity.from_([1.0, 2, 3], "m")
|
|
827
|
+
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
|
|
828
|
+
|
|
829
|
+
>>> u.Quantity.from_((1.0, 2, 3), "m")
|
|
830
|
+
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
|
|
831
|
+
|
|
832
|
+
"""
|
|
833
|
+
# Dispatch on both arguments.
|
|
834
|
+
# Construct using the standard `__init__` method.
|
|
835
|
+
return cls(jnp.asarray(value, dtype=dtype), unit)
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
@AbstractQuantity.from_.dispatch
|
|
839
|
+
def from_(
|
|
840
|
+
cls: type[AbstractQuantity],
|
|
841
|
+
value: ArrayLike | ArrayLikeSequence,
|
|
842
|
+
/,
|
|
843
|
+
*,
|
|
844
|
+
unit: Any,
|
|
845
|
+
dtype: Any = None,
|
|
846
|
+
) -> AbstractQuantity:
|
|
847
|
+
"""Make a `unxt.AbstractQuantity` from an array-like value and a unit kwarg.
|
|
848
|
+
|
|
849
|
+
Examples
|
|
850
|
+
--------
|
|
851
|
+
For this example we'll use the `unxt.Quantity` class. The same applies
|
|
852
|
+
to any subclass of `unxt.AbstractQuantity`.
|
|
853
|
+
|
|
854
|
+
>>> import unxt as u
|
|
855
|
+
>>> u.Quantity.from_([1.0, 2, 3], unit="m")
|
|
856
|
+
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
|
|
857
|
+
|
|
858
|
+
"""
|
|
859
|
+
# Dispatch on the `value` only. Dispatch to the full constructor.
|
|
860
|
+
return cls.from_(value, unit, dtype=dtype)
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
@AbstractQuantity.from_.dispatch
|
|
864
|
+
def from_(
|
|
865
|
+
cls: type[AbstractQuantity], *, value: Any, unit: Any, dtype: Any = None
|
|
866
|
+
) -> AbstractQuantity:
|
|
867
|
+
"""Construct a `AbstractQuantity` from value and unit kwargs.
|
|
868
|
+
|
|
869
|
+
Examples
|
|
870
|
+
--------
|
|
871
|
+
For this example we'll use the `Quantity` class. The same applies to
|
|
872
|
+
any subclass of `AbstractQuantity`.
|
|
873
|
+
|
|
874
|
+
>>> import unxt as u
|
|
875
|
+
>>> u.Quantity.from_(value=[1.0, 2, 3], unit="m")
|
|
876
|
+
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
|
|
877
|
+
|
|
878
|
+
"""
|
|
879
|
+
# Dispatched on no argument. Dispatch to the full constructor.
|
|
880
|
+
return cls.from_(value, unit, dtype=dtype)
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
@AbstractQuantity.from_.dispatch
|
|
884
|
+
def from_(cls: type[AbstractQuantity], mapping: Mapping[str, Any]) -> AbstractQuantity:
|
|
885
|
+
"""Construct a `Quantity` from a Mapping.
|
|
886
|
+
|
|
887
|
+
Examples
|
|
888
|
+
--------
|
|
889
|
+
For this example we'll use the `Quantity` class. The same applies to
|
|
890
|
+
any subclass of `AbstractQuantity`.
|
|
891
|
+
|
|
892
|
+
>>> import jax.numpy as jnp
|
|
893
|
+
>>> import unxt as u
|
|
894
|
+
|
|
895
|
+
>>> x = jnp.array([1.0, 2, 3])
|
|
896
|
+
>>> q = u.Quantity.from_({"value": x, "unit": "m"})
|
|
897
|
+
>>> q
|
|
898
|
+
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
|
|
899
|
+
|
|
900
|
+
>>> u.Quantity.from_({"value": q, "unit": "km"})
|
|
901
|
+
Quantity(Array([0.001, 0.002, 0.003], dtype=float32), unit='km')
|
|
902
|
+
|
|
903
|
+
"""
|
|
904
|
+
# Dispatch on both arguments.
|
|
905
|
+
# Construct using the standard `__init__` method.
|
|
906
|
+
return cls.from_(**mapping)
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
@AbstractQuantity.from_.dispatch
|
|
910
|
+
def from_(
|
|
911
|
+
cls: type[AbstractQuantity],
|
|
912
|
+
value: AbstractQuantity,
|
|
913
|
+
unit: Any,
|
|
914
|
+
/,
|
|
915
|
+
*,
|
|
916
|
+
dtype: Any = None,
|
|
917
|
+
) -> AbstractQuantity:
|
|
918
|
+
"""Construct a `Quantity` from another `Quantity`.
|
|
919
|
+
|
|
920
|
+
The `value` is converted to the new `unit`.
|
|
921
|
+
|
|
922
|
+
Examples
|
|
923
|
+
--------
|
|
924
|
+
>>> import unxt as u
|
|
925
|
+
|
|
926
|
+
>>> q = u.Quantity(1, "m")
|
|
927
|
+
>>> u.Quantity.from_(q, "cm")
|
|
928
|
+
Quantity(Array(100., dtype=float32, ...), unit='cm')
|
|
929
|
+
|
|
930
|
+
"""
|
|
931
|
+
value = jnp.asarray(uapi.uconvert(unit, value), dtype=dtype)
|
|
932
|
+
return cls(uapi.ustrip(unit, value), unit)
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
@AbstractQuantity.from_.dispatch
|
|
936
|
+
def from_(
|
|
937
|
+
cls: type[AbstractQuantity],
|
|
938
|
+
value: AbstractQuantity,
|
|
939
|
+
unit: None,
|
|
940
|
+
/,
|
|
941
|
+
*,
|
|
942
|
+
dtype: Any = None,
|
|
943
|
+
) -> AbstractQuantity:
|
|
944
|
+
"""Construct a `Quantity` from another `Quantity`.
|
|
945
|
+
|
|
946
|
+
The `value` is converted to the new `unit`.
|
|
947
|
+
|
|
948
|
+
Examples
|
|
949
|
+
--------
|
|
950
|
+
>>> import unxt as u
|
|
951
|
+
|
|
952
|
+
>>> q = u.Quantity(1, "m")
|
|
953
|
+
>>> u.Quantity.from_(q, None)
|
|
954
|
+
Quantity(Array(1, dtype=int32, ...), unit='m')
|
|
955
|
+
|
|
956
|
+
"""
|
|
957
|
+
value = jnp.asarray(value, dtype=dtype)
|
|
958
|
+
unit = uapi.unit_of(value)
|
|
959
|
+
return cls(uapi.ustrip(unit, value), unit)
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
@AbstractQuantity.from_.dispatch
|
|
963
|
+
def from_(
|
|
964
|
+
cls: type[AbstractQuantity],
|
|
965
|
+
value: AbstractQuantity,
|
|
966
|
+
/,
|
|
967
|
+
*,
|
|
968
|
+
unit: Any | None = None,
|
|
969
|
+
dtype: Any = None,
|
|
970
|
+
) -> AbstractQuantity:
|
|
971
|
+
"""Construct a `Quantity` from another `Quantity`, with no unit change."""
|
|
972
|
+
unit = value.unit if unit is None else unit
|
|
973
|
+
value = jnp.asarray(uapi.uconvert(unit, value), dtype=dtype)
|
|
974
|
+
return cls(uapi.ustrip(unit, value), unit)
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
# -----------------------------------------------
|
|
978
|
+
# Promotion rules
|
|
979
|
+
|
|
980
|
+
add_promotion_rule(AbstractQuantity, AbstractQuantity, AbstractQuantity)
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
# ===============================================================
|
|
984
|
+
# Support for ``at``.
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
# `_QuantityIndexUpdateHelper` is defined up here because it is used in the
|
|
988
|
+
# runtime-checkable type annotation in `AbstractQuantity.at`.
|
|
989
|
+
# `_QuantityIndexUpdateRef` is defined after `AbstractQuantity` because it
|
|
990
|
+
# references `AbstractQuantity` in its runtime-checkable type annotations.
|
|
991
|
+
class _QuantityIndexUpdateHelper(_IndexUpdateHelper):
|
|
992
|
+
def __getitem__(self, index: Any) -> "_IndexUpdateRef":
|
|
993
|
+
return _QuantityIndexUpdateRef(self.array, index)
|
|
994
|
+
|
|
995
|
+
def __repr__(self) -> str:
|
|
996
|
+
"""Return a string representation of the object.
|
|
997
|
+
|
|
998
|
+
Examples
|
|
999
|
+
--------
|
|
1000
|
+
>>> import unxt as u
|
|
1001
|
+
>>> q = u.Quantity([1, 2, 3, 4], "m")
|
|
1002
|
+
>>> q.at
|
|
1003
|
+
_QuantityIndexUpdateHelper(Quantity(Array([1, 2, 3, 4], dtype=int32), unit='m'))
|
|
1004
|
+
|
|
1005
|
+
"""
|
|
1006
|
+
return f"_QuantityIndexUpdateHelper({self.array!r})"
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
class _QuantityIndexUpdateRef(_IndexUpdateRef):
|
|
1010
|
+
# This is a subclass of `_IndexUpdateRef` that is used to implement the `at`
|
|
1011
|
+
# attribute of `AbstractQuantity`. See also `_QuantityIndexUpdateHelper`.
|
|
1012
|
+
|
|
1013
|
+
array: AbstractQuantity # type: ignore[assignment]
|
|
1014
|
+
|
|
1015
|
+
def __repr__(self) -> str:
|
|
1016
|
+
return super().__repr__().replace("_IndexUpdateRef", "_QuantityIndexUpdateRef")
|
|
1017
|
+
|
|
1018
|
+
@override
|
|
1019
|
+
def get( # type: ignore[override]
|
|
1020
|
+
self, *, fill_value: AbstractQuantity | None = None, **kw: Any
|
|
1021
|
+
) -> AbstractQuantity:
|
|
1022
|
+
# TODO: by quaxified super
|
|
1023
|
+
value = self.array.value.at[self.index].get(
|
|
1024
|
+
fill_value=(
|
|
1025
|
+
fill_value
|
|
1026
|
+
if fill_value is None
|
|
1027
|
+
else uapi.ustrip(self.array.unit, fill_value)
|
|
1028
|
+
),
|
|
1029
|
+
**kw,
|
|
1030
|
+
)
|
|
1031
|
+
return replace(self.array, value=value)
|
|
1032
|
+
|
|
1033
|
+
@override
|
|
1034
|
+
def set(self, values: AbstractQuantity, **kw: Any) -> AbstractQuantity: # type: ignore[override]
|
|
1035
|
+
# TODO: by quaxified super
|
|
1036
|
+
value = self.array.value.at[self.index].set(
|
|
1037
|
+
uapi.ustrip(self.array.unit, values), **kw
|
|
1038
|
+
)
|
|
1039
|
+
return replace(self.array, value=value)
|
|
1040
|
+
|
|
1041
|
+
@override
|
|
1042
|
+
def apply(self, func: Any, **kw: Any) -> AbstractQuantity: # type: ignore[override]
|
|
1043
|
+
raise NotImplementedError # TODO: by quaxified super
|
|
1044
|
+
|
|
1045
|
+
@override
|
|
1046
|
+
def add(self, values: AbstractQuantity, **kw: Any) -> AbstractQuantity: # type: ignore[override]
|
|
1047
|
+
# TODO: by quaxified super
|
|
1048
|
+
value = self.array.value.at[self.index].add(
|
|
1049
|
+
uapi.ustrip(self.array.unit, values), **kw
|
|
1050
|
+
)
|
|
1051
|
+
return replace(self.array, value=value)
|
|
1052
|
+
|
|
1053
|
+
@override
|
|
1054
|
+
def multiply(self, values: ArrayLike, **kw: Any) -> AbstractQuantity: # type: ignore[override]
|
|
1055
|
+
values = eqx.error_if(
|
|
1056
|
+
values, isinstance(values, AbstractQuantity), "values cannot be a Quantity"
|
|
1057
|
+
) # TODO: can permit dimensionless quantities.
|
|
1058
|
+
|
|
1059
|
+
# TODO: by quaxified super
|
|
1060
|
+
value = self.array.value.at[self.index].multiply(values, **kw)
|
|
1061
|
+
return replace(self.array, value=value)
|
|
1062
|
+
|
|
1063
|
+
mul = multiply # type: ignore[assignment]
|
|
1064
|
+
|
|
1065
|
+
@override
|
|
1066
|
+
def divide(self, values: ArrayLike, **kw: Any) -> AbstractQuantity: # type: ignore[override]
|
|
1067
|
+
values = eqx.error_if(
|
|
1068
|
+
values, isinstance(values, AbstractQuantity), "values cannot be a Quantity"
|
|
1069
|
+
) # TODO: can permit dimensionless quantities.
|
|
1070
|
+
|
|
1071
|
+
# TODO: by quaxified super
|
|
1072
|
+
value = self.array.value.at[self.index].divide(values, **kw)
|
|
1073
|
+
return replace(self.array, value=value)
|
|
1074
|
+
|
|
1075
|
+
@override
|
|
1076
|
+
def power(self, values: ArrayLike, **kw: Any) -> AbstractQuantity: # type: ignore[override]
|
|
1077
|
+
raise NotImplementedError
|
|
1078
|
+
|
|
1079
|
+
@override
|
|
1080
|
+
def min(self, values: AbstractQuantity, **kw: Any) -> AbstractQuantity: # type: ignore[override]
|
|
1081
|
+
# TODO: by quaxified super
|
|
1082
|
+
value = self.array.value.at[self.index].min(
|
|
1083
|
+
uapi.ustrip(self.array.unit, values), **kw
|
|
1084
|
+
)
|
|
1085
|
+
return replace(self.array, value=value)
|
|
1086
|
+
|
|
1087
|
+
@override
|
|
1088
|
+
def max(self, values: AbstractQuantity, **kw: Any) -> AbstractQuantity: # type: ignore[override]
|
|
1089
|
+
# TODO: by quaxified super
|
|
1090
|
+
value = self.array.value.at[self.index].max(
|
|
1091
|
+
uapi.ustrip(self.array.unit, values), **kw
|
|
1092
|
+
)
|
|
1093
|
+
return replace(self.array, value=value)
|
|
1094
|
+
|
|
1095
|
+
|
|
1096
|
+
# This is public!
|
|
1097
|
+
def is_any_quantity(obj: Any, /) -> TypeGuard[AbstractQuantity]:
|
|
1098
|
+
"""Check if an object is an instance of `unxt.quantity.AbstractQuantity`.
|
|
1099
|
+
|
|
1100
|
+
Examples
|
|
1101
|
+
--------
|
|
1102
|
+
>>> import unxt as u
|
|
1103
|
+
|
|
1104
|
+
>>> q = u.Quantity(1, "m")
|
|
1105
|
+
>>> is_any_quantity(q)
|
|
1106
|
+
True
|
|
1107
|
+
|
|
1108
|
+
"""
|
|
1109
|
+
return isinstance(obj, AbstractQuantity)
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
# TODO: replace with `equinox.internal.TreeWLCustom` when available.
|
|
1113
|
+
def custom_pdoc_no_kind(obj: Any) -> wl.AbstractDoc | None:
|
|
1114
|
+
"""Return custom pdoc for ``AbstractQuantity`` objects."""
|
|
1115
|
+
if isinstance(obj, jax.Array):
|
|
1116
|
+
dtype = obj.dtype.name
|
|
1117
|
+
# Added in JAX 0.4.32 to `ShapeDtypeStruct`
|
|
1118
|
+
if getattr(obj, "weak_type", False):
|
|
1119
|
+
dtype = f"weak_{dtype}"
|
|
1120
|
+
return wl.array_summary(obj.shape, dtype, kind=None)
|
|
1121
|
+
return None
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
def custom_pdoc_noarray(obj: Any) -> wl.AbstractDoc | None:
|
|
1125
|
+
"""Return custom pdoc for ``AbstractQuantity`` objects."""
|
|
1126
|
+
if isinstance(obj, jax.Array):
|
|
1127
|
+
return wl.TextDoc(np.array2string(cast("np.ndarray", obj), separator=", "))
|
|
1128
|
+
return None
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
@StaticValue.from_.dispatch
|
|
1132
|
+
def from_(cls: StaticValue, value: AbstractQuantity, /) -> StaticValue:
|
|
1133
|
+
"""Disallow conversion of `AbstractQuantity` to a value."""
|
|
1134
|
+
msg = (
|
|
1135
|
+
f"Cannot convert '{type(value).__name__}' to a value. "
|
|
1136
|
+
"For a Quantity, use the `.from_` constructor instead."
|
|
1137
|
+
)
|
|
1138
|
+
raise TypeError(msg)
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
@dispatch
|
|
1142
|
+
def convert_to_quantity_value(obj: AbstractQuantity, /) -> NoReturn:
|
|
1143
|
+
"""Disallow conversion of `AbstractQuantity` to a value.
|
|
1144
|
+
|
|
1145
|
+
>>> import unxt as u
|
|
1146
|
+
>>> from unxt.quantity import convert_to_quantity_value
|
|
1147
|
+
|
|
1148
|
+
>>> try:
|
|
1149
|
+
... convert_to_quantity_value(u.Quantity(1, "m"))
|
|
1150
|
+
... except TypeError as e:
|
|
1151
|
+
... print(e)
|
|
1152
|
+
Cannot convert 'Quantity[PhysicalType('length')]' to a value.
|
|
1153
|
+
For a Quantity, use the `.from_` constructor instead.
|
|
1154
|
+
|
|
1155
|
+
"""
|
|
1156
|
+
msg = (
|
|
1157
|
+
f"Cannot convert '{type(obj).__name__}' to a value. "
|
|
1158
|
+
"For a Quantity, use the `.from_` constructor instead."
|
|
1159
|
+
)
|
|
1160
|
+
raise TypeError(msg)
|