forked from otherjon/retaining-wall
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathunits.py
executable file
·301 lines (268 loc) · 9.4 KB
/
units.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
#!/usr/bin/python
import math, re
class Error(Exception): pass
class Units(object):
def __init__(self, data, unit_order=None, as_latex=True, **kwargs):
"""
Input:
data (str or Units object)
(str) unit name, e.g. 'lb/ft^2' or 'L*atm/mol/K'
(Units object) data to copy
unit_order (list or tuple):
ordered list containing unit names in the order they should be sorted
for display
ndigits (int): number of significant digits for display
"""
ndigits = kwargs.get('ndigits', 3)
self.as_latex = as_latex
self.ndigits = ndigits
self.unit_order = unit_order
if unit_order is None:
self.order_func = None
else:
# listed units sort in the order listed, unlisted units sort last
self.order_func = lambda a: (
a[0] in self.unit_order and (self.unit_order.index(a[0]), a[0]) or
(len(self.unit_order), a[0]) )
if isinstance(data, Units):
if 'ndigits' in kwargs:
self.ndigits = kwargs['ndigits']
else:
self.ndigits = data.ndigits
self.u = data.u.copy()
self.magnitude = data.magnitude
self.unit_order = data.unit_order
self.order_func = data.order_func
self.as_latex = data.as_latex
else:
data = str(data)
m = re.match('[-.0-9]+', data)
self.magnitude = float(m.group(0))
data = re.sub('\s', '', data[len(m.group(0)):])
if not data:
# dimensionless unit
self.u = {}
return
terms = re.split(r'[/*]', data)
operators = re.findall(r'[/*]', data)
u = self.RootUnitAndExponent(terms[0])
for i in range(len(operators)):
term, op = terms[i+1], operators[i]
thisunit = self.RootUnitAndExponent(term)
if op == '/':
for element in thisunit:
thisunit[element] = -1 * thisunit[element]
for element in thisunit:
u.setdefault(element, 0)
u[element] += thisunit[element]
self.u = u
def RootUnitAndExponent(self, s):
"""
Input: s (str) = a single unit with optional exponent, e.g. "lb" or "ft^2"
Output: a dictionary mapping unit names to exponents, e.g. {'ft': 2}
"""
if s.find('^') == -1:
return {s: 1}
caret_position = s.find('^')
unit = s[:caret_position]
exponent = int(s[caret_position+1:])
return {unit: exponent}
def __float__(self):
if self.u == {}:
return self.magnitude
raise Error("Can't evaluate dimensioned unit (%s) as a dimensionless float"
% self)
def __trunc__(self):
if self.u == {}:
return int(self.magnitude)
raise Error("Can't evaluate dimensioned unit (%s) as a dimensionless int"
% self)
def __abs__(self):
if self.u == {}:
return abs(self.magnitude)
raise Error("Can't take absolute value of dimensioned unit (%s)" % self)
def __add__(self, other):
if type(other) in (int, float) and self.u != {}:
raise Error("Can't add raw numbers to dimensioned units (%s)" % self)
result = self.__class__(self)
if type(other) in (int, float):
result.magnitude = self.magnitude + other
else:
if self.u != other.u:
raise Error("Can't add units of different types (%s + %s)" %
(self, other))
result.magnitude = self.magnitude + other.magnitude
return result
def __radd__(self, other):
return self + other
def __neg__(self):
result = self.__class__(self)
result.magnitude = -result.magnitude
return result
def __sub__(self, other):
return self + (-other)
def __rsub__(self, other):
return -self + other
def __rmul__(self, other):
return self * other
def __mul__(self, other):
order = self.unit_order
ndigits = self.ndigits
if order is None and isinstance(other, Units):
order = other.unit_order
result = self.__class__(self, unit_order=order)
if isinstance(other, Units):
result.ndigits = max(result.ndigits, other.ndigits)
result.magnitude = self.magnitude * other.magnitude
for element in other.u:
result.u.setdefault(element, 0)
result.u[element] += other.u[element]
if result.u[element] == 0:
del result.u[element]
else:
result.magnitude = self.magnitude * other
return result
def __pow__(self, other):
if type(other) is not int:
raise Error("Can't raise units to non-integer power (%s)" % other)
result = self.__class__(self)
result.magnitude = self.magnitude ** other
for unit in result.u:
result.u[unit] *= other
return result
def __div__(self, other):
if type(other) in (int, float):
# Units-object / 2.0
result = self.__class__(self)
result.magnitude = self.magnitude / other
return result
# Both self and other are Units-objects
order = self.unit_order
ndigits = self.ndigits
if order is None:
order = other.unit_order
result = self.__class__(self, unit_order=order)
result.ndigits = max(result.ndigits, other.ndigits)
result.magnitude = self.magnitude / other.magnitude
for element in other.u:
result.u.setdefault(element, 0)
result.u[element] -= other.u[element]
if result.u[element] == 0:
del result.u[element]
return result
def __rdiv__(self, other):
if type(other) in (int, float):
# 1.0 / Units-object
result = self.__class__(self)
result.magnitude = float(other) / self.magnitude
for unit, exponent in result.u.items():
result.u[unit] = -exponent
return result
def __gt__(self, other):
if len(self.u) == 0:
return self.magnitude > other
if not isinstance(other, Units):
raise Error("Can't compare dimensioned units (%s) with dimensionless "
"number (%s)" % (self, other))
if self.u != other.u:
raise Error("Can't compare units of different dimensions (%s vs. %s)" %
(self, other))
return self.magnitude > other.magnitude
def __ge__(self, other):
if len(self.u) == 0:
return self.magnitude >= other
if not isinstance(other, Units):
raise Error("Can't compare dimensioned units (%s) with dimensionless "
"number (%s)" % (self, other))
if self.u != other.u:
raise Error("Can't compare units of different dimensions (%s vs. %s)" %
(self, other))
return self.magnitude >= other.magnitude
def __lt__(self, other):
return not (self >= other)
def __le__(self, other):
return not (self > other)
def __str__(self):
pos, neg = {}, {}
for k in self.u:
if self.u[k] > 0:
pos[k] = self.u[k]
elif self.u[k] < 0:
neg[k] = -1 * self.u[k]
else:
pass
numerator_elts, denominator_elts = [], []
for element in pos:
exponent = ''
if pos[element] != 1:
exponent = '^%d' % pos[element]
if self.as_latex:
if exponent:
numerator_elts.append((element, r'\mbox{%s} \ensuremath{{\!}%s}' %
(element, exponent)))
else:
numerator_elts.append((element, r'\mbox{%s}' % element))
else:
numerator_elts.append((element, '%s%s' % (element, exponent)))
for element in neg:
exponent = ''
if neg[element] != 1:
exponent = '^%d' % neg[element]
if self.as_latex:
if exponent:
denominator_elts.append((element, r'\mbox{%s} \ensuremath{{\!}%s}' %
(element, exponent)))
else:
denominator_elts.append((element, r'\mbox{%s}' % element))
else:
denominator_elts.append((element, '%s%s' % (element, exponent)))
# sort numerator_elts and denominator_elts by self.unit_order
numerator_elts.sort(key=self.order_func)
denominator_elts.sort(key=self.order_func)
mag_str = ('%.' + str(self.ndigits) + 'f') % self.magnitude
if self.as_latex:
separator = r'\,'
else:
separator = ''
if denominator_elts:
return '%s%s %s / %s' % (mag_str, separator,
' * '.join([data for sortkey, data in numerator_elts]),
' / '.join([data for sortkey, data in denominator_elts]))
else:
return '%s%s %s' % (mag_str, separator,
' * '.join([data for sortkey, data in numerator_elts]))
class Degrees(Units):
def __init__(self, deg, ndigits=1, **kwargs):
self.u = {}
self.unit_order = None
self.order_func = None
if isinstance(deg, Degrees):
self.degrees = deg.degrees
self.ndigits = deg.ndigits
elif type(deg) in (int, float):
self.degrees = deg
self.ndigits = ndigits
else:
raise Error("Can't initialize degrees with type %s" %
deg.__class__.__name__)
self.magnitude = self.degrees
def __float__(self):
return self.radians()
def radians(self):
return math.pi * self.magnitude / 180.0
def __str__(self):
if abs(int(self.magnitude) - self.magnitude) < .0001:
return r'\ensuremath{%d ^{\circ}}' % self.magnitude
return ((r'\ensuremath{%.' + str(self.ndigits) + 'f ^{\circ}}') %
self.magnitude)
def __add__(self, other):
value = self.magnitude
if type(other) is float:
value += 180.0 / math.pi * other
elif isinstance(other, Degrees):
value += other.magnitude
else:
raise Error("Can't add '%s' to 'Degrees'" % other.__class__.__name__)
return Degrees(value)
def __neg__(self):
return Degrees(-1.0 * self.magnitude)