forked from yt-project/unyt
/
unit_systems.py
344 lines (298 loc) · 11.9 KB
/
unit_systems.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
"""
Unit system class.
"""
from collections import OrderedDict
from unyt import dimensions
from unyt._parsing import parse_unyt_expr
from unyt._unit_lookup_table import (
default_unit_symbol_lut as default_lut,
inv_name_alternatives,
physical_constants,
unit_prefixes,
)
from unyt.exceptions import IllDefinedUnitSystem, MissingMKSCurrent, UnitsNotReducible
def add_symbols(namespace, registry):
"""Adds the unit symbols from :mod:`unyt.unit_symbols` to a namespace
Parameters
----------
namespace : dict
The dict to insert unit symbols into. The keys will be string
unit names and values will be the corresponding unit objects.
registry : :class:`unyt.unit_registry.UnitRegistry`
The registry to create units from. Note that if you would like to
use a custom unit system, ensure your registry was created using
that unit system.
Example
-------
>>> from unyt.unit_registry import UnitRegistry
>>> class MyClass():
... def __init__(self):
... self.reg = UnitRegistry()
... add_symbols(vars(self), self.reg)
>>> foo = MyClass()
>>> foo.kilometer
km
>>> foo.joule
J
"""
import unyt.unit_symbols as us
from unyt.unit_object import Unit
for name, unit in vars(us).items():
if name.startswith("_"):
continue
namespace[name] = Unit(unit.expr, registry=registry)
for name in [k for k in registry.keys() if k not in namespace]:
namespace[name] = Unit(name, registry=registry)
def add_constants(namespace, registry):
"""Adds the quantities from :mod:`unyt.physical_constants` to a namespace
Parameters
----------
namespace : dict
The dict to insert quantities into. The keys will be string names
and values will be the corresponding quantities.
registry : :class:`unyt.unit_registry.UnitRegistry`
The registry to create units from. Note that if you would like to
use a custom unit system, ensure your registry was created using
that unit system.
Example
-------
>>> from unyt.unit_registry import UnitRegistry
>>> class MyClass():
... def __init__(self):
... self.reg = UnitRegistry(unit_system='cgs')
... add_constants(vars(self), self.reg)
>>> foo = MyClass()
>>> foo.gravitational_constant
unyt_quantity(6.67408e-08, 'cm**3/(g*s**2)')
>>> foo.speed_of_light
unyt_quantity(2.99792458e+10, 'cm/s')
"""
from unyt.array import unyt_quantity
for constant_name in physical_constants:
value, unit_name, alternate_names = physical_constants[constant_name]
for name in alternate_names + [constant_name]:
quan = unyt_quantity(value, unit_name, registry=registry)
try:
namespace[name] = quan.in_base(unit_system=registry.unit_system)
except UnitsNotReducible:
namespace[name] = quan
namespace[name + "_mks"] = unyt_quantity(
value, unit_name, registry=registry
)
try:
namespace[name + "_cgs"] = quan.in_cgs()
except UnitsNotReducible:
pass
if name == "h":
# backward compatibility for unyt 1.0, which defined hmks
namespace["hmks"] = namespace["h_mks"].copy()
namespace["hcgs"] = namespace["h_cgs"].copy()
def _split_prefix(symbol_str, unit_symbol_lut):
possible_prefix = symbol_str[0]
if symbol_str[:2] == "da":
possible_prefix = "da"
if possible_prefix in unit_prefixes:
# the first character could be a prefix, check the rest of the symbol
symbol_wo_pref = symbol_str[1:]
# deca is the only prefix with length 2
if symbol_str[:2] == "da":
symbol_wo_pref = symbol_str[2:]
possible_prefix = "da"
entry = unit_symbol_lut.get(symbol_wo_pref, None)
if entry and entry[4]:
return possible_prefix, symbol_wo_pref
return "", symbol_str
def _get_system_unit_string(dims, base_units):
# The dimensions of a unit object is the product of the base dimensions.
# Use sympy to factor the dimensions into base CGS unit symbols.
units = []
my_dims = dims.expand()
if my_dims is dimensions.dimensionless:
return ""
for factor in my_dims.as_ordered_factors():
dim = list(factor.free_symbols)[0]
unit_string = str(base_units[dim])
if factor.is_Pow:
power_string = f"**({factor.as_base_exp()[1]})"
else:
power_string = ""
units.append(f"({unit_string}){power_string}")
return " * ".join(units)
unit_system_registry = {}
cmks = dimensions.current_mks
class UnitSystem:
"""
Create a UnitSystem for facilitating conversions to a default set of units.
Parameters
----------
name : string
The name of the unit system. Will be used as the key in the
*unit_system_registry* dict to reference the unit system by.
length_unit : string or :class:`unyt.unit_object.Unit`
The base length unit of this unit system.
mass_unit : string or :class:`unyt.unit_object.Unit`
The base mass unit of this unit system.
time_unit : string or :class:`unyt.unit_object.Unit`
The base time unit of this unit system.
temperature_unit : string or :class:`unyt.unit_object.Unit`, optional
The base temperature unit of this unit system. Defaults to "K".
angle_unit : string or :class:`unyt.unit_object.Unit`, optional
The base angle unit of this unit system. Defaults to "rad".
mks_system: boolean, optional
Whether or not this unit system has SI-specific units.
Default: False
current_mks_unit : string or :class:`unyt.unit_object.Unit`, optional
The base current unit of this unit system. Defaults to "A".
luminous_intensity_unit : string or :class:`unyt.unit_object.Unit`, optional
The base luminous intensity unit of this unit system.
Defaults to "cd".
registry : :class:`unyt.unit_registry.UnitRegistry` object
The unit registry associated with this unit system. Only
useful for defining unit systems based on code units.
"""
def __init__(
self,
name,
length_unit,
mass_unit,
time_unit,
temperature_unit="K",
angle_unit="rad",
current_mks_unit="A",
luminous_intensity_unit="cd",
logarithmic_unit="Np",
registry=None,
):
self.registry = registry
self.units_map = OrderedDict(
[
(dimensions.length, length_unit),
(dimensions.mass, mass_unit),
(dimensions.time, time_unit),
(dimensions.temperature, temperature_unit),
(dimensions.angle, angle_unit),
(dimensions.current_mks, current_mks_unit),
(dimensions.luminous_intensity, luminous_intensity_unit),
(dimensions.logarithmic, logarithmic_unit),
]
)
for k, v in self.units_map.items():
if v is not None:
if hasattr(v, "value") and hasattr(v, "units"):
self.units_map[k] = v.value * v.units.expr
else:
self.units_map[k] = parse_unyt_expr(str(v))
for dimension, unit in self.units_map.items():
# CGS sets the current_mks unit to none, so catch it here
if unit is None and dimension is dimensions.current_mks:
continue
if unit.is_Mul:
unit = unit.as_coeff_Mul()[1]
if (
self.registry is not None
and self.registry[str(unit)][1] is not dimension
):
raise IllDefinedUnitSystem(self.units_map)
elif self.registry is None:
bu = _split_prefix(str(unit), default_lut)[1]
inferred_dimension = default_lut[inv_name_alternatives[bu]][1]
if inferred_dimension is not dimension:
raise IllDefinedUnitSystem(self.units_map)
self._dims = [
"length",
"mass",
"time",
"temperature",
"angle",
"current_mks",
"luminous_intensity",
"logarithmic",
]
self.registry = registry
self.base_units = self.units_map.copy()
unit_system_registry[name] = self
self.name = name
def __getitem__(self, key):
from unyt.unit_object import Unit
if isinstance(key, str):
key = getattr(dimensions, key)
um = self.units_map
if key not in um or um[key] is None:
if cmks in key.free_symbols and self.units_map[cmks] is None:
raise MissingMKSCurrent(self.name)
units = _get_system_unit_string(key, self.units_map)
self.units_map[key] = parse_unyt_expr(units)
return Unit(units, registry=self.registry)
return Unit(self.units_map[key], registry=self.registry)
def __setitem__(self, key, value):
if isinstance(key, str):
if key not in self._dims:
self._dims.append(key)
key = getattr(dimensions, key)
if self.units_map[cmks] is None and cmks in key.free_symbols:
raise MissingMKSCurrent(self.name)
self.units_map[key] = parse_unyt_expr(str(value))
def __str__(self):
return self.name
def __repr__(self):
repr = f"{self.name} Unit System\n"
repr += " Base Units:\n"
for dim in self.base_units:
if self.base_units[dim] is not None:
repr += f" {str(dim).strip('()')}: {self.base_units[dim]}\n"
repr += " Other Units:\n"
for key in self._dims:
dim = getattr(dimensions, key)
if dim not in self.base_units:
repr += f" {key}: {self.units_map[dim]}\n"
return repr[:-1]
@property
def has_current_mks(self):
"""Does this unit system have an MKS current dimension?"""
return self.units_map[cmks] is not None
#: The CGS unit system
cgs_unit_system = UnitSystem("cgs", "cm", "g", "s", current_mks_unit=None)
cgs_unit_system["energy"] = "erg"
cgs_unit_system["specific_energy"] = "erg/g"
cgs_unit_system["pressure"] = "dyne/cm**2"
cgs_unit_system["force"] = "dyne"
cgs_unit_system["magnetic_field_cgs"] = "gauss"
cgs_unit_system["charge_cgs"] = "esu"
cgs_unit_system["current_cgs"] = "statA"
cgs_unit_system["power"] = "erg/s"
#: The MKS unit system
mks_unit_system = UnitSystem("mks", "m", "kg", "s")
mks_unit_system["energy"] = "J"
mks_unit_system["specific_energy"] = "J/kg"
mks_unit_system["pressure"] = "Pa"
mks_unit_system["force"] = "N"
mks_unit_system["magnetic_field"] = "T"
mks_unit_system["charge"] = "C"
mks_unit_system["frequency"] = "Hz"
mks_unit_system["power"] = "W"
mks_unit_system["electric_potential"] = "V"
mks_unit_system["capacitance"] = "F"
mks_unit_system["inductance"] = "H"
mks_unit_system["resistance"] = "ohm"
mks_unit_system["magnetic_flux"] = "Wb"
mks_unit_system["luminous_flux"] = "lm"
#: The imperial unit system
imperial_unit_system = UnitSystem("imperial", "ft", "lb", "s", temperature_unit="R")
imperial_unit_system["force"] = "lbf"
imperial_unit_system["energy"] = "ft*lbf"
imperial_unit_system["pressure"] = "lbf/ft**2"
imperial_unit_system["power"] = "hp"
#: The galactic unit system
galactic_unit_system = UnitSystem("galactic", "kpc", "Msun", "Myr")
galactic_unit_system["energy"] = "keV"
galactic_unit_system["magnetic_field_cgs"] = "uG"
#: The solar unit system
solar_unit_system = UnitSystem("solar", "AU", "Mearth", "yr")
#: Geometrized unit system
geometrized_unit_system = UnitSystem("geometrized", "l_geom", "m_geom", "t_geom")
#: Planck unit system
planck_unit_system = UnitSystem(
"planck", "l_pl", "m_pl", "t_pl", temperature_unit="T_pl"
)
planck_unit_system["energy"] = "E_pl"
planck_unit_system["charge_mks"] = "q_pl"