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,234 @@
1
+ r"""Experimental features.
2
+
3
+ .. warning::
4
+
5
+ These features may be removed or changed in the future without notice.
6
+
7
+ On some occasions JAX's automatic differentiation functions do not work well
8
+ with quantities. This is checked by enabling runtime type-checking (see the
9
+ docs), which will raise an error if a quantity's units do not match the expected
10
+ input / output units of a function. In these cases, you can use the functions in
11
+ this module to provide the units to the automatic differentiation functions.
12
+ Instead of directly propagating the units through the automatic differentiation
13
+ functions, the units are stripped and re-applied, while also being provided
14
+ within the function being AD'd.
15
+
16
+ To import this experimental module
17
+
18
+ >>> from unxt import experimental
19
+
20
+ """
21
+ # pylint: disable=import-error
22
+
23
+ __all__ = ("grad", "hessian", "jacfwd")
24
+
25
+ import functools as ft
26
+ from collections.abc import Callable
27
+ from typing import Any, TypeVar, TypeVarTuple
28
+ from typing_extensions import Unpack
29
+
30
+ import equinox as eqx
31
+ import jax
32
+ from jaxtyping import ArrayLike
33
+ from plum import type_unparametrized
34
+
35
+ from .quantity import AbstractQuantity, BareQuantity
36
+ from .units import AbstractUnit
37
+ from unxt_api import unit, unit_of, ustrip
38
+
39
+ Args = TypeVarTuple("Args")
40
+ R = TypeVar("R", bound=AbstractQuantity)
41
+
42
+
43
+ def unit_or_none(obj: Any) -> AbstractUnit | None:
44
+ return obj if obj is None else unit(obj)
45
+
46
+
47
+ def grad(
48
+ fun: Callable[[Unpack[Args]], R],
49
+ argnums: int = 0,
50
+ *,
51
+ units: tuple[AbstractUnit | str, ...],
52
+ ) -> Callable[[Unpack[Args]], R]:
53
+ """Gradient of a function with units.
54
+
55
+ In general, if you can use ``quax.quaxify(jax.grad(func))`` (or the
56
+ syntactic sugar ``quaxed.grad(func)``), that's the better option! The
57
+ difference from those functions is how this units are supported. ``quaxify``
58
+ will directly propagate the units through the automatic differentiation
59
+ functions. But sometimes that doesn't work and we need to strip the units
60
+ and re-apply them. This function does that, using the "units" kwarg.
61
+
62
+ See Also
63
+ --------
64
+ jax.grad : The original JAX gradient function.
65
+
66
+ Examples
67
+ --------
68
+ >>> import jax.numpy as jnp
69
+ >>> import unxt as u
70
+
71
+ >>> def cube_volume(x: u.Quantity["length"]) -> u.Quantity["volume"]:
72
+ ... return x**3
73
+
74
+ >>> grad_cube_volume = u.experimental.grad(cube_volume, units=("m",))
75
+ >>> grad_cube_volume(u.Q(2.0, "m"))
76
+ Quantity(Array(12., dtype=float32, weak_type=True), unit='m2')
77
+
78
+ """
79
+ theunits: tuple[AbstractUnit | None, ...] = tuple(map(unit_or_none, units))
80
+
81
+ # Gradient of function, stripping and adding units
82
+ @ft.partial(jax.grad, argnums=argnums)
83
+ def gradfun_mag(*args: Any) -> ArrayLike:
84
+ args_ = (
85
+ (a if unit is None else BareQuantity(a, unit))
86
+ for a, unit in zip(args, theunits, strict=True)
87
+ )
88
+ return ustrip(fun(*args_)) # type: ignore[arg-type]
89
+
90
+ def gradfun(*args: *Args) -> R:
91
+ # Get the value of the args. They are turned back into Quantity
92
+ # inside the function we are taking the grad of.
93
+ args_ = tuple( # type: ignore[var-annotated]
94
+ (a if unit is None else ustrip(unit, a))
95
+ for a, unit in zip(args, theunits, strict=True) # type: ignore[arg-type]
96
+ )
97
+ # Call the grad, returning a Quantity
98
+ value = fun(*args)
99
+ grad_value = gradfun_mag(*args_)
100
+ # Adjust the Quantity by the units of the derivative
101
+ # TODO: get Quantity[unit] / unit2 -> Quantity[unit/unit2] working
102
+ return type_unparametrized(value)(
103
+ grad_value, unit_of(value) / theunits[argnums]
104
+ )
105
+
106
+ return gradfun
107
+
108
+
109
+ def jacfwd(
110
+ fun: Callable[[Unpack[Args]], R],
111
+ argnums: int = 0,
112
+ *,
113
+ units: tuple[AbstractUnit | str, ...],
114
+ ) -> Callable[[Unpack[Args]], R]:
115
+ """Jacobian of ``fun`` evaluated column-by-column using forward-mode AD.
116
+
117
+ In general, if you can use `quax.quaxify(jax.jacfwd(func))` (or the
118
+ syntactic sugar `quax.jacfwd(func)`), that's the better option! The
119
+ difference from those functions is how this units are supported. `quaxify`
120
+ will directly propagate the units through the automatic differentiation
121
+ functions. But sometimes that doesn't work and we need to strip the units
122
+ and re-apply them. This function does that, using the ``units`` kwarg.
123
+
124
+ See Also
125
+ --------
126
+ jax.jacfwd : The original JAX jacfwd function.
127
+
128
+ Examples
129
+ --------
130
+ >>> import jax.numpy as jnp
131
+ >>> import unxt as u
132
+
133
+ >>> def cubbe_volume(x: u.Quantity["length"]) -> u.Quantity["volume"]:
134
+ ... return x**3
135
+
136
+ >>> jacfwd_cubbe_volume = u.experimental.jacfwd(cubbe_volume, units=("m",))
137
+ >>> jacfwd_cubbe_volume(u.Q(2.0, "m"))
138
+ BareQuantity(Array(12., dtype=float32, weak_type=True), unit='m2')
139
+
140
+ """
141
+ argnums = eqx.error_if(
142
+ argnums,
143
+ not isinstance(argnums, int),
144
+ "only int argnums are currently supported",
145
+ )
146
+
147
+ theunits: tuple[AbstractUnit | None, ...] = tuple(map(unit_or_none, units))
148
+
149
+ @ft.partial(jax.jacfwd, argnums=argnums)
150
+ def jacfun_mag(*args: Any) -> R:
151
+ args_ = tuple(
152
+ (a if unit is None else BareQuantity(a, unit))
153
+ for a, unit in zip(args, theunits, strict=True)
154
+ )
155
+ return fun(*args_) # type: ignore[arg-type]
156
+
157
+ def jacfun(*args: *Args) -> R:
158
+ # Get the value of the args. They are turned back into Quantity
159
+ # inside the function we are taking the Jacobian of.
160
+ args_ = tuple( # type: ignore[var-annotated]
161
+ (a if unit is None else ustrip(unit, a))
162
+ for a, unit in zip(args, theunits, strict=True) # type: ignore[arg-type]
163
+ )
164
+ # Call the Jacobian, returning a Quantity
165
+ value = jacfun_mag(*args_)
166
+ # Adjust the Quantity by the units of the derivative
167
+ # TODO: check the unit correction
168
+ # TODO: get Quantity[unit] / unit2 -> Quantity[unit/unit2] working
169
+ return type_unparametrized(value)(
170
+ ustrip(value), unit_of(value) / theunits[argnums]
171
+ )
172
+
173
+ return jacfun
174
+
175
+
176
+ def hessian(
177
+ fun: Callable[[Unpack[Args]], R],
178
+ argnums: int = 0,
179
+ *,
180
+ units: tuple[AbstractUnit | str, ...],
181
+ ) -> Callable[[Unpack[Args]], R]:
182
+ """Hessian.
183
+
184
+ In general, if you can use `quax.quaxify(jax.hessian(func))` (or the
185
+ syntactic sugar `quax.hessian(func)`), that's the better option! The
186
+ difference from those functions is how this units are supported. `quaxify`
187
+ will directly propagate the units through the automatic differentiation
188
+ functions. But sometimes that doesn't work and we need to strip the units
189
+ and re-apply them. This function does that, using the ``units`` kwarg.
190
+
191
+ See Also
192
+ --------
193
+ jax.hessian : The original JAX hessian function.
194
+
195
+ Examples
196
+ --------
197
+ >>> import jax.numpy as jnp
198
+ >>> import unxt as u
199
+
200
+ >>> def cubbe_volume(x: u.Quantity["length"]) -> u.Quantity["volume"]:
201
+ ... return x**3
202
+
203
+ >>> hessian_cubbe_volume = u.experimental.hessian(cubbe_volume, units=("m",))
204
+ >>> hessian_cubbe_volume(u.Q(2.0, "m"))
205
+ BareQuantity(Array(12., dtype=float32, weak_type=True), unit='m')
206
+
207
+ """
208
+ theunits: tuple[AbstractUnit, ...] = tuple(map(unit_or_none, units))
209
+
210
+ @ft.partial(jax.hessian)
211
+ def hessfun_mag(*args: Any) -> R:
212
+ args_ = tuple(
213
+ (a if unit is None else BareQuantity(a, unit))
214
+ for a, unit in zip(args, theunits, strict=True)
215
+ )
216
+ return fun(*args_) # type: ignore[arg-type]
217
+
218
+ def hessfun(*args: *Args) -> R:
219
+ # Get the value of the args. They are turned back into Quantity
220
+ # inside the function we are taking the hessian of.
221
+ args_ = tuple( # type: ignore[var-annotated]
222
+ (a if unit is None else ustrip(unit, a))
223
+ for a, unit in zip(args, units, strict=True) # type: ignore[arg-type]
224
+ )
225
+ # Call the hessian, returning a Quantity
226
+ value = hessfun_mag(*args_)
227
+ # Adjust the Quantity by the units of the derivative
228
+ # TODO: check the unit correction
229
+ # TODO: get Quantity[unit] / unit2 -> Quantity[unit/unit2] working
230
+ return type_unparametrized(value)(
231
+ ustrip(value), unit_of(value) / theunits[argnums] ** 2
232
+ )
233
+
234
+ return hessfun
@@ -0,0 +1,24 @@
1
+ """Quantities in JAX."""
2
+
3
+ __all__ = (
4
+ "AbstractParametricQuantity",
5
+ "AbstractQuantity",
6
+ "Quantity",
7
+ "StaticQuantity",
8
+ "StaticValue",
9
+ "BareQuantity",
10
+ "is_any_quantity",
11
+ "convert_to_quantity_value",
12
+ "AbstractAngle",
13
+ "Angle",
14
+ )
15
+
16
+
17
+ from .angle import Angle
18
+ from .base import AbstractQuantity, is_any_quantity
19
+ from .base_angle import AbstractAngle
20
+ from .base_parametric import AbstractParametricQuantity
21
+ from .quantity import Quantity
22
+ from .static_quantity import StaticQuantity
23
+ from .unchecked import BareQuantity
24
+ from .value import StaticValue, convert_to_quantity_value
@@ -0,0 +1,58 @@
1
+ """Angular quantities."""
2
+
3
+ __all__ = ("Angle",)
4
+
5
+ from typing import final
6
+
7
+ import equinox as eqx
8
+ from jaxtyping import Array, Real
9
+
10
+ import unxt_api as uapi
11
+ from .base_angle import AbstractAngle
12
+ from .value import StaticValue, convert_to_quantity_value
13
+ from unxt.units import AbstractUnit
14
+
15
+
16
+ @final
17
+ class Angle(AbstractAngle):
18
+ """Angular quantity.
19
+
20
+ Examples
21
+ --------
22
+ >>> import unxt as u
23
+
24
+ Create an Angle:
25
+
26
+ >>> q = u.Angle(1, "rad")
27
+ >>> q
28
+ Angle(Array(1, dtype=int32, ...), unit='rad')
29
+
30
+ Wrap an Angle to a range:
31
+
32
+ >>> q = u.Angle(370, "deg")
33
+ >>> q.wrap_to(u.Q(0, "deg"), u.Q(360, "deg"))
34
+ Angle(Array(10, dtype=int32, ...), unit='deg')
35
+
36
+ Create an Angle array:
37
+
38
+ >>> q = u.Angle([1, 2, 3], "deg")
39
+ >>> q
40
+ Angle(Array([1, 2, 3], dtype=int32), unit='deg')
41
+
42
+ Do math on an Angle:
43
+
44
+ >>> 2 * q
45
+ Angle(Array([2, 4, 6], dtype=int32), unit='deg')
46
+
47
+ >>> q % u.Q(4, "deg")
48
+ Angle(Array([1, 2, 3], dtype=int32), unit='deg')
49
+
50
+ """
51
+
52
+ value: Real[Array | StaticValue, "*shape"] = eqx.field(
53
+ converter=convert_to_quantity_value
54
+ )
55
+ """The value of the `unxt.AbstractQuantity`."""
56
+
57
+ unit: AbstractUnit = eqx.field(static=True, converter=uapi.unit)
58
+ """The unit associated with this value."""