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,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."""
|