1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """
23 A physical quantity is a number with a unit, like 10 km/h. Units can be any
24 of the SI units, plus a bunch of non-SI, bits, dollars, and any combination
25 of them. They can include the standard SI prefixes. Magnitude can operate
26 with physical quantities, parse their units, and print them. You don't have
27 to worry about unit consistency or conversions; everything is handled
28 transparently. By default output is done in basic SI units, but you can
29 specify any output unit, as long as it can be reduced to the basic units of
30 the physical quantity.
31
32 The basic units understood by the magnitude module are:
33
34 indicator meaning
35 --------- -------
36 $ dollar ('dollar' is also acceptable)
37 A amperes
38 bit binary digit
39 cd candela
40 K degrees Kelvin
41 kg kilograms
42 m meters
43 mol amount of substance
44 s seconds
45
46 From these basic units you can derive many other units. The magnitude
47 package predefines these derived units:
48
49 Bq becquerel
50 C coulomb
51 c speed of light (m/s)
52 day
53 degC degree Celsius
54 dpi dots per inch
55 F farad
56 ft feet ("'" is also acceptable)
57 g gram
58 gravity acceleration due to gravity (m/s**2)
59 Gy gray
60 H henry
61 h hour
62 Hz Hertz
63 inch ('"' is also acceptable)
64 ips inches per second
65 J joule
66 kat byte
67 l liter
68 lightyear light year
69 lm lumen
70 lpi lines per inch
71 lux
72 min minute
73 N newton
74 ohm
75 Pa pascal
76 S siemens
77 Sv sievert
78 T tesla
79 V volt
80 W watt
81 Wb weber
82 year
83
84 Two magnitudes have no units, 'rad' (radian - unit of plane angle) and 'sr'
85 (steradian - unit of solid angle).
86
87 Any of the above units can be augmented with the following set of scale
88 prefixes:
89
90 letter scale name
91 ------ ----- ----
92 y 1e-24 yocto
93 z 1e-21 zepto
94 a 1e-18 atto
95 f 1e-15 femto
96 p 1e-12 pico
97 n 1e-9 nano
98 u 1e-6 micro
99 m 1e-3 mili
100 c 1e-2 centi
101 d 1e-1 deci
102 k 1e3 kilo
103 Ki 2^10 Kibi
104 M 1e6 mega
105 Mi 2^20 Mebi
106 G 1e9 giga
107 Gi 2^30 Gibi
108 T 1e12 tera
109 Ti 2^40 Tebi
110 P 1e15 peta
111 Pi 2^50 Pebi
112 E 1e18 exa
113 Ei 2^60 Exbi
114 Z 1e21 zetta
115 Y 1e24 yotta
116
117 You can define new magnitudes by instantiating the Magnitude class. Suppose
118 you want to define pounds as a magnitude and associate with it the unit
119 'lb'. A pound is 0.45359237 kilograms, so we have
120
121 >>> lb = Magnitude(0.45359237, kg=1)
122
123 To make it recognized automatically you also have to present it to the system:
124
125 >>> new_mag('lb', lb)
126
127 You can then use it as you would any other predefined physical quantity:
128
129 >>> me = mg(180, 'lb')
130 >>> print me.ounit('kg').toval()
131 81.6466266
132
133 The following online references provide more detail about physical units and
134 the SI system.
135
136 http://physics.nist.gov/cuu/Units/units.html
137 http://en.wikipedia.org/wiki/SI
138 http://www.gnu.org/software/units/units.html for units.dat
139 http://www.cip.physik.uni-muenchen.de/~tf/misc/etools.lisp
140
141 This code was very much inspired by
142 http://www.cs.utexas.edu/users/novak/units.html
143 and its associated paper,
144 http://www.cs.utexas.edu/users/novak/units95.html
145 """
146
147 import re, math
148 import types
149
150
151
152
153
154
157
158 _mags = {}
159 _unames = ['m', 's', 'K', 'kg', 'A', 'mol', 'cd', '$', 'bit']
160 _prefix = {'y': 1e-24,
161 'z': 1e-21,
162 'a': 1e-18,
163 'f': 1e-15,
164 'p': 1e-12,
165 'n': 1e-9,
166 'u': 1e-6,
167 'm': 1e-3,
168 'c': 1e-2,
169 'd': 1e-1,
170 'k': 1e3,
171 'M': 1e6,
172 'G': 1e9,
173 'T': 1e12,
174 'P': 1e15,
175 'E': 1e18,
176 'Z': 1e21,
177 'Y': 1e24,
178
179
180
181
182
183
184 'Ki': 2 ** 10,
185 'Mi': 2 ** 20,
186 'Gi': 2 ** 30,
187 'Ti': 2 ** 40,
188 'Pi': 2 ** 50,
189 'Ei': 2 ** 60
190 }
191
192 _prn_format = "%.*f"
193 _prn_prec = 4
194 _prn_units = True
195
207
209 """ Change the output precission. Default is 4. Returns it. """
210 global _prn_prec
211 if prec is not None:
212 _prn_prec = prec
213 return _prn_prec
214
221
222
223
224
225
226
227
228
229
231 match = re.search(r'(\d+)x(\d+)', res)
232 if match:
233 return int(match.group(1)), int(match.group(2))
234 if (res[0] == '[') and (res[-1] == ']'):
235 return (int(res[1:-1]), int(res[1:-1]))
236
238 return (len(res) > 2) and (res[0] == '[') and (res[-1] == ']')
239
241 hr, vr = res2num(res)
242 return 0.0254 * 0.0254 / (vr * hr)
243
244
245
246
248 return (isinstance(n, types.ComplexType) or
249 isinstance(n, types.FloatType) or
250 isinstance(n, types.IntType) or
251 isinstance(n, types.LongType))
252
254 - def __init__(self, val, m=0, s=0, K=0, kg=0, A=0, mol=0, cd=0, dollar=0,
255 bit=0):
256 self.val = val
257 self.unit = [m, s, K, kg, A, mol, cd, dollar, bit]
258 self.out_unit = None
259 self.out_factor = None
260 self.oprec = None
261 self.oformat = None
262
265
266 - def toval(self, ounit=''):
274
276 oformat = self.oformat
277 oprec = self.oprec
278 if oprec is None:
279 oprec = _prn_prec
280 if oformat is None:
281 oformat = _prn_format
282 if self.out_unit:
283 m = self.copy()
284 m.div_by(self.out_factor)
285 if '*' in oformat:
286 st = oformat % (oprec, m.val)
287 else:
288 st = oformat % (m.val)
289 if _prn_units:
290 return st + ' ' + self.out_unit
291 return st
292
293 if '*' in oformat:
294 st = oformat % (oprec, self.val)
295 else:
296 st = oformat % (self.val)
297
298 if not _prn_units:
299 return st
300
301 u = self.unit
302 num = ' '
303 for i in range(len(_unames)):
304 if u[i] > 1:
305 num = num + _unames[i] + str(u[i]) + ' '
306 elif u[i] == 1:
307 num = num + _unames[i] + ' '
308 den = ''
309 for i in range(len(_unames)):
310 if u[i] < -1:
311 den = den + _unames[i] + str(-u[i]) + ' '
312 elif u[i] == -1:
313 den = den + _unames[i] + ' '
314 if den:
315 if num == ' ':
316 num += '1 '
317 st += (num + '/ ' + den)
318 elif num != ' ':
319 st += num
320 return st
321
323 """Converts a string with units to a Magnitude. Can't divide: use
324 with the numerator and the denominator separately (hence the term).
325
326 """
327 m = Magnitude(1.0)
328 units = re.split(r'\s', s)
329 for u in units:
330 if re.search(r'[^\s]', u):
331 exp = 1
332 if re.search(r'\d$', u):
333 exp = int(u[-1])
334 u = u[0:-1]
335 if _mags.has_key(u):
336 u = _mags[u].copy()
337 elif ((len(u)>=3) and _prefix.has_key(u[0:2]) and
338 _mags.has_key(u[2:])):
339 pr = _prefix[u[0:2]]
340 u = _mags[u[2:]].copy(); u.val = pr * u.val
341 elif ((len(u)>=2) and _prefix.has_key(u[0]) and
342 _mags.has_key(u[1:])):
343 pr = _prefix[u[0]]
344 u = _mags[u[1:]].copy(); u.val = pr * u.val
345 elif isres(u):
346 u = Magnitude(res2m2(u), m=2)
347 elif u == '':
348 u = Magnitude(1.0)
349 else:
350 raise MagnitudeError("Don't know about unit %s" % u)
351 for i in range(exp):
352 m.mult_by(u)
353 return m
354
356 """This is the good one: uses term2mag to convert a string
357 with units, possibly including a / to separate a numerator and
358 a denominator, to a Magnitude.
359
360 """
361 m = Magnitude(1.0)
362 if unit:
363 q = re.split(r'/', unit)
364 if re.search(r'[^\s]', q[0]):
365 m.mult_by(self.term2mag(q[0]))
366 if (len(q) == 2) and re.search(r'[^\s]', q[1]):
367 m.div_by(self.term2mag(q[1]))
368 return m
369
371 return self.unit == [0] * 9
372
375
377 o = self.sunit2mag(u)
378 return (self.unit == o.unit)
379
381 self.val *= m.val
382 for i in range(len(self.unit)):
383 self.unit[i] = self.unit[i] + m.unit[i]
384 self.out_unit = None
385
387 self.val /= m.val
388 for i in range(len(self.unit)):
389 self.unit[i] = self.unit[i] - m.unit[i]
390 self.out_unit = None
391
393 self.out_unit = unit
394 self.out_factor = self.sunit2mag(unit)
395 if self.out_factor.unit != self.unit:
396 raise MagnitudeError("Inconsistent Magnitude units: %s, %s" %
397 (self.out_factor.unit, self.unit))
398 return self
399
402
405
407 if not isinstance(m, Magnitude):
408 if type(m) == tuple:
409 if len(m) == 2:
410 r = Magnitude(m[0])
411 r.mult_by(self.sunit2mag(m[1]))
412 return self, r
413 elif len(m) == 1:
414 return self, Magnitude(m[0])
415 else:
416 return None
417 elif numberp(m):
418 return self, Magnitude(m)
419 else:
420 return None
421 else:
422 return self, m
423
425 if m.unit != self.unit:
426 raise MagnitudeError("Incompatible units: %s and %s" %
427 (m.unit, self.unit))
428 r = self.copy()
429 r.val += m.val
430 return r
431
434
436 if m.unit != self.unit:
437 raise MagnitudeError("Incompatible units: %s and %s" %
438 (m.unit, self.unit))
439 self.val += m.val
440 return self
441
443 if m.unit != self.unit:
444 raise MagnitudeError("Incompatible units: %s and %s" %
445 (m.unit, self.unit))
446 r = self.copy()
447 r.val -= m.val
448 return r
449
452
454 if m.unit != self.unit:
455 raise MagnitudeError("Incompatible units: %s and %s" %
456 (m.unit, self.unit))
457 self.val -= m.val
458 return self
459
464
469
471 self.mult_by(m)
472 return self
473
478
483
485 self.div_by(m)
486 return self
487
489 r = self.copy()
490 r.val = r.val % n.val
491 for i in range(len(r.unit)):
492 r.unit[i] = r.unit[i] - n.unit[i]
493 self.out_unit = None
494 return r
495
497 self.val %= n.val
498 for i in range(len(self.unit)):
499 self.unit[i] = self.unit[i] - n.unit[i]
500 self.out_unit = None
501 return self
502
504 r = self.copy()
505 r.div_by(m)
506 r.val = math.floor(r.val)
507 return r
508
510 self.div_by(m)
511 self.val = math.floor(self.val)
512 return self
513
516
519
520 - def __pow__(self, n, modulo=None):
521 r = self.copy()
522 if modulo and (r.val == math.floor(r.val)):
523
524
525 r.val = int(r.val)
526 if isinstance(n, Magnitude):
527 if not n.dimensionless():
528 raise MagnitudeError("Cannot use a dimensional number as"
529 "exponent, %s" % (n))
530 n = n.val
531 r.val = pow(r.val, n, modulo)
532 for i in range(len(r.unit)):
533 r.unit[i] *= n
534 return r
535
537 if not n.dimensionless():
538 raise MagnitudeError("Cannot use a dimensional number as"
539 "exponent, %s" % (n))
540 n = n.val
541 self.val = pow(self.val, n)
542 for i in range(len(self.unit)):
543 self.unit[i] *= n
544 return self
545
547 r = self.copy()
548 r.val = -r.val
549 return r
550
553
555 r = self.copy()
556 r.val = abs(r.val)
557 return r
558
560 if m.unit != self.unit:
561 raise MagnitudeError("Incompatible units in comparison: %s and %s" %
562 (m.unit, self.unit))
563 return cmp(self.val, m.val)
564
567
569 return long(self.toval())
570
572 return float(self.toval())
573
575 r = self.copy()
576 r.val = math.ceil(r.val)
577 return r
578
580 r = self.copy()
581 r.val = math.floor(r.val)
582 return r
583
585 r = self.copy()
586 r.val = round(r.val)
587 return r
588
590 return Magnitude(math.ceil(math.log(self.val) / math.log(2.0)),
591 bit=1)
592
595
596
597
598
599 -def mg(v, unit='', ounit=''):
609
611 """Ensures that something is a Magnitude."""
612 if not isinstance(m, Magnitude):
613 if type(m) == tuple:
614 if len(m) == 2:
615 return mg(m[0], m[1], unit)
616 elif (len(m) == 1) and numberp(m[0]):
617 if unit:
618 return mg(m[0], unit)
619 return Magnitude(m[0])
620 else:
621 raise MagnitudeError("Can't convert %s to Magnitude" %
622 (m,))
623 elif numberp(m):
624 if unit:
625 return mg(m, unit)
626 return Magnitude(m)
627 else:
628 raise MagnitudeError("Can't convert %s to Magnitude" %
629 (m,))
630 else:
631 return m
632
633
634
635
640
650
652 """Define a new magnitude understood by the package."""
653 _mags[indicator] = mag
654
655
656
658
659 new_mag('m', Magnitude(1.0, m=1))
660 new_mag('s', Magnitude(1.0, s=1))
661 new_mag('K', Magnitude(1.0, K=1))
662 new_mag('kg', Magnitude(1.0, kg=1))
663 new_mag('A', Magnitude(1.0, A=1))
664 new_mag('mol', Magnitude(1.0, mol=1))
665 new_mag('cd', Magnitude(1.0, cd=1))
666 new_mag('$', Magnitude(1.0, dollar=1))
667 new_mag('dollar', Magnitude(1.0, dollar=1))
668 new_mag('bit', Magnitude(1.0, bit=1))
669
670
671 new_mag('rad', Magnitude(1.0))
672 new_mag('sr', Magnitude(1.0))
673 new_mag('Hz', Magnitude(1.0, s=-1))
674 new_mag('g', Magnitude(1e-3, kg=1))
675 new_mag('N', Magnitude(1.0, m=1, kg=1, s=-2))
676 new_mag('Pa', Magnitude(1.0, m=-1, kg=1, s=-2))
677 new_mag('J', Magnitude(1.0, m=2, kg=1, s=-2))
678 new_mag('W', Magnitude(1.0, m=2, kg=1, s=-3))
679 new_mag('C', Magnitude(1.0, s=1, A=1))
680 new_mag('V', Magnitude(1.0, m=2, kg=1, s=-3, A=-1))
681 new_mag('F', Magnitude(1.0, m=-2, kg=-1, s=4, A=2))
682 new_mag('ohm', Magnitude(1.0, m=2, kg=1, s=-3, A=-2))
683 new_mag('S', Magnitude(1.0, m=-2, kg=-1, s=3, A=2))
684 new_mag('Wb', Magnitude(1.0, m=2, kg=1, s=-2, A=-1))
685 new_mag('T', Magnitude(1.0, kg=1, s=-2, A=-1))
686 new_mag('H', Magnitude(1.0, m=2, kg=1, s=-2, A=-2))
687 new_mag('degC', Magnitude(1.0, K=1))
688 new_mag('lm', Magnitude(1.0, cd=1))
689 new_mag('lux', Magnitude(1.0, m=-2, cd=1))
690 new_mag('Bq', Magnitude(1.0, s=-1))
691 new_mag('Gy', Magnitude(1.0, m=2, s=-2))
692 new_mag('Sv', Magnitude(1.0, m=2, s=-2))
693 new_mag('kat', Magnitude(1.0, s=-1, mol=1))
694
695 new_mag('b', Magnitude(8.0, bit=1))
696
697
698
699 new_mag("'", Magnitude(0.3048, m=1))
700 new_mag('ft', Magnitude(0.3048, m=1))
701 new_mag('inch', Magnitude(0.0254, m=1))
702 new_mag('"', Magnitude(0.0254, m=1))
703 new_mag('lightyear', Magnitude(2.99792458e8 * 365.25 * 86400, m=1))
704
705
706 new_mag('l', Magnitude(0.001, m=3))
707
708
709
710
711
712
713
714 new_mag('year', Magnitude(31556925.974678401, s=1))
715 new_mag('day', Magnitude(86400, s=1))
716 new_mag('h', Magnitude(3600, s=1))
717 new_mag('min', Magnitude(60, s=1))
718
719
720 new_mag('dpi', Magnitude(1.0 / 0.0254, m=-1))
721 new_mag('lpi', Magnitude(1.0 / 0.0254, m=-1))
722
723
724 new_mag('ips', Magnitude(0.0254, m=1, s=-1))
725 new_mag('c', Magnitude(2.99792458e8, m=1, s=-1))
726
727
728 new_mag('gravity', Magnitude(9.80665, m=1, s=-2))
729
730
731 if not _mags:
732 _init_mags()
733