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.

Files changed (52) hide show
  1. unxt/__init__.py +79 -0
  2. unxt/_interop/__init__.py +18 -0
  3. unxt/_interop/optional_deps.py +13 -0
  4. unxt/_interop/unxt_interop_astropy/__init__.py +5 -0
  5. unxt/_interop/unxt_interop_astropy/custom_types.py +12 -0
  6. unxt/_interop/unxt_interop_astropy/dimensions.py +53 -0
  7. unxt/_interop/unxt_interop_astropy/quantity.py +308 -0
  8. unxt/_interop/unxt_interop_astropy/units.py +111 -0
  9. unxt/_interop/unxt_interop_gala/__init__.py +5 -0
  10. unxt/_interop/unxt_interop_gala/unitsystems.py +108 -0
  11. unxt/_interop/unxt_interop_mpl/__init__.py +81 -0
  12. unxt/_src/__init__.py +6 -0
  13. unxt/_src/dimensions.py +400 -0
  14. unxt/_src/experimental.py +234 -0
  15. unxt/_src/quantity/__init__.py +24 -0
  16. unxt/_src/quantity/angle.py +58 -0
  17. unxt/_src/quantity/base.py +1160 -0
  18. unxt/_src/quantity/base_angle.py +89 -0
  19. unxt/_src/quantity/base_parametric.py +290 -0
  20. unxt/_src/quantity/flag.py +137 -0
  21. unxt/_src/quantity/mixins.py +238 -0
  22. unxt/_src/quantity/quantity.py +123 -0
  23. unxt/_src/quantity/register_api.py +383 -0
  24. unxt/_src/quantity/register_conversions.py +95 -0
  25. unxt/_src/quantity/register_dispatches.py +246 -0
  26. unxt/_src/quantity/register_primitives.py +5075 -0
  27. unxt/_src/quantity/static_quantity.py +90 -0
  28. unxt/_src/quantity/unchecked.py +45 -0
  29. unxt/_src/quantity/value.py +289 -0
  30. unxt/_src/units.py +103 -0
  31. unxt/_src/unitsystems/__init__.py +40 -0
  32. unxt/_src/unitsystems/base.py +258 -0
  33. unxt/_src/unitsystems/builtin.py +190 -0
  34. unxt/_src/unitsystems/builtin_dimensions.py +21 -0
  35. unxt/_src/unitsystems/compare.py +13 -0
  36. unxt/_src/unitsystems/core.py +307 -0
  37. unxt/_src/unitsystems/flags.py +80 -0
  38. unxt/_src/unitsystems/realizations.py +73 -0
  39. unxt/_src/unitsystems/utils.py +95 -0
  40. unxt/_src/utils.py +112 -0
  41. unxt/_version.py +34 -0
  42. unxt/_version.pyi +2 -0
  43. unxt/dims.py +19 -0
  44. unxt/py.typed +0 -0
  45. unxt/quantity.py +172 -0
  46. unxt/setup_package.py +60 -0
  47. unxt/units.py +19 -0
  48. unxt/unitsystems.py +67 -0
  49. unxt-0.1.dev463.dist-info/METADATA +533 -0
  50. unxt-0.1.dev463.dist-info/RECORD +52 -0
  51. unxt-0.1.dev463.dist-info/WHEEL +4 -0
  52. unxt-0.1.dev463.dist-info/licenses/LICENSE +29 -0
