1 # Copyright (C) 2010 Massimo Sandal <devicerandom@gmail.com>
2 # Rolf Schmidt <rschmidt@alcor.concordia.ca>
3 # W. Trevor King <wking@drexel.edu>
5 # This file is part of Hooke.
7 # Hooke is free software: you can redistribute it and/or modify it
8 # under the terms of the GNU Lesser General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # Hooke is distributed in the hope that it will be useful, but WITHOUT
13 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
15 # Public License for more details.
17 # You should have received a copy of the GNU Lesser General Public
18 # License along with Hooke. If not, see
19 # <http://www.gnu.org/licenses/>.
21 """Define functions for handling numbers in SI notation.
25 To output a scale, choose any value on the axis and find the
26 multiplier and prefix for it. Use those to format the rest of the
27 scale. As values can span several orders of magnitude, you have
28 to decide what units to use.
30 >>> xs = (985e-12, 1e-9, 112358e-12)
32 Get the power from the first (or last, or middle, ...) value
34 >>> p = get_power(xs[0])
36 ... print ppSI(x, decimals=2, power=p)
40 >>> print prefix_from_value(xs[0]) + 'N'
45 from numpy import isnan
68 """A dictionary of SI prefixes from 10**24 to 10**-24.
80 _DATA_LABEL_REGEXP = re.compile('^([^(]*[^ ]) ?\(([^)]*)\)$')
81 """Used by :func:`data_label_unit`.
85 def ppSI(value, unit='', decimals=None, power=None, pad=False):
86 """Pretty-print `value` in SI notation.
88 The current implementation ignores `pad` if `decimals` is `None`.
92 >>> x = math.pi * 1e-8
93 >>> print ppSI(x, 'N')
95 >>> print ppSI(x, 'N', 3)
97 >>> print ppSI(x, 'N', 4, power=-12)
99 >>> print ppSI(x, 'N', 5, pad=True)
102 If you want the decimal indented by six spaces with `decimal=2`,
103 `pad` should be the sum of
105 * 6 (places before the decimal point)
106 * 1 (length of the decimal point)
107 * 2 (places after the decimal point)
109 >>> print ppSI(-x, 'N', 2, pad=(6+1+2))
114 if value == None or isnan(value):
117 if power == None: # auto-detect power
118 power = get_power(value)
121 format = lambda n: '%f' % n
123 if pad == False: # no padding
124 format = lambda n: '%.*f' % (decimals, n)
126 if pad == True: # auto-generate pad
127 # 1 for ' ', 1 for '-', 3 for number, 1 for '.', and decimals.
129 format = lambda n: '%*.*f' % (pad, decimals, n)
131 prefix = ' '+PREFIX[power]
133 prefix = 'e%d ' % power
134 return '%s%s%s' % (format(value / pow(10,power)), prefix, unit)
137 def get_power(value):
138 """Return the SI power for which `0 <= |value|/10**pow < 1000`.
153 if value != 0 and not isnan(value):
155 value_temp = math.floor(math.log10(math.fabs(value)))
156 # reduce the log10 to a multiple of 3
157 return int(value_temp - (value_temp % 3))
161 def prefix_from_value(value):
162 """Determine the SI power of `value` and return its prefix.
166 >>> prefix_from_value(0)
168 >>> prefix_from_value(1e10)
171 return PREFIX[get_power(value)]
173 def join_data_label(name, unit):
174 """Create laels for `curve.data[i].info['columns']`.
182 >>> join_data_label('z piezo', 'm')
184 >>> join_data_label('deflection', 'N')
187 return '%s (%s)' % (name, unit)
189 def split_data_label(label):
190 """Split `curve.data[i].info['columns']` labels into `(name, unit)`.
198 >>> split_data_label('z piezo (m)')
200 >>> split_data_label('deflection (N)')
203 m = _DATA_LABEL_REGEXP.match(label)
204 assert m != None, label