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,307 @@
|
|
|
1
|
+
"""Tools for representing systems of units using ``astropy.units``."""
|
|
2
|
+
|
|
3
|
+
__all__ = ("unitsystem", "unitsystem_of")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from dataclasses import field, make_dataclass
|
|
8
|
+
from typing import Annotated, Any
|
|
9
|
+
|
|
10
|
+
import equinox as eqx
|
|
11
|
+
import jax.tree_util as jtu
|
|
12
|
+
import numpy as np
|
|
13
|
+
from astropy.constants import G as const_G # noqa: N811, pylint: disable=E0611
|
|
14
|
+
from astropy.units import UnitBase as AstropyUnitBase
|
|
15
|
+
from plum import dispatch
|
|
16
|
+
|
|
17
|
+
from . import builtin_dimensions as ud
|
|
18
|
+
from .base import UNITSYSTEMS_REGISTRY, AbstractUnitSystem
|
|
19
|
+
from .builtin import DimensionlessUnitSystem
|
|
20
|
+
from .flags import AbstractUSysFlag, DynamicalSimUSysFlag, StandardUSysFlag
|
|
21
|
+
from .realizations import NAMED_UNIT_SYSTEMS, dimensionless
|
|
22
|
+
from .utils import parse_dimlike_name
|
|
23
|
+
from unxt.dims import dimension_of
|
|
24
|
+
from unxt.units import unit
|
|
25
|
+
|
|
26
|
+
# ===================================================================
|
|
27
|
+
# `unitsystem` function
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dispatch
|
|
31
|
+
def unitsystem(usys: AbstractUnitSystem, /) -> AbstractUnitSystem:
|
|
32
|
+
"""Convert a UnitSystem to a UnitSystem.
|
|
33
|
+
|
|
34
|
+
Examples
|
|
35
|
+
--------
|
|
36
|
+
>>> from unxt.unitsystems import unitsystem
|
|
37
|
+
>>> usys = unitsystem("kpc", "Myr", "Msun", "radian")
|
|
38
|
+
>>> usys
|
|
39
|
+
unitsystem(kpc, Myr, solMass, rad)
|
|
40
|
+
|
|
41
|
+
>>> unitsystem(usys) is usys
|
|
42
|
+
True
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
return usys
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dispatch
|
|
49
|
+
def unitsystem(seq: Sequence[Any], /) -> AbstractUnitSystem:
|
|
50
|
+
"""Convert a UnitSystem or tuple of arguments to a UnitSystem.
|
|
51
|
+
|
|
52
|
+
Examples
|
|
53
|
+
--------
|
|
54
|
+
>>> import unxt as u
|
|
55
|
+
|
|
56
|
+
>>> u.unitsystem(())
|
|
57
|
+
DimensionlessUnitSystem()
|
|
58
|
+
|
|
59
|
+
>>> u.unitsystem(("kpc", "Myr", "Msun", "radian"))
|
|
60
|
+
unitsystem(kpc, Myr, solMass, rad)
|
|
61
|
+
|
|
62
|
+
>>> u.unitsystem(["kpc", "Myr", "Msun", "radian"])
|
|
63
|
+
unitsystem(kpc, Myr, solMass, rad)
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
return unitsystem(*seq) if len(seq) > 0 else dimensionless
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dispatch
|
|
70
|
+
def unitsystem(_: None, /) -> DimensionlessUnitSystem:
|
|
71
|
+
"""Dimensionless unit system from None.
|
|
72
|
+
|
|
73
|
+
Examples
|
|
74
|
+
--------
|
|
75
|
+
>>> from unxt.unitsystems import unitsystem
|
|
76
|
+
>>> unitsystem(None)
|
|
77
|
+
DimensionlessUnitSystem()
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
return dimensionless
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dispatch
|
|
84
|
+
def unitsystem(*args: Any) -> AbstractUnitSystem:
|
|
85
|
+
"""Convert a set of arguments to a UnitSystem.
|
|
86
|
+
|
|
87
|
+
Examples
|
|
88
|
+
--------
|
|
89
|
+
>>> from unxt.unitsystems import unitsystem
|
|
90
|
+
|
|
91
|
+
>>> unitsystem("kpc", "Myr", "Msun", "radian")
|
|
92
|
+
unitsystem(kpc, Myr, solMass, rad)
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
# Convert everything to a unit
|
|
96
|
+
args = tuple(map(unit, args))
|
|
97
|
+
|
|
98
|
+
# Check that the units all have different dimensions
|
|
99
|
+
dims = tuple(map(dimension_of, args))
|
|
100
|
+
dims = eqx.error_if(
|
|
101
|
+
dims,
|
|
102
|
+
len(set(dims)) < len(dims),
|
|
103
|
+
"some dimensions are repeated",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Return if the unit system is already registered
|
|
107
|
+
if dims in UNITSYSTEMS_REGISTRY:
|
|
108
|
+
return UNITSYSTEMS_REGISTRY[dims](*args)
|
|
109
|
+
|
|
110
|
+
# Otherwise, create a new unit system
|
|
111
|
+
# dimension names of all the units
|
|
112
|
+
du = {parse_dimlike_name(x).replace(" ", "_"): dimension_of(x) for x in args}
|
|
113
|
+
# name: physical types
|
|
114
|
+
cls_name = "".join(k.title().replace("_", "") for k in du) + "UnitSystem" # pylint: disable=unreachable
|
|
115
|
+
# fields: name, unit
|
|
116
|
+
fields = [
|
|
117
|
+
(
|
|
118
|
+
k,
|
|
119
|
+
Annotated[AstropyUnitBase, v],
|
|
120
|
+
field(init=True, repr=True, hash=True, compare=True), # pylint: disable=invalid-field-call
|
|
121
|
+
)
|
|
122
|
+
for k, v in du.items()
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
def _reduce_(self: AbstractUnitSystem) -> tuple:
|
|
126
|
+
return (_call_unitsystem, self.base_units, None, None, None, None)
|
|
127
|
+
|
|
128
|
+
# Make and register the dataclass class
|
|
129
|
+
unitsystem_cls: type[AbstractUnitSystem] = make_dataclass(
|
|
130
|
+
cls_name,
|
|
131
|
+
fields,
|
|
132
|
+
bases=(AbstractUnitSystem,),
|
|
133
|
+
namespace={"__reduce__": _reduce_},
|
|
134
|
+
frozen=True,
|
|
135
|
+
slots=True,
|
|
136
|
+
eq=True,
|
|
137
|
+
repr=True,
|
|
138
|
+
init=True,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Register the dynamically created unit system as a static PyTree node
|
|
142
|
+
jtu.register_static(unitsystem_cls)
|
|
143
|
+
|
|
144
|
+
# Make the dataclass instance
|
|
145
|
+
return unitsystem_cls(*args)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dispatch
|
|
149
|
+
def unitsystem(name: str, /) -> AbstractUnitSystem:
|
|
150
|
+
"""Return unit system from name.
|
|
151
|
+
|
|
152
|
+
Examples
|
|
153
|
+
--------
|
|
154
|
+
>>> from unxt.unitsystems import unitsystem
|
|
155
|
+
>>> unitsystem("galactic")
|
|
156
|
+
unitsystem(kpc, Myr, solMass, rad)
|
|
157
|
+
|
|
158
|
+
>>> unitsystem("solarsystem")
|
|
159
|
+
unitsystem(AU, yr, solMass, rad)
|
|
160
|
+
|
|
161
|
+
>>> unitsystem("dimensionless")
|
|
162
|
+
DimensionlessUnitSystem()
|
|
163
|
+
|
|
164
|
+
"""
|
|
165
|
+
return NAMED_UNIT_SYSTEMS[name]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@dispatch
|
|
169
|
+
def unitsystem(usys: AbstractUnitSystem, *args: Any) -> AbstractUnitSystem:
|
|
170
|
+
"""Create a unit system from an existing unit system and additional units.
|
|
171
|
+
|
|
172
|
+
Examples
|
|
173
|
+
--------
|
|
174
|
+
We can add a new unit definition to an existing unit system:
|
|
175
|
+
|
|
176
|
+
>>> from unxt.unitsystems import unitsystem
|
|
177
|
+
>>> usys = unitsystem("galactic")
|
|
178
|
+
>>> unitsystem(usys, "km/s")
|
|
179
|
+
LengthTimeMassAngleSpeedUnitSystem(length=Unit("kpc"), time=Unit("Myr"), mass=Unit("solMass"), angle=Unit("rad"), speed=Unit("km / s"))
|
|
180
|
+
|
|
181
|
+
We can also override the base unit of an existing unit system:
|
|
182
|
+
|
|
183
|
+
>>> new_usys = unitsystem(usys, "pc")
|
|
184
|
+
>>> new_usys
|
|
185
|
+
TimeMassAngleLengthUnitSystem(time=Unit("Myr"), mass=Unit("solMass"), angle=Unit("rad"), length=Unit("pc"))
|
|
186
|
+
|
|
187
|
+
""" # noqa: E501
|
|
188
|
+
# TODO: not need this hack for single-string inputs
|
|
189
|
+
# TODO: process new units without making a whole unit system
|
|
190
|
+
if len(args) == 1 and isinstance(args[0], str):
|
|
191
|
+
new_usys = unitsystem(unit(args[0]))
|
|
192
|
+
else:
|
|
193
|
+
new_usys = unitsystem(*args)
|
|
194
|
+
|
|
195
|
+
current_units = [
|
|
196
|
+
u for u in usys.base_units if dimension_of(u) not in new_usys.base_dimensions
|
|
197
|
+
]
|
|
198
|
+
return unitsystem(*current_units, *args) # pylint: disable=unreachable
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@dispatch
|
|
202
|
+
def unitsystem(flag: type[AbstractUSysFlag], *_: Any) -> AbstractUnitSystem:
|
|
203
|
+
"""Raise an exception since the flag is abstract."""
|
|
204
|
+
msg = "Do not use the AbstractUSysFlag directly, only use subclasses."
|
|
205
|
+
raise TypeError(msg)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@dispatch
|
|
209
|
+
def unitsystem(flag: type[StandardUSysFlag], *args: Any) -> AbstractUnitSystem:
|
|
210
|
+
"""Create a standard unit system using the inputted units.
|
|
211
|
+
|
|
212
|
+
Examples
|
|
213
|
+
--------
|
|
214
|
+
>>> from unxt import unitsystem, unitsystems
|
|
215
|
+
>>> unitsystem(unitsystems.StandardUSysFlag, "kpc", "Myr", "Msun")
|
|
216
|
+
LengthTimeMassUnitSystem(length=Unit("kpc"), time=Unit("Myr"), mass=Unit("solMass"))
|
|
217
|
+
|
|
218
|
+
"""
|
|
219
|
+
return unitsystem(*args)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dispatch
|
|
223
|
+
def unitsystem(
|
|
224
|
+
flag: type[DynamicalSimUSysFlag],
|
|
225
|
+
*args: Any,
|
|
226
|
+
G: float | int = 1.0, # noqa: N803
|
|
227
|
+
) -> AbstractUnitSystem:
|
|
228
|
+
"""Make a dynamical unit system.
|
|
229
|
+
|
|
230
|
+
Examples
|
|
231
|
+
--------
|
|
232
|
+
>>> from unxt.unitsystems import unitsystem, DynamicalSimUSysFlag
|
|
233
|
+
|
|
234
|
+
>>> unitsystem(DynamicalSimUSysFlag, "m", "kg")
|
|
235
|
+
LengthMassTimeUnitSystem(length=Unit("m"), mass=Unit("kg"), time=Unit("122404 s"))
|
|
236
|
+
|
|
237
|
+
"""
|
|
238
|
+
tmp = unitsystem(*args)
|
|
239
|
+
|
|
240
|
+
# Use G for computing the missing units below:
|
|
241
|
+
G = G * const_G
|
|
242
|
+
|
|
243
|
+
added = ()
|
|
244
|
+
if ud.length in tmp.base_dimensions and ud.mass in tmp.base_dimensions:
|
|
245
|
+
time = 1 / np.sqrt(G * tmp["mass"] / tmp["length"] ** 3)
|
|
246
|
+
added = (time,)
|
|
247
|
+
elif ud.length in tmp.base_dimensions and ud.time in tmp.base_dimensions:
|
|
248
|
+
mass = 1 / G * tmp["length"] ** 3 / tmp["time"] ** 2
|
|
249
|
+
added = (mass,)
|
|
250
|
+
elif ud.length in tmp.base_dimensions and ud.speed in tmp.base_dimensions:
|
|
251
|
+
time = tmp["length"] / tmp["velocity"]
|
|
252
|
+
mass = tmp["velocity"] ** 2 / G * tmp["length"]
|
|
253
|
+
added = (time, mass)
|
|
254
|
+
elif ud.mass in tmp.base_dimensions and ud.time in tmp.base_dimensions:
|
|
255
|
+
length = np.cbrt(G * tmp["mass"] * tmp["time"] ** 2)
|
|
256
|
+
added = (length,)
|
|
257
|
+
elif ud.mass in tmp.base_dimensions and ud.speed in tmp.base_dimensions:
|
|
258
|
+
length = G * tmp["mass"] / tmp["velocity"] ** 2
|
|
259
|
+
time = length / tmp["velocity"]
|
|
260
|
+
added = (length, time)
|
|
261
|
+
elif ud.time in tmp.base_dimensions and ud.speed in tmp.base_dimensions:
|
|
262
|
+
mass = 1 / G * tmp["velocity"] ** 3 * tmp["time"]
|
|
263
|
+
length = G * mass / tmp["velocity"] ** 2
|
|
264
|
+
added = (mass, length)
|
|
265
|
+
|
|
266
|
+
return unitsystem(*tmp, *added)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# ----
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _call_unitsystem(*args: Any) -> AbstractUnitSystem:
|
|
273
|
+
return unitsystem(*args)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
# ===================================================================
|
|
277
|
+
# `unitsystem_of` function
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@dispatch
|
|
281
|
+
def unitsystem_of(obj: Any, /) -> DimensionlessUnitSystem:
|
|
282
|
+
"""Return the unit system of the object.
|
|
283
|
+
|
|
284
|
+
Examples
|
|
285
|
+
--------
|
|
286
|
+
>>> from unxt.unitsystems import unitsystem_of
|
|
287
|
+
|
|
288
|
+
>>> unitsystem_of(1)
|
|
289
|
+
DimensionlessUnitSystem()
|
|
290
|
+
|
|
291
|
+
"""
|
|
292
|
+
return dimensionless
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@dispatch
|
|
296
|
+
def unitsystem_of(obj: AbstractUnitSystem, /) -> AbstractUnitSystem:
|
|
297
|
+
"""Return the unit system from the unit system.
|
|
298
|
+
|
|
299
|
+
Examples
|
|
300
|
+
--------
|
|
301
|
+
>>> from unxt.unitsystems import galactic, unitsystem_of
|
|
302
|
+
|
|
303
|
+
>>> unitsystem_of(galactic) is galactic
|
|
304
|
+
True
|
|
305
|
+
|
|
306
|
+
"""
|
|
307
|
+
return obj
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Flags for unit system definitions."""
|
|
2
|
+
|
|
3
|
+
__all__ = ("AbstractUSysFlag", "DynamicalSimUSysFlag", "StandardUSysFlag")
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AbstractUSysFlag:
|
|
9
|
+
"""Abstract class for unit system flags to provide dispatch control.
|
|
10
|
+
|
|
11
|
+
Unit system flags are used to indicate the type of unit system being
|
|
12
|
+
defined. They are not intended to be instantiated and are used to provide
|
|
13
|
+
dispatch control for defining unit systems.
|
|
14
|
+
|
|
15
|
+
Raises
|
|
16
|
+
------
|
|
17
|
+
ValueError
|
|
18
|
+
If an attempt is made to instantiate a unit system flag class.
|
|
19
|
+
|
|
20
|
+
See Also
|
|
21
|
+
--------
|
|
22
|
+
`unxt.unitsystems.unitsystem` :
|
|
23
|
+
Function to define a unit system. The `AbstractUSysFlag` can be provided
|
|
24
|
+
as the first argument to indicate the type of unit system being defined.
|
|
25
|
+
This is useful for multiple-dispatching to the correct constructor.
|
|
26
|
+
|
|
27
|
+
Examples
|
|
28
|
+
--------
|
|
29
|
+
For this example we will use the `unxt.unitsystems.StandardUSysFlag`, which
|
|
30
|
+
indicates the standard construction of a unit system directly from the set
|
|
31
|
+
of base units. Flags like `unxt.unitsystems.DynamicalSimUSysFlag` can be
|
|
32
|
+
used to create a unit system where the gravitational constant ``G`` is
|
|
33
|
+
unit-less and units are defined accordingly.
|
|
34
|
+
|
|
35
|
+
>>> import unxt
|
|
36
|
+
|
|
37
|
+
Define a unit system with the standard flag:
|
|
38
|
+
|
|
39
|
+
>>> unxt.unitsystem(unxt.unitsystems.StandardUSysFlag, "m", "kg", "s")
|
|
40
|
+
LengthMassTimeUnitSystem(length=Unit("m"), mass=Unit("kg"), time=Unit("s"))
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __new__(cls, *_: Any, **__: Any) -> None: # type: ignore[misc]
|
|
45
|
+
msg = "unit system flag classes cannot be instantiated."
|
|
46
|
+
raise ValueError(msg)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class StandardUSysFlag(AbstractUSysFlag):
|
|
50
|
+
"""Flag to indicate a standard unit system with no additional arguments.
|
|
51
|
+
|
|
52
|
+
Examples
|
|
53
|
+
--------
|
|
54
|
+
>>> from unxt.unitsystems import unitsystem, StandardUSysFlag
|
|
55
|
+
|
|
56
|
+
Define a unit system with the standard flag:
|
|
57
|
+
|
|
58
|
+
>>> unitsystem(StandardUSysFlag, "m", "kg", "s")
|
|
59
|
+
LengthMassTimeUnitSystem(length=Unit("m"), mass=Unit("kg"), time=Unit("s"))
|
|
60
|
+
|
|
61
|
+
Further examples may be found in the ``unitsystem`` docs.
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class DynamicalSimUSysFlag(AbstractUSysFlag):
|
|
67
|
+
"""Flag to indicate a unit system with optional definition of G.
|
|
68
|
+
|
|
69
|
+
Examples
|
|
70
|
+
--------
|
|
71
|
+
>>> from unxt.unitsystems import unitsystem, DynamicalSimUSysFlag
|
|
72
|
+
|
|
73
|
+
Define a unit system with the dynamical simulation flag:
|
|
74
|
+
|
|
75
|
+
>>> unitsystem(DynamicalSimUSysFlag, "m", "kg")
|
|
76
|
+
LengthMassTimeUnitSystem(length=Unit("m"), mass=Unit("kg"), time=Unit("122404 s"))
|
|
77
|
+
|
|
78
|
+
Further examples may be found in the ``unitsystem`` docs.
|
|
79
|
+
|
|
80
|
+
"""
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Realizations of unit systems."""
|
|
2
|
+
# pylint: disable=no-member
|
|
3
|
+
|
|
4
|
+
__all__ = (
|
|
5
|
+
# unit system instance
|
|
6
|
+
"galactic",
|
|
7
|
+
"dimensionless",
|
|
8
|
+
"solarsystem",
|
|
9
|
+
"si",
|
|
10
|
+
"cgs",
|
|
11
|
+
# unit system alias
|
|
12
|
+
"NAMED_UNIT_SYSTEMS",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from .base import AbstractUnitSystem
|
|
16
|
+
from .builtin import (
|
|
17
|
+
CGSUnitSystem,
|
|
18
|
+
DimensionlessUnitSystem,
|
|
19
|
+
LTMAUnitSystem,
|
|
20
|
+
SIUnitSystem,
|
|
21
|
+
)
|
|
22
|
+
from unxt.units import unit
|
|
23
|
+
|
|
24
|
+
# Dimensionless. This is a singleton.
|
|
25
|
+
dimensionless = DimensionlessUnitSystem()
|
|
26
|
+
|
|
27
|
+
# Galactic unit system
|
|
28
|
+
galactic = LTMAUnitSystem(
|
|
29
|
+
length=unit("kpc"),
|
|
30
|
+
time=unit("Myr"),
|
|
31
|
+
mass=unit("Msun"),
|
|
32
|
+
angle=unit("radian"),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Solar system units
|
|
36
|
+
solarsystem = LTMAUnitSystem(
|
|
37
|
+
length=unit("au"), time=unit("yr"), mass=unit("Msun"), angle=unit("radian")
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
#: International System of Units
|
|
41
|
+
si = SIUnitSystem(
|
|
42
|
+
length=unit("meter"),
|
|
43
|
+
time=unit("second"),
|
|
44
|
+
mass=unit("kilogram"),
|
|
45
|
+
electric_current=unit("ampere"),
|
|
46
|
+
temperature=unit("Kelvin"),
|
|
47
|
+
amount=unit("mole"),
|
|
48
|
+
luminous_intensity=unit("candela"),
|
|
49
|
+
angle=unit("radian"),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
#: Centimeter, gram, second unit system
|
|
53
|
+
cgs = CGSUnitSystem(
|
|
54
|
+
length=unit("centimeter"),
|
|
55
|
+
time=unit("second"),
|
|
56
|
+
mass=unit("gram"),
|
|
57
|
+
angle=unit("radian"),
|
|
58
|
+
force=unit("dyne"),
|
|
59
|
+
energy=unit("erg"),
|
|
60
|
+
pressure=unit("barye"),
|
|
61
|
+
dynamic_viscosity=unit("poise"),
|
|
62
|
+
kinematic_viscosity=unit("stokes"),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
#: Named unit systems
|
|
67
|
+
NAMED_UNIT_SYSTEMS: dict[str, AbstractUnitSystem] = {
|
|
68
|
+
"galactic": galactic,
|
|
69
|
+
"solarsystem": solarsystem,
|
|
70
|
+
"dimensionless": dimensionless,
|
|
71
|
+
"si": si,
|
|
72
|
+
"cgs": cgs,
|
|
73
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Unit system utils."""
|
|
2
|
+
|
|
3
|
+
__all__: tuple[str, ...] = ()
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from plum import dispatch
|
|
9
|
+
|
|
10
|
+
from zeroth import zeroth
|
|
11
|
+
|
|
12
|
+
from . import builtin_dimensions as bdims
|
|
13
|
+
from unxt.dims import AbstractDimension, dimension_of
|
|
14
|
+
from unxt.units import AbstractUnit
|
|
15
|
+
|
|
16
|
+
# ------------------------------------
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dispatch.abstract
|
|
20
|
+
def parse_dimlike_name(pt: Any, /) -> str:
|
|
21
|
+
"""Get the dimension name from the object."""
|
|
22
|
+
raise NotImplementedError # pragma: no cover
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dispatch
|
|
26
|
+
def parse_dimlike_name(pt: str, /) -> str:
|
|
27
|
+
"""Return the dimension name.
|
|
28
|
+
|
|
29
|
+
Note that this does not check for the existence of that dimension.
|
|
30
|
+
|
|
31
|
+
Examples
|
|
32
|
+
--------
|
|
33
|
+
>>> from unxt._src.unitsystems.utils import parse_dimlike_name
|
|
34
|
+
|
|
35
|
+
>>> parse_dimlike_name("length")
|
|
36
|
+
'length'
|
|
37
|
+
|
|
38
|
+
>>> parse_dimlike_name("not real")
|
|
39
|
+
'not_real'
|
|
40
|
+
|
|
41
|
+
>>> try:
|
|
42
|
+
... parse_dimlike_name("*62")
|
|
43
|
+
... except ValueError as e:
|
|
44
|
+
... print(e)
|
|
45
|
+
Input contains non-letter characters
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
# A regex search to match anything that's not a letter or a whitespace.
|
|
49
|
+
if re.search(r"[^a-zA-Z_ ]", pt):
|
|
50
|
+
msg = "Input contains non-letter characters"
|
|
51
|
+
raise ValueError(msg)
|
|
52
|
+
|
|
53
|
+
return pt.replace(" ", "_")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dispatch
|
|
57
|
+
def parse_dimlike_name(pt: AbstractDimension, /) -> str:
|
|
58
|
+
"""Return the dimension name from a dimension.
|
|
59
|
+
|
|
60
|
+
Examples
|
|
61
|
+
--------
|
|
62
|
+
>>> import unxt as u
|
|
63
|
+
>>> from unxt._src.unitsystems.utils import parse_dimlike_name
|
|
64
|
+
|
|
65
|
+
>>> parse_dimlike_name(u.dimension("length"))
|
|
66
|
+
'length'
|
|
67
|
+
|
|
68
|
+
>>> parse_dimlike_name(u.dimension("speed"))
|
|
69
|
+
'speed'
|
|
70
|
+
|
|
71
|
+
"""
|
|
72
|
+
# Note: this is not deterministic b/c ``_physical_type`` is a set
|
|
73
|
+
# that's why the `if` statement is needed.
|
|
74
|
+
match pt:
|
|
75
|
+
case bdims.speed:
|
|
76
|
+
out = "speed"
|
|
77
|
+
case _:
|
|
78
|
+
out = parse_dimlike_name(zeroth(pt._physical_type)) # noqa: SLF001
|
|
79
|
+
return out
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dispatch
|
|
83
|
+
def parse_dimlike_name(pt: AbstractUnit, /) -> str:
|
|
84
|
+
"""Return the dimension name from a unit.
|
|
85
|
+
|
|
86
|
+
Examples
|
|
87
|
+
--------
|
|
88
|
+
>>> import unxt as u
|
|
89
|
+
>>> from unxt._src.unitsystems.utils import parse_dimlike_name
|
|
90
|
+
|
|
91
|
+
>>> parse_dimlike_name(u.unit("km"))
|
|
92
|
+
'length'
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
return parse_dimlike_name(dimension_of(pt))
|
unxt/_src/utils.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Utilities.
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Galactic Dynamics. All rights reserved.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__all__: tuple[str, ...] = ()
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Protocol, TypeVar, cast, runtime_checkable
|
|
9
|
+
|
|
10
|
+
from jax import dtypes
|
|
11
|
+
from jax.numpy import dtype as DType # noqa: N812
|
|
12
|
+
|
|
13
|
+
import quaxed.lax as qlax
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from typing import Self
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_singleton_insts: dict[type, object] = {}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SingletonMixin:
|
|
23
|
+
"""Singleton class.
|
|
24
|
+
|
|
25
|
+
This class is a mixin that can be used to create singletons.
|
|
26
|
+
|
|
27
|
+
Examples
|
|
28
|
+
--------
|
|
29
|
+
>>> class MySingleton(SingletonMixin):
|
|
30
|
+
... pass
|
|
31
|
+
|
|
32
|
+
>>> a = MySingleton()
|
|
33
|
+
>>> b = MySingleton()
|
|
34
|
+
>>> a is b
|
|
35
|
+
True
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __new__(cls, /, *_: Any, **__: Any) -> "Self":
|
|
40
|
+
# Check if instance already exists
|
|
41
|
+
if cls in _singleton_insts:
|
|
42
|
+
return cast("Self", _singleton_insts[cls])
|
|
43
|
+
# Create new instance and cache it
|
|
44
|
+
self = object.__new__(cls)
|
|
45
|
+
_singleton_insts[cls] = self
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@runtime_checkable
|
|
50
|
+
class HasDType(Protocol):
|
|
51
|
+
"""Protocol for objects that have a dtype attribute."""
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def dtype(self) -> DType:
|
|
55
|
+
"""The dtype of the object."""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
HasDTypeT = TypeVar("HasDTypeT", bound=HasDType)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def promote_dtypes(*arrays: HasDTypeT) -> tuple[HasDTypeT, ...]:
|
|
62
|
+
"""Promotes all input arrays to a common dtype.
|
|
63
|
+
|
|
64
|
+
Examples
|
|
65
|
+
--------
|
|
66
|
+
>>> import jax.numpy as jnp
|
|
67
|
+
>>> import unxt as u
|
|
68
|
+
|
|
69
|
+
>>> x1 = jnp.array([1, 2, 3], dtype=jnp.int32)
|
|
70
|
+
>>> x2 = jnp.array([4, 5, 6], dtype=jnp.float32)
|
|
71
|
+
|
|
72
|
+
>>> x1, x2 = promote_dtypes(x1, x2)
|
|
73
|
+
>>> x1.dtype, x2.dtype
|
|
74
|
+
(dtype('float32'), dtype('float32'))
|
|
75
|
+
|
|
76
|
+
>>> q1 = u.Quantity.from_([1, 2, 3], "m", dtype=int)
|
|
77
|
+
>>> q2 = u.Q([4.0, 5, 6], unit="km")
|
|
78
|
+
>>> q1, q2 = promote_dtypes(q1, q2)
|
|
79
|
+
>>> q1.dtype, q2.dtype
|
|
80
|
+
(dtype('float32'), dtype('float32'))
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
common_dtype = dtypes.result_type(*arrays)
|
|
84
|
+
# TODO: check if this copies.
|
|
85
|
+
return tuple([qlax.convert_element_type(arr, common_dtype) for arr in arrays]) # type: ignore[arg-type,misc] # pylint: disable=R1728
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def promote_dtypes_if_needed(
|
|
89
|
+
original_dtypes: tuple[DType, ...], /, *args: HasDTypeT
|
|
90
|
+
) -> tuple[HasDTypeT, ...]:
|
|
91
|
+
"""Promotes dtypes of args if they differ from original_dtypes.
|
|
92
|
+
|
|
93
|
+
Examples
|
|
94
|
+
--------
|
|
95
|
+
>>> import jax.numpy as jnp
|
|
96
|
+
>>> import unxt as u
|
|
97
|
+
>>> from unxt._src.utils import promote_dtypes_if_needed
|
|
98
|
+
>>> x1 = jnp.array([1, 2, 3], dtype=jnp.int32)
|
|
99
|
+
>>> x2 = jnp.array([4, 5, 6], dtype=jnp.float32)
|
|
100
|
+
>>> original_dtypes = (x1.dtype, x1.dtype)
|
|
101
|
+
>>> x1_new, x2_new = promote_dtypes_if_needed(original_dtypes, x1, x2)
|
|
102
|
+
>>> x1_new.dtype, x2_new.dtype
|
|
103
|
+
(dtype('float32'), dtype('float32'))
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
# Compare equality of all `original_dtypes` and all `args` dtypes.
|
|
107
|
+
all_orig_dtypes_eq = all(dt == original_dtypes[0] for dt in original_dtypes)
|
|
108
|
+
all_new_dtypes_eq = all(arg.dtype == args[0].dtype for arg in args)
|
|
109
|
+
if all_orig_dtypes_eq and not all_new_dtypes_eq:
|
|
110
|
+
return promote_dtypes(*args)
|
|
111
|
+
|
|
112
|
+
return args
|
unxt/_version.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '0.1.dev463'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 'dev463')
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
unxt/_version.pyi
ADDED