@@ -0,0 +1,89 @@
1
+ """Base classes for angular quantities."""
2
+
3
+ __all__ = ("AbstractAngle",)
4
+
5
+ import equinox as eqx
6
+ from jaxtyping import Array, Shaped
7
+ from plum import add_promotion_rule
8
+
9
+ import unxt_api as uapi
10
+ from .base import AbstractQuantity
11
+ from .quantity import Quantity
12
+ from .unchecked import BareQuantity
13
+ from unxt._src.dimensions import dimension, dimension_of
14
+ from unxt._src.quantity.value import StaticValue
15
+ from unxt.units import AbstractUnit
16
+
17
+ angle_dimension = dimension("angle")
18
+
19
+
20
+ class AbstractAngle(AbstractQuantity):
21
+ """Angular Quantity.
22
+
23
+ See Also
24
+ --------
25
+ `unxt.Angle` : a concrete implementation of this class.
26
+
27
+ Examples
28
+ --------
29
+ For this example, we will use the concrete implementation of
30
+ `unxt.AbstractAngle`, `unxt.Angle`.
31
+
32
+ >>> from unxt import Angle
33
+
34
+ >>> Angle(90, "deg")
35
+ Angle(Array(90, dtype=int32, ...), unit='deg')
36
+
37
+ Angles have to have dimensions of angle.
38
+
39
+ >>> try:
40
+ ... Angle(90, "m")
41
+ ... except ValueError as e:
42
+ ... print(e)
43
+ Angle must have units with angular dimensions.
44
+
45
+ """
46
+
47
+ value: eqx.AbstractVar[Shaped[Array | StaticValue, "*shape"]]
48
+ """The value of the `unxt.AbstractQuantity`."""
49
+
50
+ unit: eqx.AbstractVar[AbstractUnit]
51
+ """The unit associated with this value."""
52
+
53
+ def __check_init__(self) -> None:
54
+ """Check the initialization."""
55
+ if dimension_of(self) != angle_dimension:
56
+ msg = f"{type(self).__name__} must have units with angular dimensions."
57
+ raise ValueError(msg)
58
+
59
+ def wrap_to(
60
+ self, /, min: AbstractQuantity, max: AbstractQuantity
61
+ ) -> "AbstractAngle":
62
+ """Wrap the angle to the range [min, max).
63
+
64
+ Parameters
65
+ ----------
66
+ min, max
67
+ The minimum, maximum value of the range.
68
+
69
+ See Also
70
+ --------
71
+ `unxt.quantity.wrap_to` : functional version of this method.
72
+
73
+ Examples
74
+ --------
75
+ >>> import unxt as u
76
+ >>> angle = u.Angle(370, "deg")
77
+ >>> angle.wrap_to(min=u.Q(0, "deg"), max=u.Q(360, "deg"))
78
+ Angle(Array(10, dtype=int32, ...), unit='deg')
79
+
80
+ """
81
+ return uapi.wrap_to(self, min, max)
82
+
83
+
84
+ # Add a rule that when a AbstractAngle interacts with a Quantity, the
85
+ # angle degrades to a Quantity. This is necessary for many operations, e.g.
86
+ # division of an angle by non-dimensionless quantity where the resulting units
87
+ # are not those of an angle.
88
+ add_promotion_rule(AbstractAngle, Quantity, Quantity)
89
+ add_promotion_rule(AbstractAngle, BareQuantity, BareQuantity)
@@ -0,0 +1,290 @@
1
+ # pylint: disable=import-error, no-member, unsubscriptable-object
2
+ # b/c it doesn't understand dataclass fields
3
+
4
+ __all__ = ("AbstractParametricQuantity",)
5
+
6
+ from collections.abc import Callable
7
+ from functools import partial
8
+ from typing import Any
9
+
10
+ import equinox as eqx
11
+ import jax
12
+ import wadler_lindig as wl
13
+ from astropy.units import PhysicalType
14
+ from jaxtyping import Array, Shaped
15
+ from plum import dispatch, parametric, type_unparametrized
16
+
17
+ from dataclassish import field_items, fields
18
+
19
+ from .base import AbstractQuantity
20
+ from .value import StaticValue
21
+ from unxt._src.dimensions import name_of
22
+ from unxt.dims import AbstractDimension, dimension, dimension_of
23
+ from unxt.units import AbstractUnit, unit as parse_unit
24
+
25
+
26
+ @parametric
27
+ class AbstractParametricQuantity(AbstractQuantity):
28
+ """Arrays with associated units.
29
+
30
+ This class is parametrized by the dimensions of the units.
31
+
32
+ """
33
+
34
+ value: eqx.AbstractVar[Shaped[Array | StaticValue, "*shape"]]
35
+ """The value of the `AbstractQuantity`."""
36
+
37
+ unit: eqx.AbstractVar[AbstractUnit]
38
+ """The unit associated with this value."""
39
+
40
+ def __post_init__(self) -> None:
41
+ """Check whether the arguments are valid."""
42
+ self._type_parameter: AbstractDimension
43
+
44
+ def __check_init__(self) -> None:
45
+ """Check whether the arguments are valid."""
46
+ expected_dimensions = self._type_parameter
47
+ got_dimensions = dimension_of(self.unit)
48
+ if got_dimensions != expected_dimensions: # pylint: disable=unreachable
49
+ msg = "Physical type mismatch." # TODO: better error message
50
+ raise ValueError(msg)
51
+
52
+ # ---------------------------------------------------------------
53
+ # Plum stuff
54
+
55
+ @classmethod
56
+ @dispatch
57
+ def __init_type_parameter__(
58
+ cls, dims: AbstractDimension, /
59
+ ) -> tuple[AbstractDimension]:
60
+ """Check whether the type parameters are valid."""
61
+ return (dims,)
62
+
63
+ @classmethod
64
+ @dispatch
65
+ def __init_type_parameter__(cls, dims: str, /) -> tuple[AbstractDimension]:
66
+ """Check whether the type parameters are valid."""
67
+ dims_: AbstractDimension
68
+ try:
69
+ dims_ = dimension(dims)
70
+ except ValueError:
71
+ dims_ = dimension_of(parse_unit(dims))
72
+ return (dims_,)
73
+
74
+ @classmethod
75
+ @dispatch
76
+ def __init_type_parameter__(cls, unit: AbstractUnit, /) -> tuple[AbstractDimension]:
77
+ """Infer the type parameter from the arguments."""
78
+ dims = dimension_of(unit)
79
+ if dims != "unknown": # pylint: disable=unreachable
80
+ return (dims,)
81
+ return (PhysicalType(unit, unit.to_string(fraction=False)),)
82
+
83
+ @classmethod
84
+ def __infer_type_parameter__(
85
+ cls, value: Any, unit: Any, **kwargs: Any
86
+ ) -> tuple[AbstractDimension]:
87
+ """Infer the type parameter from the arguments."""
88
+ return (dimension_of(parse_unit(unit)),)
89
+
90
+ @classmethod
91
+ @dispatch
92
+ def __le_type_parameter__(
93
+ cls, left: tuple[AbstractDimension], right: tuple[AbstractDimension]
94
+ ) -> bool:
95
+ """Define an order on type parameters.
96
+
97
+ That is, check whether `left <= right` or not.
98
+ """
99
+ (dim_left,) = left
100
+ (dim_right,) = right
101
+ return dim_left == dim_right
102
+
103
+ # ---------------------------------------------------------------
104
+ # misc
105
+
106
+ def __getnewargs_ex__(self) -> tuple[tuple[Any, ...], dict[str, Any]]:
107
+ """Return args, kwargs for ``__new__``.
108
+
109
+ In protocols 2+, this is used to determine the values (args,
110
+ and kwargs) passed to ``__new__``. We implement ``__getnewargs_ex__``
111
+ instead of ``__getnewargs__`` because the latter does not support
112
+ keyword-only arguments.
113
+
114
+ Examples
115
+ --------
116
+ >>> import copy as pycopy
117
+ >>> import unxt as u
118
+
119
+ >>> x = u.Quantity([1, 2, 3], "m")
120
+ >>> pycopy.copy(x)
121
+ Quantity(Array([1, 2, 3], dtype=int32), unit='m')
122
+
123
+ """
124
+ return (), field_items(self)
125
+
126
+ # TODO: see if pickling can be accomplished without reduce.
127
+ # https://docs.python.org/3.12/library/pickle.html
128
+ def __reduce__(
129
+ self,
130
+ ) -> tuple[Callable[..., "AbstractParametricQuantity"], tuple[Any, ...]]:
131
+ r"""Return the reduced value: a constructor and arguments.
132
+
133
+ The ``__reduce__`` protocol has limited support for keyword-only
134
+ argument. The only built-in means to pass kwargs is to the
135
+ ``__setstate__`` method or through a callable with signature ``(obj,
136
+ state)``. Neither of these methods allow for the kwargs to be passed to
137
+ the constructor, only after the object is partially initialized. This
138
+ does not work well for JAX, Equinox, or particularly run-time
139
+ typechecking. To get around this, instead of the class type as the
140
+ customary first element of reduced value we will instead return a
141
+ `functools.partial`-wrapping of the class type with the kwargs bundled
142
+ into the partial object. This is allowed by the ``__reduce__`` protocol,
143
+ which says the first returned item must be any "callable object that
144
+ will be called to create the initial version of the object." In
145
+ conjunction with the standard args, the `functools.partial`-wrapped
146
+ class type will properly construct any parametric subclass.
147
+
148
+ Returns
149
+ -------
150
+ functools.partial[type]
151
+ The `plum.type_unparametrized` form of this class object, which is
152
+ the parametric class without the specialization to the specific
153
+ parameter. The specialized subtype cannot be pickled since it is
154
+ dynamically produced. The type is wrapped into a `functools.partial`
155
+ with the keyword-only arguments to the constructor.
156
+ tuple[Any, ...]
157
+ The arguments to this class. Note: the keyword-only arguments are
158
+ bundled with the type.
159
+
160
+ Examples
161
+ --------
162
+ >>> import pickle
163
+ >>> import unxt as u
164
+
165
+ >>> x = u.Quantity([1, 2, 3], "m")
166
+ >>> pickle.dumps(x)
167
+ b'...'
168
+
169
+ """
170
+ args, kwargs = [], {}
171
+ for f in fields(self):
172
+ v = getattr(self, f.name)
173
+ if f.kw_only:
174
+ kwargs[f.name] = v
175
+ else:
176
+ args.append(v)
177
+
178
+ return partial(type_unparametrized(self), **kwargs), tuple(args)
179
+
180
+ def __pdoc__(
181
+ self,
182
+ *,
183
+ include_params: bool = False,
184
+ named_unit: bool = True,
185
+ use_short_name: bool = False,
186
+ **kwargs: Any,
187
+ ) -> wl.AbstractDoc:
188
+ """Return the Wadler-Lindig representation of this class.
189
+
190
+ This is used for the `__repr__` and `__str__` methods or when using the
191
+ `wadler_lindig` library.
192
+
193
+ Parameters
194
+ ----------
195
+ include_params
196
+ If `True`, the type parameter is included in the representation. If
197
+ `False`, the type parameter is not included in the representation.
198
+ For example, ``Quantity['length'][i32]`` versus ``Quantity[i32]``.
199
+ named_unit
200
+ If `True`, the unit is included in the representation as a named
201
+ argument. If `False`, the unit is included as a positional argument.
202
+ For example, ``Quantity(<array>, unit='m')`` versus
203
+ ``Quantity(<array>, 'm')``.
204
+ use_short_name
205
+ If `True` and the class has a ``short_name`` class variable, use the
206
+ short name instead of the full class name. For example, ``Q(...)``
207
+ instead of ``Quantity(...)``.
208
+ kwargs
209
+ Additional keyword arguments ``wadler_lindig.pdoc`` method for
210
+ formatting the value, stringified unit, and other fields.
211
+
212
+ Examples
213
+ --------
214
+ >>> import unxt as u
215
+ >>> import wadler_lindig as wl
216
+
217
+ >>> q = u.Quantity([1, 2, 3], "m")
218
+
219
+ The default pretty printing:
220
+
221
+ >>> wl.pprint(q)
222
+ Quantity(i32[3], unit='m')
223
+
224
+ The type parameter can be included in the representation:
225
+
226
+ >>> wl.pprint(q, include_params=True)
227
+ Quantity['length'](i32[3], unit='m')
228
+
229
+ The `str` method uses this as well:
230
+
231
+ >>> print(q)
232
+ Quantity['length']([1, 2, 3], unit='m')
233
+
234
+ Arrays can be printed in full:
235
+
236
+ >>> wl.pprint(q, short_arrays=False)
237
+ Quantity(Array([1, 2, 3], dtype=int32), unit='m')
238
+
239
+ The `repr` method uses this setting:
240
+
241
+ >>> print(repr(q))
242
+ Quantity(Array([1, 2, 3], dtype=int32), unit='m')
243
+
244
+ The units can be turned from a named argument to a positional argument
245
+ by setting `named_unit=False`:
246
+
247
+ >>> wl.pprint(q, named_unit=False)
248
+ Quantity(i32[3], 'm')
249
+
250
+ The class can be printed with its short name:
251
+
252
+ >>> wl.pprint(q, use_short_name=True)
253
+ Q(i32[3], unit='m')
254
+
255
+ Short name with type parameter:
256
+
257
+ >>> wl.pprint(q, use_short_name=True, include_params=True)
258
+ Q['length'](i32[3], unit='m')
259
+
260
+ """
261
+ pdoc = super().__pdoc__(
262
+ include_params=include_params,
263
+ named_unit=named_unit,
264
+ use_short_name=use_short_name,
265
+ **kwargs,
266
+ )
267
+
268
+ # Type Parameter
269
+ if not include_params:
270
+ param = wl.TextDoc("")
271
+ else:
272
+ param = wl.TextDoc(f"[{name_of(self._type_parameter)!r}]")
273
+
274
+ return wl.ConcatDoc(pdoc.children[0], param, *pdoc.children[1:])
275
+
276
+ def __repr__(self) -> str:
277
+ return wl.pformat(
278
+ self, include_params=False, named_unit=True, short_arrays=False, indent=4
279
+ )
280
+
281
+ def __str__(self) -> str:
282
+ return wl.pformat(
283
+ self,
284
+ include_params=True,
285
+ named_unit=True,
286
+ short_arrays="compact"
287
+ if not isinstance(self.value, jax.core.Tracer)
288
+ else True,
289
+ indent=4,
290
+ )
@@ -0,0 +1,137 @@
1
+ """Flags for quantity operations."""
2
+
3
+ __all__ = ("AllowValue",)
4
+
5
+ from typing import Any, NoReturn
6
+
7
+ from plum import dispatch
8
+
9
+ import unxt_api as uapi
10
+ from .base import AbstractQuantity
11
+
12
+
13
+ class AllowValue:
14
+ """A flag to allow a value to be passed through `unxt.ustrip`.
15
+
16
+ Examples
17
+ --------
18
+ >>> import jax.numpy as jnp
19
+ >>> import unxt as u
20
+ >>> from unxt.quantity import AllowValue
21
+
22
+ >>> x = jnp.array(1)
23
+ >>> y = u.ustrip(AllowValue, "km", x)
24
+ >>> y is x
25
+ True
26
+
27
+ >>> u.ustrip(AllowValue, "km", u.Q(1000, "m"))
28
+ Array(1., dtype=float32, ...)
29
+
30
+ This is a flag, so it cannot be instantiated.
31
+
32
+ >>> try:
33
+ ... AllowValue()
34
+ ... except TypeError as e:
35
+ ... print(e)
36
+ Cannot instantiate AllowValue
37
+
38
+ """
39
+
40
+ def __new__(cls) -> NoReturn:
41
+ msg = "Cannot instantiate AllowValue"
42
+ raise TypeError(msg)
43
+
44
+
45
+ @dispatch
46
+ def ustrip(flag: type[AllowValue], unit: Any, x: Any, /) -> Any:
47
+ """Strip the units from a value. This is a no-op.
48
+
49
+ Examples
50
+ --------
51
+ >>> import jax.numpy as jnp
52
+ >>> import unxt as u
53
+ >>> from unxt.quantity import AllowValue
54
+
55
+ >>> x = jnp.array(1)
56
+ >>> y = u.ustrip(AllowValue, "km", x)
57
+ >>> y is x
58
+ True
59
+
60
+ >>> x = 1_000
61
+ >>> y = u.ustrip(AllowValue, "km", x)
62
+ >>> y is x
63
+ True
64
+
65
+ >>> x = "hello"
66
+ >>> y = u.ustrip(AllowValue, "km", x)
67
+ >>> y is x
68
+ True
69
+
70
+ """
71
+ return x
72
+
73
+
74
+ @dispatch
75
+ def ustrip(flag: type[AllowValue], x: Any, /) -> Any:
76
+ """Strip the units from a value. This is a no-op.
77
+
78
+ Examples
79
+ --------
80
+ >>> import jax.numpy as jnp
81
+ >>> import unxt as u
82
+ >>> from unxt.quantity import AllowValue
83
+
84
+ >>> x = jnp.array(1)
85
+ >>> y = u.ustrip(AllowValue, x)
86
+ >>> y is x
87
+ True
88
+
89
+ >>> x = 1_000
90
+ >>> y = u.ustrip(AllowValue, x)
91
+ >>> y is x
92
+ True
93
+
94
+ >>> x = "hello"
95
+ >>> y = u.ustrip(AllowValue, x)
96
+ >>> y is x
97
+ True
98
+
99
+ """
100
+ return x
101
+
102
+
103
+ @dispatch # TODO: type annotate by value
104
+ def ustrip(flag: type[AllowValue], unit: Any, x: AbstractQuantity, /) -> Any:
105
+ """Strip the units from a quantity.
106
+
107
+ Examples
108
+ --------
109
+ >>> import unxt as u
110
+ >>> from unxt.quantity import AllowValue
111
+ >>> q = u.Q(1000, "m")
112
+ >>> u.ustrip(AllowValue, "km", q)
113
+ Array(1., dtype=float32, ...)
114
+
115
+ """
116
+ return uapi.ustrip(unit, x)
117
+
118
+
119
+ @dispatch
120
+ def ustrip(flag: type[AllowValue], x: AbstractQuantity, /) -> Any:
121
+ """Strip the units from a quantity.
122
+
123
+ Examples
124
+ --------
125
+ >>> import jax.numpy as jnp
126
+ >>> import unxt as u
127
+ >>> from unxt.quantity import AllowValue
128
+
129
+ >>> x = u.Q(1, "kpc")
130
+ >>> y = u.ustrip(AllowValue, x)
131
+ >>> not isinstance(y, u.Quantity)
132
+ True
133
+ >>> y == 1
134
+ Array(True, dtype=bool, ...)
135
+
136
+ """
137
+ return uapi.ustrip(x)