aesoptparam 0.3.6__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 aesoptparam might be problematic. Click here for more details.
- aesoptparam/__init__.py +13 -0
- aesoptparam/example.py +110 -0
- aesoptparam/parameterized.py +417 -0
- aesoptparam/parameters.py +641 -0
- aesoptparam/serializer.py +94 -0
- aesoptparam/test/dummy_instance.json +30 -0
- aesoptparam/test/dummy_schema.json +752 -0
- aesoptparam/test/test_parameterized.py +574 -0
- aesoptparam/test/test_parameters.py +519 -0
- aesoptparam/test/test_units.py +369 -0
- aesoptparam/test/test_utils.py +147 -0
- aesoptparam/utils/__init__.py +63 -0
- aesoptparam/utils/html_repr.py +533 -0
- aesoptparam/utils/json_utils.py +127 -0
- aesoptparam/utils/unit_library.ini +233 -0
- aesoptparam/utils/units.py +1060 -0
- aesoptparam-0.3.6.dist-info/METADATA +86 -0
- aesoptparam-0.3.6.dist-info/RECORD +21 -0
- aesoptparam-0.3.6.dist-info/WHEEL +5 -0
- aesoptparam-0.3.6.dist-info/licenses/LICENSE +21 -0
- aesoptparam-0.3.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1060 @@
|
|
|
1
|
+
"""
|
|
2
|
+
From: https://github.com/OpenMDAO/OpenMDAO/blob/master/openmdao/utils/units.py
|
|
3
|
+
Classes and functions to support unit conversion.
|
|
4
|
+
|
|
5
|
+
The module provides a basic set of predefined physical quantities
|
|
6
|
+
in its built-in library; however, it also supports generation of
|
|
7
|
+
personal libararies which can be saved and reused.
|
|
8
|
+
This module is based on the PhysicalQuantities module
|
|
9
|
+
in Scientific Python, by Konrad Hinsen. Modifications by
|
|
10
|
+
Justin Gray.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os.path
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
from collections import OrderedDict
|
|
17
|
+
from configparser import RawConfigParser as ConfigParser
|
|
18
|
+
from math import floor, pi
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
####################################
|
|
23
|
+
# Class Definitions
|
|
24
|
+
####################################
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class NumberDict(OrderedDict):
|
|
28
|
+
"""
|
|
29
|
+
Dictionary storing numerical values.
|
|
30
|
+
|
|
31
|
+
An instance of this class acts like an array of numbers with
|
|
32
|
+
generalized (non-integer) indices. A value of zero is assumed
|
|
33
|
+
for undefined entries. NumberDict instances support addition
|
|
34
|
+
and subtraction with other NumberDict instances, and multiplication
|
|
35
|
+
and division by scalars.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __getitem__(self, item):
|
|
39
|
+
"""
|
|
40
|
+
Get the item, or 0.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
item : key
|
|
45
|
+
key to get the item
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
int
|
|
50
|
+
value of the given key
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
return dict.__getitem__(self, item)
|
|
54
|
+
except KeyError:
|
|
55
|
+
return 0
|
|
56
|
+
|
|
57
|
+
def __add__(self, other):
|
|
58
|
+
"""
|
|
59
|
+
Add another NumberDict to myself.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
other : NumberDict
|
|
64
|
+
the other NumberDict Instance
|
|
65
|
+
|
|
66
|
+
Returns
|
|
67
|
+
-------
|
|
68
|
+
NumberDict
|
|
69
|
+
new NumberDict with self+other values
|
|
70
|
+
"""
|
|
71
|
+
sum_dict = NumberDict()
|
|
72
|
+
for k, v in self.items():
|
|
73
|
+
sum_dict[k] = v
|
|
74
|
+
for k, v in other.items():
|
|
75
|
+
sum_dict[k] = sum_dict[k] + v
|
|
76
|
+
return sum_dict
|
|
77
|
+
|
|
78
|
+
def __sub__(self, other):
|
|
79
|
+
"""
|
|
80
|
+
Add another NumberDict from myself.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
other : NumberDict
|
|
85
|
+
the other NumberDict Instance
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
NumberDict
|
|
90
|
+
new NumberDict instance, with self-other values
|
|
91
|
+
"""
|
|
92
|
+
sum_dict = NumberDict()
|
|
93
|
+
for k, v in self.items():
|
|
94
|
+
sum_dict[k] = v
|
|
95
|
+
for k, v in other.items():
|
|
96
|
+
sum_dict[k] = sum_dict[k] - v
|
|
97
|
+
return sum_dict
|
|
98
|
+
|
|
99
|
+
def __rsub__(self, other):
|
|
100
|
+
"""
|
|
101
|
+
Add subtract myself from another NumberDict.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
other : NumberDict
|
|
106
|
+
the other NumberDict Instance
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
NumberDict
|
|
111
|
+
new NumberDict instance, with other-self values
|
|
112
|
+
"""
|
|
113
|
+
sum_dict = NumberDict()
|
|
114
|
+
for k, v in other.items():
|
|
115
|
+
sum_dict[k] = v
|
|
116
|
+
for k, v in self.items():
|
|
117
|
+
sum_dict[k] = sum_dict[k] - v
|
|
118
|
+
return sum_dict
|
|
119
|
+
|
|
120
|
+
def __mul__(self, other):
|
|
121
|
+
"""
|
|
122
|
+
Multiply myself by another NumberDict.
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
other : NumberDict
|
|
127
|
+
the other NumberDict Instance
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
NumberDict
|
|
132
|
+
new NumberDict instance, with other*self values
|
|
133
|
+
"""
|
|
134
|
+
new = NumberDict()
|
|
135
|
+
for key, value in self.items():
|
|
136
|
+
new[key] = other * value
|
|
137
|
+
return new
|
|
138
|
+
|
|
139
|
+
__rmul__ = __mul__
|
|
140
|
+
|
|
141
|
+
def __div__(self, other):
|
|
142
|
+
"""
|
|
143
|
+
Divide myself by another NumberDict.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
other : int
|
|
148
|
+
value to divide by
|
|
149
|
+
|
|
150
|
+
Returns
|
|
151
|
+
-------
|
|
152
|
+
NumberDict
|
|
153
|
+
new NumberDict instance, with self/other values
|
|
154
|
+
"""
|
|
155
|
+
new = NumberDict()
|
|
156
|
+
for key, value in self.items():
|
|
157
|
+
new[key] = value / other
|
|
158
|
+
return new
|
|
159
|
+
|
|
160
|
+
__truediv__ = __div__ # for python 3
|
|
161
|
+
|
|
162
|
+
def __repr__(self):
|
|
163
|
+
"""
|
|
164
|
+
Return a string deceleration of myself.
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
other : NumberDict
|
|
169
|
+
the other NumberDict Instance
|
|
170
|
+
|
|
171
|
+
Returns
|
|
172
|
+
-------
|
|
173
|
+
str
|
|
174
|
+
str representation for the creation of this NumberDict
|
|
175
|
+
"""
|
|
176
|
+
return repr(dict(self))
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class PhysicalUnit(object):
|
|
180
|
+
"""
|
|
181
|
+
Physical unit.
|
|
182
|
+
|
|
183
|
+
A physical unit is defined by a name (possibly composite), a scaling
|
|
184
|
+
factor, and the exponentials of each of the SI base units that enter into
|
|
185
|
+
it. Units can be multiplied, divided, and raised to integer powers.
|
|
186
|
+
|
|
187
|
+
Parameters
|
|
188
|
+
----------
|
|
189
|
+
names : dict or str
|
|
190
|
+
A dictionary mapping each name component to its
|
|
191
|
+
associated integer power (e.g., C{{'m': 1, 's': -1}})
|
|
192
|
+
for M{m/s}). As a shorthand, a string may be passed
|
|
193
|
+
which is assigned an implicit power 1.
|
|
194
|
+
factor : float
|
|
195
|
+
A scaling factor.
|
|
196
|
+
powers : list of int
|
|
197
|
+
The integer powers for each of the nine base units.
|
|
198
|
+
offset : float
|
|
199
|
+
An additive offset to the base unit (used only for temperatures).
|
|
200
|
+
|
|
201
|
+
Attributes
|
|
202
|
+
----------
|
|
203
|
+
_names : dict or str
|
|
204
|
+
A dictionary mapping each name component to its
|
|
205
|
+
associated integer power (e.g., C{{'m': 1, 's': -1}})
|
|
206
|
+
for M{m/s}). As a shorthand, a string may be passed
|
|
207
|
+
which is assigned an implicit power 1.
|
|
208
|
+
_factor : float
|
|
209
|
+
A scaling factor.
|
|
210
|
+
_powers : list of int
|
|
211
|
+
The integer powers for each of the nine base units.
|
|
212
|
+
_offset : float
|
|
213
|
+
An additive offset to the base unit (used only for temperatures)
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
def __init__(self, names, factor, powers, offset=0):
|
|
217
|
+
"""
|
|
218
|
+
Initialize all attributes.
|
|
219
|
+
"""
|
|
220
|
+
if isinstance(names, str):
|
|
221
|
+
self._names = NumberDict(((names, 1),))
|
|
222
|
+
else:
|
|
223
|
+
self._names = names
|
|
224
|
+
|
|
225
|
+
self._factor = float(factor)
|
|
226
|
+
self._offset = float(offset)
|
|
227
|
+
self._powers = powers
|
|
228
|
+
|
|
229
|
+
def __repr__(self):
|
|
230
|
+
"""
|
|
231
|
+
Get the string representation of this unit.
|
|
232
|
+
|
|
233
|
+
Returns
|
|
234
|
+
-------
|
|
235
|
+
str
|
|
236
|
+
str representation of how to instantiate this PhysicalUnit
|
|
237
|
+
"""
|
|
238
|
+
return "PhysicalUnit(%s,%s,%s,%s)" % (
|
|
239
|
+
self._names,
|
|
240
|
+
self._factor,
|
|
241
|
+
self._powers,
|
|
242
|
+
self._offset,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
def __str__(self):
|
|
246
|
+
"""
|
|
247
|
+
Convert myself to string.
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
str
|
|
252
|
+
str representation of a PhysicalUnit
|
|
253
|
+
"""
|
|
254
|
+
return "<PhysicalUnit " + self.name() + ">"
|
|
255
|
+
|
|
256
|
+
def __lt__(self, other):
|
|
257
|
+
"""
|
|
258
|
+
Compare myself to other.
|
|
259
|
+
|
|
260
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
other : PhysicalUnit
|
|
263
|
+
The other physical unit to be compared to
|
|
264
|
+
|
|
265
|
+
Returns
|
|
266
|
+
-------
|
|
267
|
+
bool
|
|
268
|
+
self._factor < other._factor
|
|
269
|
+
"""
|
|
270
|
+
if self._powers != other._powers or self._offset != other._offset:
|
|
271
|
+
raise TypeError(
|
|
272
|
+
f"Units '{self.name()}' and '{other.name()}' are incompatible."
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
return self._factor < other._factor
|
|
276
|
+
|
|
277
|
+
def __gt__(self, other):
|
|
278
|
+
"""
|
|
279
|
+
Compare myself to other.
|
|
280
|
+
|
|
281
|
+
Parameters
|
|
282
|
+
----------
|
|
283
|
+
other : PhysicalUnit
|
|
284
|
+
The other physical unit to be compared to
|
|
285
|
+
|
|
286
|
+
Returns
|
|
287
|
+
-------
|
|
288
|
+
bool
|
|
289
|
+
self._factor > other._factor
|
|
290
|
+
"""
|
|
291
|
+
if self._powers != other._powers:
|
|
292
|
+
raise TypeError(
|
|
293
|
+
f"Units '{self.name()}' and '{other.name()}' are incompatible."
|
|
294
|
+
)
|
|
295
|
+
return self._factor > other._factor
|
|
296
|
+
|
|
297
|
+
def __eq__(self, other):
|
|
298
|
+
"""
|
|
299
|
+
Test for equality.
|
|
300
|
+
|
|
301
|
+
Parameters
|
|
302
|
+
----------
|
|
303
|
+
other : PhysicalUnit
|
|
304
|
+
The other physical unit to be compared to
|
|
305
|
+
|
|
306
|
+
Returns
|
|
307
|
+
-------
|
|
308
|
+
bool
|
|
309
|
+
true if _factor, _offset, and _powers all match
|
|
310
|
+
"""
|
|
311
|
+
return (
|
|
312
|
+
self._factor == other._factor
|
|
313
|
+
and self._offset == other._offset
|
|
314
|
+
and self.is_compatible(other)
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
def __mul__(self, other):
|
|
318
|
+
"""
|
|
319
|
+
Multiply myself by other.
|
|
320
|
+
|
|
321
|
+
Parameters
|
|
322
|
+
----------
|
|
323
|
+
other : PhysicalUnit
|
|
324
|
+
The other physical unit to be compared to
|
|
325
|
+
|
|
326
|
+
Returns
|
|
327
|
+
-------
|
|
328
|
+
PhysicalUnit
|
|
329
|
+
new PhysicalUnit instance representing the product of two units
|
|
330
|
+
"""
|
|
331
|
+
if self._offset != 0 or (
|
|
332
|
+
isinstance(other, PhysicalUnit) and other._offset != 0
|
|
333
|
+
):
|
|
334
|
+
raise TypeError(
|
|
335
|
+
f"Can't multiply units: either '{self.name()}' or '{other.name()}' "
|
|
336
|
+
"has a non-zero offset."
|
|
337
|
+
)
|
|
338
|
+
if isinstance(other, PhysicalUnit):
|
|
339
|
+
return PhysicalUnit(
|
|
340
|
+
self._names + other._names,
|
|
341
|
+
self._factor * other._factor,
|
|
342
|
+
[a + b for a, b in zip(self._powers, other._powers)],
|
|
343
|
+
)
|
|
344
|
+
else:
|
|
345
|
+
return PhysicalUnit(
|
|
346
|
+
self._names + {str(other): 1},
|
|
347
|
+
self._factor * other,
|
|
348
|
+
self._powers,
|
|
349
|
+
self._offset * other,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
__rmul__ = __mul__
|
|
353
|
+
|
|
354
|
+
def __div__(self, other):
|
|
355
|
+
"""
|
|
356
|
+
Divide myself by other.
|
|
357
|
+
|
|
358
|
+
Parameters
|
|
359
|
+
----------
|
|
360
|
+
other : PhysicalUnit
|
|
361
|
+
The other physical unit to be operated on
|
|
362
|
+
|
|
363
|
+
Returns
|
|
364
|
+
-------
|
|
365
|
+
PhysicalUnit
|
|
366
|
+
new PhysicalUnit instance representing the self/other
|
|
367
|
+
"""
|
|
368
|
+
if self._offset != 0 or (
|
|
369
|
+
isinstance(other, PhysicalUnit) and other._offset != 0
|
|
370
|
+
):
|
|
371
|
+
raise TypeError(
|
|
372
|
+
f"Can't divide units: either '{self.name()}' or '{other.name()}' "
|
|
373
|
+
"has a non-zero offset."
|
|
374
|
+
)
|
|
375
|
+
if isinstance(other, PhysicalUnit):
|
|
376
|
+
return PhysicalUnit(
|
|
377
|
+
self._names - other._names,
|
|
378
|
+
self._factor / other._factor,
|
|
379
|
+
[a - b for (a, b) in zip(self._powers, other._powers)],
|
|
380
|
+
)
|
|
381
|
+
else:
|
|
382
|
+
return PhysicalUnit(
|
|
383
|
+
self._names + {str(other): -1},
|
|
384
|
+
self._factor / float(other),
|
|
385
|
+
self._powers,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
__truediv__ = __div__ # for python 3
|
|
389
|
+
|
|
390
|
+
def __rdiv__(self, other):
|
|
391
|
+
"""
|
|
392
|
+
Divide other by myself.
|
|
393
|
+
|
|
394
|
+
Parameters
|
|
395
|
+
----------
|
|
396
|
+
other : PhysicalUnit
|
|
397
|
+
The other physical unit to be operated on
|
|
398
|
+
|
|
399
|
+
Returns
|
|
400
|
+
-------
|
|
401
|
+
PhysicalUnit
|
|
402
|
+
new PhysicalUnit instance representing the other/self
|
|
403
|
+
"""
|
|
404
|
+
return PhysicalUnit(
|
|
405
|
+
{str(other): 1} - self._names,
|
|
406
|
+
float(other) / self._factor,
|
|
407
|
+
[-x for x in self._powers],
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
__rtruediv__ = __rdiv__
|
|
411
|
+
|
|
412
|
+
def __pow__(self, power):
|
|
413
|
+
"""
|
|
414
|
+
Raise myself to a power.
|
|
415
|
+
|
|
416
|
+
Parameters
|
|
417
|
+
----------
|
|
418
|
+
power : float or int
|
|
419
|
+
power to raise self by
|
|
420
|
+
|
|
421
|
+
Returns
|
|
422
|
+
-------
|
|
423
|
+
PhysicalUnit
|
|
424
|
+
new PhysicalUnit of self^power
|
|
425
|
+
"""
|
|
426
|
+
if self._offset != 0:
|
|
427
|
+
raise TypeError(
|
|
428
|
+
f"Can't exponentiate unit '{self.name()}' because it "
|
|
429
|
+
"has a non-zero offset."
|
|
430
|
+
)
|
|
431
|
+
if isinstance(power, int):
|
|
432
|
+
return PhysicalUnit(
|
|
433
|
+
power * self._names,
|
|
434
|
+
pow(self._factor, power),
|
|
435
|
+
[x * power for x in self._powers],
|
|
436
|
+
)
|
|
437
|
+
if isinstance(power, float):
|
|
438
|
+
inv_exp = 1.0 / power
|
|
439
|
+
rounded = int(floor(inv_exp + 0.5))
|
|
440
|
+
if abs(inv_exp - rounded) < 1.0e-10:
|
|
441
|
+
if all([x % rounded == 0 for x in self._powers]):
|
|
442
|
+
f = self._factor**power
|
|
443
|
+
p = [x / rounded for x in self._powers]
|
|
444
|
+
if all([x % rounded == 0 for x in self._names.values()]):
|
|
445
|
+
names = self._names / rounded
|
|
446
|
+
else:
|
|
447
|
+
names = NumberDict()
|
|
448
|
+
if f != 1.0:
|
|
449
|
+
names[str(f)] = 1
|
|
450
|
+
for x, name in zip(p, _UNIT_LIB.base_names):
|
|
451
|
+
names[name] = x
|
|
452
|
+
return PhysicalUnit(names, f, p)
|
|
453
|
+
|
|
454
|
+
raise TypeError(
|
|
455
|
+
f"Can't exponentiate unit '{self.name()}': "
|
|
456
|
+
"only integer and inverse integer exponents are allowed."
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
def in_base_units(self):
|
|
460
|
+
"""
|
|
461
|
+
Return the base unit equivalent of this unit.
|
|
462
|
+
|
|
463
|
+
Returns
|
|
464
|
+
-------
|
|
465
|
+
PhysicalUnit
|
|
466
|
+
The equivalent base unit.
|
|
467
|
+
"""
|
|
468
|
+
num = ""
|
|
469
|
+
denom = ""
|
|
470
|
+
for unit, power in zip(_UNIT_LIB.base_names, self._powers):
|
|
471
|
+
if power < 0:
|
|
472
|
+
denom = denom + "/" + unit
|
|
473
|
+
if power < -1:
|
|
474
|
+
denom = denom + "**" + str(-power)
|
|
475
|
+
elif power > 0:
|
|
476
|
+
num = num + "*" + unit
|
|
477
|
+
if power > 1:
|
|
478
|
+
num = num + "**" + str(power)
|
|
479
|
+
|
|
480
|
+
if len(num) == 0:
|
|
481
|
+
num = "1"
|
|
482
|
+
else:
|
|
483
|
+
num = num[1:]
|
|
484
|
+
|
|
485
|
+
return _find_unit(num + denom)
|
|
486
|
+
|
|
487
|
+
def conversion_tuple_to(self, other):
|
|
488
|
+
"""
|
|
489
|
+
Compute the tuple of (factor, offset) for conversion.
|
|
490
|
+
|
|
491
|
+
Parameters
|
|
492
|
+
----------
|
|
493
|
+
other : PhysicalUnit
|
|
494
|
+
Another unit.
|
|
495
|
+
|
|
496
|
+
Returns
|
|
497
|
+
-------
|
|
498
|
+
Tuple with two floats
|
|
499
|
+
The conversion factor and offset from this unit to another unit.
|
|
500
|
+
"""
|
|
501
|
+
if not self.is_compatible(other):
|
|
502
|
+
raise TypeError(
|
|
503
|
+
f"Units '{self.name()}' and '{other.name()}' are incompatible."
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
# let (s1,d1) be the conversion tuple from 'self' to base units
|
|
507
|
+
# (ie. (x+d1)*s1 converts a value x from 'self' to base units,
|
|
508
|
+
# and (x/s1)-d1 converts x from base to 'self' units)
|
|
509
|
+
# and (s2,d2) be the conversion tuple from 'other' to base units
|
|
510
|
+
# then we want to compute the conversion tuple (S,D) from
|
|
511
|
+
# 'self' to 'other' such that (x+D)*S converts x from 'self'
|
|
512
|
+
# units to 'other' units
|
|
513
|
+
# the formula to convert x from 'self' to 'other' units via the
|
|
514
|
+
# base units is (by definition of the conversion tuples):
|
|
515
|
+
# ( ((x+d1)*s1) / s2 ) - d2
|
|
516
|
+
# = ( (x+d1) * s1/s2) - d2
|
|
517
|
+
# = ( (x+d1) * s1/s2 ) - (d2*s2/s1) * s1/s2
|
|
518
|
+
# = ( (x+d1) - (d1*s2/s1) ) * s1/s2
|
|
519
|
+
# = (x + d1 - d2*s2/s1) * s1/s2
|
|
520
|
+
# thus, D = d1 - d2*s2/s1 and S = s1/s2
|
|
521
|
+
|
|
522
|
+
factor = self._factor / other._factor
|
|
523
|
+
offset = self._offset - (other._offset * other._factor / self._factor)
|
|
524
|
+
return (factor, offset)
|
|
525
|
+
|
|
526
|
+
def is_compatible(self, other):
|
|
527
|
+
"""
|
|
528
|
+
Check for compatibility with another unit.
|
|
529
|
+
|
|
530
|
+
Parameters
|
|
531
|
+
----------
|
|
532
|
+
other : PhysicalUnit
|
|
533
|
+
Another unit.
|
|
534
|
+
|
|
535
|
+
Returns
|
|
536
|
+
-------
|
|
537
|
+
bool
|
|
538
|
+
Indicates if two units are compatible.
|
|
539
|
+
"""
|
|
540
|
+
return self._powers == other._powers
|
|
541
|
+
|
|
542
|
+
def is_dimensionless(self):
|
|
543
|
+
"""
|
|
544
|
+
Dimensionless PQ.
|
|
545
|
+
|
|
546
|
+
Returns
|
|
547
|
+
-------
|
|
548
|
+
bool
|
|
549
|
+
Indicates if this is dimensionless.
|
|
550
|
+
"""
|
|
551
|
+
return not any(self._powers)
|
|
552
|
+
|
|
553
|
+
def is_angle(self):
|
|
554
|
+
"""
|
|
555
|
+
Check if this PQ is an Angle.
|
|
556
|
+
|
|
557
|
+
Returns
|
|
558
|
+
-------
|
|
559
|
+
bool
|
|
560
|
+
Indicates if this an angle type.
|
|
561
|
+
"""
|
|
562
|
+
return (
|
|
563
|
+
self._powers[_UNIT_LIB.base_types["angle"]] == 1 and sum(self._powers) == 1
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
def set_name(self, name):
|
|
567
|
+
"""
|
|
568
|
+
Set the name.
|
|
569
|
+
|
|
570
|
+
Parameters
|
|
571
|
+
----------
|
|
572
|
+
name : str
|
|
573
|
+
The name.
|
|
574
|
+
"""
|
|
575
|
+
self._names = NumberDict()
|
|
576
|
+
self._names[name] = 1
|
|
577
|
+
|
|
578
|
+
def name(self):
|
|
579
|
+
"""
|
|
580
|
+
Compute the name of this unit.
|
|
581
|
+
|
|
582
|
+
Returns
|
|
583
|
+
-------
|
|
584
|
+
str
|
|
585
|
+
String representation of the unit.
|
|
586
|
+
"""
|
|
587
|
+
num = ""
|
|
588
|
+
denom = ""
|
|
589
|
+
for unit, power in self._names.items():
|
|
590
|
+
if power < 0:
|
|
591
|
+
denom = denom + "/" + unit
|
|
592
|
+
if power < -1:
|
|
593
|
+
denom = denom + "**" + str(-power)
|
|
594
|
+
elif power > 0:
|
|
595
|
+
num = num + "*" + unit
|
|
596
|
+
if power > 1:
|
|
597
|
+
num = num + "**" + str(power)
|
|
598
|
+
if len(num) == 0:
|
|
599
|
+
num = "1"
|
|
600
|
+
else:
|
|
601
|
+
num = num[1:]
|
|
602
|
+
return num + denom
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
####################################
|
|
606
|
+
# Module Functions
|
|
607
|
+
####################################
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def _new_unit(name, factor, powers):
|
|
611
|
+
"""
|
|
612
|
+
Create new Unit.
|
|
613
|
+
|
|
614
|
+
Parameters
|
|
615
|
+
----------
|
|
616
|
+
name : str
|
|
617
|
+
The name of the new unit
|
|
618
|
+
factor : float
|
|
619
|
+
conversion factor to base units
|
|
620
|
+
powers : [int, ...]
|
|
621
|
+
power of base units
|
|
622
|
+
|
|
623
|
+
"""
|
|
624
|
+
_UNIT_LIB.unit_table[name] = PhysicalUnit(name, factor, powers)
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def add_offset_unit(name, baseunit, factor, offset, comment=""):
|
|
628
|
+
"""
|
|
629
|
+
Adding Offset Unit.
|
|
630
|
+
|
|
631
|
+
Parameters
|
|
632
|
+
----------
|
|
633
|
+
name : str
|
|
634
|
+
The name of the unit.
|
|
635
|
+
baseunit : str or instance of PhysicalUnit
|
|
636
|
+
The unit upon which this offset unit is based.
|
|
637
|
+
factor : str
|
|
638
|
+
The scaling factor used to define the new unit w.r.t. base unit.
|
|
639
|
+
offset : float
|
|
640
|
+
Zero offset for new unit.
|
|
641
|
+
comment : str
|
|
642
|
+
Optional comment to describe unit.
|
|
643
|
+
"""
|
|
644
|
+
if isinstance(baseunit, str):
|
|
645
|
+
baseunit = _find_unit(baseunit)
|
|
646
|
+
# else, baseunit should be a instance of PhysicalUnit
|
|
647
|
+
# names, factor, powers, offset=0
|
|
648
|
+
unit = PhysicalUnit(
|
|
649
|
+
baseunit._names, baseunit._factor * factor, baseunit._powers, offset
|
|
650
|
+
)
|
|
651
|
+
unit.set_name(name)
|
|
652
|
+
if name in _UNIT_LIB.unit_table:
|
|
653
|
+
if (
|
|
654
|
+
_UNIT_LIB.unit_table[name]._factor != unit._factor
|
|
655
|
+
or _UNIT_LIB.unit_table[name]._powers != unit._powers
|
|
656
|
+
):
|
|
657
|
+
raise KeyError(
|
|
658
|
+
f"Unit '{name}' already defined with different factor or powers."
|
|
659
|
+
)
|
|
660
|
+
_UNIT_LIB.unit_table[name] = unit
|
|
661
|
+
_UNIT_LIB.set("units", name, unit)
|
|
662
|
+
if comment:
|
|
663
|
+
_UNIT_LIB.help.append((name, comment, unit))
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
def add_unit(name, unit, comment=""):
|
|
667
|
+
"""
|
|
668
|
+
Adding Unit.
|
|
669
|
+
|
|
670
|
+
Parameters
|
|
671
|
+
----------
|
|
672
|
+
name : str
|
|
673
|
+
The name of the unit being added. For example: 'Hz'.
|
|
674
|
+
unit : str
|
|
675
|
+
Definition of the unit w.r.t. some other unit. For example: '1/s'.
|
|
676
|
+
comment : str
|
|
677
|
+
Optional comment to describe unit.
|
|
678
|
+
"""
|
|
679
|
+
if comment:
|
|
680
|
+
_UNIT_LIB.help.append((name, comment, unit))
|
|
681
|
+
if isinstance(unit, str):
|
|
682
|
+
unit = eval(
|
|
683
|
+
unit,
|
|
684
|
+
{"__builtins__": None, "pi": pi}, # nosec: scope limited
|
|
685
|
+
_UNIT_LIB.unit_table,
|
|
686
|
+
)
|
|
687
|
+
unit.set_name(name)
|
|
688
|
+
if name in _UNIT_LIB.unit_table:
|
|
689
|
+
if (
|
|
690
|
+
_UNIT_LIB.unit_table[name]._factor != unit._factor
|
|
691
|
+
or _UNIT_LIB.unit_table[name]._powers != unit._powers
|
|
692
|
+
):
|
|
693
|
+
raise KeyError(
|
|
694
|
+
f"Unit '{name}' already defined with different factor or powers."
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
_UNIT_LIB.unit_table[name] = unit
|
|
698
|
+
_UNIT_LIB.set("units", name, unit)
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
_UNIT_LIB = ConfigParser()
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def _do_nothing(string):
|
|
705
|
+
"""
|
|
706
|
+
Make the ConfigParser case sensitive.
|
|
707
|
+
|
|
708
|
+
Defines an optionxform for the units configparser that
|
|
709
|
+
does nothing, resulting in a case-sensitive parser.
|
|
710
|
+
|
|
711
|
+
Parameters
|
|
712
|
+
----------
|
|
713
|
+
string : str
|
|
714
|
+
The string to be transformed for the ConfigParser
|
|
715
|
+
|
|
716
|
+
Returns
|
|
717
|
+
-------
|
|
718
|
+
str
|
|
719
|
+
The same string that was given as a parameter.
|
|
720
|
+
"""
|
|
721
|
+
return string
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
_UNIT_LIB.optionxform = _do_nothing
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
def import_library(libfilepointer):
|
|
728
|
+
"""
|
|
729
|
+
Import a units library, replacing any existing definitions.
|
|
730
|
+
|
|
731
|
+
Parameters
|
|
732
|
+
----------
|
|
733
|
+
libfilepointer : file
|
|
734
|
+
New library file to work with.
|
|
735
|
+
|
|
736
|
+
Returns
|
|
737
|
+
-------
|
|
738
|
+
ConfigParser
|
|
739
|
+
Newly updated units library for the module.
|
|
740
|
+
"""
|
|
741
|
+
global _UNIT_LIB
|
|
742
|
+
global _UNIT_CACHE
|
|
743
|
+
_UNIT_CACHE = {}
|
|
744
|
+
_UNIT_LIB = ConfigParser()
|
|
745
|
+
_UNIT_LIB.optionxform = _do_nothing
|
|
746
|
+
|
|
747
|
+
_UNIT_LIB.read_file(libfilepointer)
|
|
748
|
+
|
|
749
|
+
required_base_types = ["length", "mass", "time", "temperature", "angle"]
|
|
750
|
+
_UNIT_LIB.base_names = list()
|
|
751
|
+
# used to is_angle() and other base type checking
|
|
752
|
+
_UNIT_LIB.base_types = dict()
|
|
753
|
+
_UNIT_LIB.unit_table = dict()
|
|
754
|
+
_UNIT_LIB.prefixes = dict()
|
|
755
|
+
_UNIT_LIB.help = list()
|
|
756
|
+
|
|
757
|
+
for prefix, factor in _UNIT_LIB.items("prefixes"):
|
|
758
|
+
factor, comma, comment = factor.partition(",")
|
|
759
|
+
_UNIT_LIB.prefixes[prefix] = float(factor)
|
|
760
|
+
|
|
761
|
+
base_list = [0] * len(_UNIT_LIB.items("base_units"))
|
|
762
|
+
|
|
763
|
+
for i, (unit_type, name) in enumerate(_UNIT_LIB.items("base_units")):
|
|
764
|
+
_UNIT_LIB.base_types[unit_type] = i
|
|
765
|
+
powers = list(base_list)
|
|
766
|
+
powers[i] = 1
|
|
767
|
+
# print '%20s'%unit_type, powers
|
|
768
|
+
# cant use add_unit because no base units exist yet
|
|
769
|
+
_new_unit(name, 1, powers)
|
|
770
|
+
_UNIT_LIB.base_names.append(name)
|
|
771
|
+
|
|
772
|
+
# test for required base types
|
|
773
|
+
missing = [
|
|
774
|
+
utype for utype in required_base_types if utype not in _UNIT_LIB.base_types
|
|
775
|
+
]
|
|
776
|
+
if missing: # pragma: no cover
|
|
777
|
+
raise ValueError(
|
|
778
|
+
"Not all required base types were present in the config file. missing: "
|
|
779
|
+
f"{missing}, at least {required_base_types} required."
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
_update_library(_UNIT_LIB)
|
|
783
|
+
return _UNIT_LIB
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
def update_library(filename): # pragma: no cover
|
|
787
|
+
"""
|
|
788
|
+
Update units in current library from `filename`.
|
|
789
|
+
|
|
790
|
+
Parameters
|
|
791
|
+
----------
|
|
792
|
+
filename : str or file
|
|
793
|
+
Source of units configuration data.
|
|
794
|
+
"""
|
|
795
|
+
if isinstance(filename, str):
|
|
796
|
+
inp = open(filename, "rU")
|
|
797
|
+
else:
|
|
798
|
+
inp = filename
|
|
799
|
+
try:
|
|
800
|
+
cfg = ConfigParser()
|
|
801
|
+
cfg.optionxform = _do_nothing
|
|
802
|
+
|
|
803
|
+
# New in Python 3.2: read_file() replaces readfp().
|
|
804
|
+
if sys.version_info >= (3, 2):
|
|
805
|
+
cfg.read_file(inp)
|
|
806
|
+
else:
|
|
807
|
+
cfg.readfp(inp)
|
|
808
|
+
|
|
809
|
+
_update_library(cfg)
|
|
810
|
+
finally:
|
|
811
|
+
inp.close()
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
def _update_library(cfg): # pragma: no cover
|
|
815
|
+
"""
|
|
816
|
+
Update library from :class:`ConfigParser` `cfg`.
|
|
817
|
+
|
|
818
|
+
Parameters
|
|
819
|
+
----------
|
|
820
|
+
cfg : ConfigParser
|
|
821
|
+
ConfigParser loaded with unit_lib.ini data
|
|
822
|
+
"""
|
|
823
|
+
retry1 = set()
|
|
824
|
+
for name, unit in cfg.items("units"):
|
|
825
|
+
data = [item.strip() for item in unit.split(",")]
|
|
826
|
+
if len(data) == 2:
|
|
827
|
+
unit, comment = data
|
|
828
|
+
try:
|
|
829
|
+
add_unit(name, unit, comment)
|
|
830
|
+
except NameError:
|
|
831
|
+
retry1.add((name, unit, comment))
|
|
832
|
+
elif len(data) == 4:
|
|
833
|
+
factor, baseunit, offset, comment = data
|
|
834
|
+
try:
|
|
835
|
+
add_offset_unit(name, baseunit, float(factor), float(offset), comment)
|
|
836
|
+
except NameError:
|
|
837
|
+
retry1.add((name, baseunit, float(factor), float(offset), comment))
|
|
838
|
+
else:
|
|
839
|
+
raise ValueError(f"Unit '{name}' definition {unit} has invalid format.")
|
|
840
|
+
retry_count = 0
|
|
841
|
+
last_retry_count = -1
|
|
842
|
+
while last_retry_count != retry_count and retry1:
|
|
843
|
+
last_retry_count = retry_count
|
|
844
|
+
retry_count = 0
|
|
845
|
+
retry2 = retry1.copy()
|
|
846
|
+
for data in retry2:
|
|
847
|
+
if len(data) == 3:
|
|
848
|
+
name, unit, comment = data
|
|
849
|
+
try:
|
|
850
|
+
add_unit(name, unit, comment)
|
|
851
|
+
retry1.remove(data)
|
|
852
|
+
except NameError:
|
|
853
|
+
retry_count += 1
|
|
854
|
+
else:
|
|
855
|
+
try:
|
|
856
|
+
name, factor, baseunit, offset, comment = data
|
|
857
|
+
add_offset_unit(name, factor, baseunit, offset, comment)
|
|
858
|
+
retry1.remove(data)
|
|
859
|
+
except NameError:
|
|
860
|
+
retry_count += 1
|
|
861
|
+
if retry1:
|
|
862
|
+
raise ValueError(
|
|
863
|
+
"The following units were not defined because they"
|
|
864
|
+
" could not be resolved as a function of any other"
|
|
865
|
+
" defined units: %s." % [x[0] for x in retry1]
|
|
866
|
+
)
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
_UNIT_CACHE = {}
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
def _find_unit(unit, error=False):
|
|
873
|
+
"""
|
|
874
|
+
Find unit helper function.
|
|
875
|
+
|
|
876
|
+
Parameters
|
|
877
|
+
----------
|
|
878
|
+
unit : str
|
|
879
|
+
str representing the desired unit
|
|
880
|
+
error : bool
|
|
881
|
+
If True, raise exception if unit isn't found.
|
|
882
|
+
|
|
883
|
+
Returns
|
|
884
|
+
-------
|
|
885
|
+
PhysicalUnit
|
|
886
|
+
The actual unit object
|
|
887
|
+
"""
|
|
888
|
+
if isinstance(unit, str):
|
|
889
|
+
# Deal with 'as' for attoseconds
|
|
890
|
+
reg1 = re.compile(r"\bas\b")
|
|
891
|
+
unit = re.sub(reg1, "as_", unit)
|
|
892
|
+
|
|
893
|
+
name = unit.strip()
|
|
894
|
+
try:
|
|
895
|
+
unit = _UNIT_CACHE[name]
|
|
896
|
+
except KeyError:
|
|
897
|
+
try:
|
|
898
|
+
unit = eval(
|
|
899
|
+
name,
|
|
900
|
+
{"__builtins__": None}, # nosec: scope limited
|
|
901
|
+
_UNIT_LIB.unit_table,
|
|
902
|
+
)
|
|
903
|
+
except Exception:
|
|
904
|
+
# This unit might include prefixed units that aren't in the
|
|
905
|
+
# unit_table. We must parse them ALL and add them to the
|
|
906
|
+
# unit_table.
|
|
907
|
+
|
|
908
|
+
# First character of a unit is always alphabet or $.
|
|
909
|
+
# Remaining characters may include numbers.
|
|
910
|
+
regex = re.compile("[A-Z,a-z]{1}[A-Z,a-z,0-9]*")
|
|
911
|
+
|
|
912
|
+
unit_table = _UNIT_LIB.unit_table
|
|
913
|
+
prefixes = _UNIT_LIB.prefixes
|
|
914
|
+
|
|
915
|
+
for item in regex.findall(name):
|
|
916
|
+
item = re.sub(reg1, "as_", item)
|
|
917
|
+
|
|
918
|
+
# check if this was a compound unit, so each
|
|
919
|
+
# substring might be a unit
|
|
920
|
+
try:
|
|
921
|
+
eval(
|
|
922
|
+
item, {"__builtins__": None}, unit_table
|
|
923
|
+
) # nosec: scope limited
|
|
924
|
+
|
|
925
|
+
except Exception: # maybe is a prefixed unit then
|
|
926
|
+
base_unit = item[1:].rstrip("_")
|
|
927
|
+
|
|
928
|
+
# check for single letter prefix before unit
|
|
929
|
+
if item[0] in prefixes and base_unit in unit_table:
|
|
930
|
+
add_unit(item, prefixes[item[0]] * unit_table[base_unit])
|
|
931
|
+
|
|
932
|
+
# check for double letter prefix before unit
|
|
933
|
+
elif item[0:2] in prefixes and item[2:] in unit_table:
|
|
934
|
+
add_unit(item, prefixes[item[0:2]] * unit_table[item[2:]])
|
|
935
|
+
|
|
936
|
+
# no prefixes found, unknown unit
|
|
937
|
+
else:
|
|
938
|
+
if error:
|
|
939
|
+
raise ValueError(f"The units '{name}' are invalid.")
|
|
940
|
+
return None
|
|
941
|
+
|
|
942
|
+
unit = eval(
|
|
943
|
+
name, {"__builtins__": None}, unit_table
|
|
944
|
+
) # nosec: scope limited
|
|
945
|
+
|
|
946
|
+
_UNIT_CACHE[name] = unit
|
|
947
|
+
else:
|
|
948
|
+
name = unit
|
|
949
|
+
|
|
950
|
+
if not isinstance(unit, PhysicalUnit):
|
|
951
|
+
raise ValueError(f"The units '{name}' are invalid.")
|
|
952
|
+
|
|
953
|
+
return unit
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
def valid_units(unit):
|
|
957
|
+
"""
|
|
958
|
+
Return whether the given units are vaild.
|
|
959
|
+
|
|
960
|
+
Parameters
|
|
961
|
+
----------
|
|
962
|
+
unit : str
|
|
963
|
+
String representation of the units.
|
|
964
|
+
|
|
965
|
+
Returns
|
|
966
|
+
-------
|
|
967
|
+
bool
|
|
968
|
+
True for valid, False for invalid.
|
|
969
|
+
"""
|
|
970
|
+
return _find_unit(unit) is not None
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
def unit_conversion(old_units, new_units):
|
|
974
|
+
"""
|
|
975
|
+
Return conversion factor and offset between old and new units.
|
|
976
|
+
|
|
977
|
+
Parameters
|
|
978
|
+
----------
|
|
979
|
+
old_units : str
|
|
980
|
+
Original units as a string.
|
|
981
|
+
new_units : str
|
|
982
|
+
New units to return the value in.
|
|
983
|
+
|
|
984
|
+
Returns
|
|
985
|
+
-------
|
|
986
|
+
(float, float)
|
|
987
|
+
Conversion factor and offset.
|
|
988
|
+
"""
|
|
989
|
+
return _find_unit(old_units, error=True).conversion_tuple_to(
|
|
990
|
+
_find_unit(new_units, error=True)
|
|
991
|
+
)
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
def convert_units(val, old_units, new_units=None):
|
|
995
|
+
"""
|
|
996
|
+
Take a given quantity and return in different units.
|
|
997
|
+
|
|
998
|
+
Parameters
|
|
999
|
+
----------
|
|
1000
|
+
val : float
|
|
1001
|
+
Value in original units.
|
|
1002
|
+
old_units : str or None
|
|
1003
|
+
Original units as a string or None.
|
|
1004
|
+
new_units : str or None
|
|
1005
|
+
New units to return the value in or None.
|
|
1006
|
+
|
|
1007
|
+
Returns
|
|
1008
|
+
-------
|
|
1009
|
+
float
|
|
1010
|
+
Value in new units.
|
|
1011
|
+
"""
|
|
1012
|
+
if not old_units or not new_units: # one side has no units
|
|
1013
|
+
return val
|
|
1014
|
+
|
|
1015
|
+
old_unit = _find_unit(old_units, error=True)
|
|
1016
|
+
new_unit = _find_unit(new_units, error=True)
|
|
1017
|
+
|
|
1018
|
+
(factor, offset) = old_unit.conversion_tuple_to(new_unit)
|
|
1019
|
+
return (val + offset) * factor
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
def simplify_unit(old_unit_str):
|
|
1023
|
+
"""
|
|
1024
|
+
Simplify unit string using built-in naming method.
|
|
1025
|
+
|
|
1026
|
+
Unit string 'ft*s/s' becomes 'ft'.
|
|
1027
|
+
|
|
1028
|
+
Parameters
|
|
1029
|
+
----------
|
|
1030
|
+
old_unit_str : str
|
|
1031
|
+
Unit string to simplify.
|
|
1032
|
+
msginfo : str
|
|
1033
|
+
A string prepended to the ValueError which is raised if the units are invalid.
|
|
1034
|
+
|
|
1035
|
+
Returns
|
|
1036
|
+
-------
|
|
1037
|
+
str
|
|
1038
|
+
Simplified unit string.
|
|
1039
|
+
"""
|
|
1040
|
+
if old_unit_str is None:
|
|
1041
|
+
return None
|
|
1042
|
+
|
|
1043
|
+
found_unit = _find_unit(old_unit_str, True)
|
|
1044
|
+
|
|
1045
|
+
new_str = found_unit.name()
|
|
1046
|
+
if new_str == "1":
|
|
1047
|
+
# Special Case. Unity always becomes None.
|
|
1048
|
+
new_str = None
|
|
1049
|
+
|
|
1050
|
+
# Restore units 'as' (attoseconds).
|
|
1051
|
+
if new_str:
|
|
1052
|
+
reg1 = re.compile(r"\bas_\b")
|
|
1053
|
+
new_str = reg1.sub("as", new_str)
|
|
1054
|
+
return new_str
|
|
1055
|
+
|
|
1056
|
+
|
|
1057
|
+
# Load in the default unit library
|
|
1058
|
+
file_path = open(os.path.join(os.path.dirname(__file__), "unit_library.ini"))
|
|
1059
|
+
with file_path as default_lib:
|
|
1060
|
+
import_library(default_lib)
|