Skip to main content

physical quantities (numbers with units)

Project description

Version: 0.2.0
Released: 2016-10-23
https://img.shields.io/travis/KenKundert/quantiphy/master.svg https://img.shields.io/coveralls/KenKundert/quantiphy.svg https://img.shields.io/pypi/v/quantiphy.svg https://img.shields.io/pypi/pyversions/quantiphy.svg https://img.shields.io/pypi/dd/quantiphy.svg

Use ‘pip install quantiphy’ to install. Requires Python3.4 or better.

Introduction

QuantiPhy is a light-weight package that allows numbers to be combined with units into physical quantities. Physical quantities are very commonly encountered when working with real-world systems when numbers are involved. And when encountered, the numbers often use SI scale factors to make them easier to read and write. For example, imagine trying to determine the rise time and bandwidth of a simple RC circuit:

>>> from quantiphy import Quantity
>>> from math import pi

>>> r = Quantity('R = 1kOhm')
>>> c = Quantity('C = 1nF')
>>> tau = Quantity(r*c, 'Tau s')
>>> bw = Quantity(1/(2*pi*tau), 'BW Hz')
>>> print('Given: {0:Q}, {1:Q}.'.format(r, c))
Given: R = 1kOhm, C = 1nF.

>>> print('Computed: {0:Q}, {1:Q}.'.format(tau, bw))
Computed: Tau = 1us, BW = 159.15kHz.

A quantity is the pairing of a real number and units, though the units are optional. The Quantity class is used to combine the pair into a single object, and then provides methods to provide access to the pair in useful ways. In the above example quantities were created from strings that contained the value and unit (ex. ‘1nF’) or from arguments where the value and units were specified explicitly (ex. r*c, ‘s’). Once created, the quantity objects can be treated like simple real values, but when printed, their values presented with their units using SI scale factors.

Quantities

The Quantity class is used to create a quantity (an object with both a value and units). QuantiPhy also allows a name and description to be associated with the quantity. Normally, creating a quantity takes one or two arguments. The first is taken to be the value, and the second, if given, is taken to be the model, which is a source of default values. More on this in a bit, but for the time being you can assume the model is a string that contains the units for the quantity. The value may be given as a float or as a string. The string may be in floating point notation, in scientific notation, or with SI scale factors and may include the units. In addition, you may give a name and description. For example, any of the following ways can be used to specify 1ns:

>>> period = Quantity(1e-9, 's')
>>> print(period)
1ns

>>> period = Quantity('0.000000001 s')
>>> print(period)
1ns

>>> period = Quantity('1e-9s')
>>> print(period)
1ns

>>> period = Quantity('1ns')
>>> print(period)
1ns

So far our 1ns is just a value. But it can be given a name and/or description as follows:

>>> period = Quantity('period = 10ns -- clock period')
>>> print(period.name, '=', period, '#', period.desc)
period = 10ns # clock period

If you only specify a real number for the value, then the units, name, and description do not get values. This is where the second argument, the model, helps. It may be another quantity or it may be a string. Any attributes that are not provided by the first argument are taken from the second if available. If the second argument is a string, it is split. If it contains one value, that value is taken to be the units, if it contains two, those values are taken to be the name and units, and it it contains more than two, the remaining values are taken to be the description. For example:

>>> out_period = Quantity(10*period, period)
>>> print(out_period.name, '=', out_period, '#', out_period.desc)
period = 100ns # clock period

>>> freq = Quantity(100e6, 'Hz')
>>> print(freq)
100MHz

>>> freq = Quantity(100e6, 'Fin Hz')
>>> print(freq.name, '=', freq, '#', freq.desc)
Fin = 100MHz #

>>> freq = Quantity(100e6, 'Fin Hz Input frequency')
>>> print(freq.name, '=', freq, '#', freq.desc)
Fin = 100MHz # Input frequency

In addition, you can explicitly specify the units, the name, and the description using named arguments. These values override anything specified in the value or the model.

>>> out_period = Quantity(
...     10*period, period, name='output period',
...     desc='period at output of frequency divider'
... )
>>> print(out_period.name, '=', out_period, '#', out_period.desc)
output period = 100ns # period at output of frequency divider

Finally, you can overwrite the quantities attributes to override the units, name, or description.

>>> out_period = Quantity(10*period)
>>> out_period.units = 's'
>>> out_period.name = 'output period'
>>> out_period.desc = 'period at output of frequency divider'
>>> print(out_period.name, '=', out_period, '#', out_period.desc)
output period = 100ns # period at output of frequency divider

From a quantity object, you access its value in various ways:

>>> h_line = Quantity('1420.405751786 MHz')

>>> h_line.as_tuple()
(1420405751.786, 'Hz')

>>> str(h_line)
'1.4204GHz'

>>> h_line.render()
'1.4204GHz'

>>> h_line.render(si=False)
'1.4204e9Hz'

You can also access the value without the units:

>>> float(h_line)
1420405751.786

>>> h_line.render(False)
'1.4204G'

>>> h_line.render(False, si=False)
'1.4204e9'

Or you can access just the units:

>>> h_line.units
'Hz'

You can also access the full precision of the quantity:

>>> h_line.render(prec='full')
'1.420405751786GHz'

>>> h_line.render(si=False, prec='full')
'1.420405751786e9Hz'

Full precision implies whatever precision was used when specifying the quantity if it was specified as a string. If it was specified as a real number, then a fixed, user controllable number of digits are used (default=12). Generally one uses ‘full’ when generating output that will be read by a machine. Conversely it is best not to use ‘full’ when generating output meant for humans to read.

If you specify fmt to render, it will generally include the name and perhaps the description if they are available. The formatting is controlled by ‘assign_fmt’, which is described later. With the default formatting, the description is not printed.

>>> h_line.render(fmt=True)
'1.4204GHz'
>>> out_period.render(fmt=True)
'output period = 100ns'

Quantities As Reals

You can use a quantity in the same way that you can use a real number, meaning that you can use it in expressions and it will evaluate to its real value:

>>> period = Quantity('1us')
>>> print(period)
1us

>>> frequency = 1/period
>>> print(frequency)
1000000.0

>>> type(period)
<class 'quantiphy.Quantity'>

>>> type(frequency)
<class 'float'>

Notice that when performing arithmetic operations on quantities the units are completely ignored.

Preferences

You can adjust some of the behavior of these functions on a global basis using set_preferences:

>>> Quantity.set_preferences(prec=2, spacer=' ')
>>> h_line.render()
'1.42 GHz'

>>> h_line.render(prec=4)
'1.4204 GHz'

Specifying prec (precision) as 4 gives 5 digits of precision (you get one more digit than the number you specify for precision). Thus, the common range for prec is from 0 to around 12 to 14 for double precision numbers.

Passing None as a value in set_preferences returns that preference to its default value:

>>> Quantity.set_preferences(prec=None, spacer=None)
>>> h_line.render()
'1.4204GHz'

The available preferences are:

si (bool):

Use SI scale factors by default. Default is True.

units (bool):

Output units by default. Default is True.

prec (int):

Default precision in digits where 0 corresponds to 1 digit, must be nonnegative. This precision is used when full precision is not requested. Default is 4 digits.

full_prec (int):

Default full precision in digits where 0 corresponds to 1 digit. Must be nonnegative. This precision is used when full precision is requested if the precision is not otherwise known. Default is 12 digits.

spacer (str):

May be ‘’ or ‘ ‘, use the latter if you prefer a space between the number and the units. Generally using ‘ ‘ makes numbers easier to read, particularly with complex units, and using ‘’ is easier to parse. Default is ‘’.

unity_sf (str):

The output scale factor for unity, generally ‘’ or ‘_’. Default is ‘’.

output_sf (str):

Which scale factors to output, generally one would only use familiar scale factors. Default is ‘TGMkmunpfa’.

ignore_sf (bool):

Whether scale factors should be ignored by default. Default is False.

reltol (real):

Relative tolerance, used by is_close() when determining equivalence.

abstol (real):

Absolute tolerance, used by is_close() when determining equivalence.

keep_components (bool):

Whether components of number should be kept if the quantities’ value was given as string. Doing so takes a bit of space, but allows the original precision of the number to be recreated when full precision is requested.

assign_fmt (str):

Format string for an assignment. Will be passed through string format method. Format string takes three possible arguments named n, q, and d for the name, value and description. The default is ‘{n} = {v}’

assign_rec (str):

Regular expression used to recognize an assignment. Used in add_to_namespace(). Default recognizes the form:

“Temp = 300_K – Temperature”.

Exceptional Values

You can test whether the value of the quantity is infinite or is not-a-number.

>>> h_line.is_infinite()
False

>>> h_line.is_nan()
False

Equivalence

You can determine whether the value of a quantity or real number is equivalent to that of a quantity. The two values need not be identical, they just need to be close to be deemed equivalent. The reltol and abstol preferences are used to determine if they are close.

>>> h_line.is_close(h_line)
True

>>> h_line.is_close(h_line + 1)
True

>>> h_line.is_close(h_line + 1e4)
False

Physical Constants

The Quantity class also supports a small number of predefined physical constants.

Plank’s constant:

>>> plank = Quantity('h')
>>> print(plank)
662.61e-36J-s

>>> rplank = Quantity('hbar')
>>> print(rplank)
105.46e-36J-s

Boltzmann’s constant: Boltzmann’s constant:

>>> boltz = Quantity('k')
>>> print(boltz)
13.806e-24J/K

Elementary charge:

>>> q = Quantity('q')
>>> print(q)
160.22e-21C

Speed of light:

>>> c = Quantity('c')
>>> print(c)
299.79Mm/s

Zero degrees Celsius in Kelvin:

>>> zeroC = Quantity('C0')
>>> print(zeroC)
273.15K

QuantiPhy uses k rather than K to represent kilo so that you can distinguish between kilo and Kelvin.

Permittivity of free space:

>>> eps0 = Quantity('eps0')
>>> print(eps0)
8.8542pF/m

Permeability of free space:

>>> mu0 = Quantity('mu0')
>>> print(mu0)
1.2566uH/m

Characteristic impedance of free space:

>>> Z0 = Quantity('Z0')
>>> print(Z0)
376.73Ohms

You can add additional constants by adding them to the CONSTANTS dictionary:

>>> from quantiphy import Quantity, CONSTANTS
>>> CONSTANTS['h_line'] = (1.420405751786e9, 'Hz')
>>> h_line = Quantity('h_line')
>>> print(h_line)
1.4204GHz

The value of the constant may be a tuple or a string. If it is a string, it will be interpreted as if it were passed as the primary argument to Quantity. If it is a tuple, it may contain up to 4 values, the value, the units, the name, and the description. This value may also be a string, and if so it must contain a simple number. The benefit of using a string in this case is that QuantiPhy will recognize the significant figures and use them as the full precision for the quantity.

>>> CONSTANTS['lambda'] = 'λ = 211.0611405389mm -- wavelength of hydrogen line'
>>> print('{:S}'.format(Quantity('lambda')))
λ = 211.06mm
>>> CONSTANTS['lambda'] = (Quantity('c')/h_line,)
>>> print('{:S}'.format(Quantity('lambda')))
211.06m
>>> CONSTANTS['lambda'] = (Quantity('c')/h_line, 'm')
>>> print('{:S}'.format(Quantity('lambda')))
211.06mm
>>> CONSTANTS['lambda'] = (Quantity('c')/h_line, 'm', 'λ')
>>> print('{:S}'.format(Quantity('lambda')))
λ = 211.06mm
>>> CONSTANTS['lambda'] = (Quantity('c')/h_line, 'm', 'λ', 'wavelength of hydrogen line')
>>> print('{:S}'.format(Quantity('lambda')))
λ = 211.06mm

String Formatting

Quantities can be passed into the string format method:

>>> print('{}'.format(h_line))
1.4204GHz

>>> print('{:s}'.format(h_line))
1.4204GHz

In these cases the preferences for use of SI scale factors and outputting of units is honored.

You can specify the precision as part of the format specification

>>> print('{:.6}'.format(h_line))
1.420406GHz

You can also specify the width and alignment.

>>> print('|{:15.6}|'.format(h_line))
|1.420406GHz    |

>>> print('|{:<15.6}|'.format(h_line))
|1.420406GHz    |

>>> print('|{:>15.6}|'.format(h_line))
|    1.420406GHz|

The ‘q’ type specifier can be used to explicitly indicate that both the number and the units are desired and that SI scale factors should be used, regardless of the current preferences.

>>> print('{:.6q}'.format(h_line))
1.420406GHz

Alternately, ‘r’ can be used to indicate just the number represented using SI scale factors is desired, and the units should not be included.

>>> print('{:r}'.format(h_line))
1.4204G

You can also use the floating point format type specifiers:

>>> print('{:f}'.format(h_line))
1420405751.7860

>>> print('{:e}'.format(h_line))
1.4204e+09

>>> print('{:g}'.format(h_line))
1.4204e+09

Use ‘u’ to indicate that only the units are desired:

>>> print('{:u}'.format(h_line))
Hz

Access the name or description of the quantity using ‘n’ and ‘d’.

>>> wavelength = Quantity('lambda')
>>> print('{:n}'.format(wavelength))
λ
>>> print('{:d}'.format(wavelength))
wavelength of hydrogen line

Using the upper case versions of the format codes that print the numerical value of the quantity (SQRFEG) to indicate that the name and perhaps description should be included as well. They are under the control of the assign_fmt preference.

>>> trise = Quantity('10ns', name='trise')

>>> print('{:S}'.format(trise))
trise = 10ns

>>> print('{:Q}'.format(trise))
trise = 10ns

>>> print('{:R}'.format(trise))
trise = 10n

>>> print('{:F}'.format(trise))
trise = 0.0000

>>> print('{:E}'.format(trise))
trise = 1.0000e-08

>>> print('{:G}'.format(trise))
trise = 1e-08

>>> print('{0:Q} ({0:d})'.format(wavelength))
λ = 211.06mm (wavelength of hydrogen line)

>>> Quantity.set_preferences(assign_fmt='{n} = {v} -- {d}')

>>> print('{:S}'.format(wavelength))
λ = 211.06mm -- wavelength of hydrogen line

You can also specify two values to *assign_fmt*, in which case the first is
used if there is a description and the second used otherwise.

>>> Quantity.set_preferences(assign_fmt=('{n} = {v} -- {d}', '{n} = {v}'))

>>> print('{:S}'.format(trise))
trise = 10ns

>>> print('{:S}'.format(wavelength))
λ = 211.06mm -- wavelength of hydrogen line

Exceptions

A ValueError is raised if Quantity is passed a string it cannot convert into a number:

>>> try:
...     q = Quantity('xxx')
... except ValueError as err:
...     print(err)
xxx: not a valid number.

Add to Namespace

It is possible to put a collection of quantities in a text string and then use the add_to_namespace function to parse the quantities and add them to the Python namespace. For example:

>>> design_parameters = '''
...     Fref = 156 MHz  -- Reference frequency
...     Kdet = 88.3 uA  -- Gain of phase detector (Imax)
...     Kvco = 9.07 GHz/V  -- Gain of VCO
... '''
>>> Quantity.add_to_namespace(design_parameters)

>>> print(Fref, Kdet, Kvco, sep='\n')
156MHz
88.3uA
9.07GHz/V

Any number of quantities may be given, with each quantity given on its own line. The identifier given to the left ‘=’ is the name of the variable in the local namespace that is used to hold the quantity. The text after the ‘–’ is used as a description of the quantity.

Scale Factors and Units

By default, QuantiPhy treats both the scale factor and the units as being optional. With the scale factor being optional, the meaning of some specifications can be ambiguous. For example, ‘1m’ may represent 1 milli or it may represent 1 meter. Similarly, ‘1meter’ my represent 1 meter or 1 milli-eter. To allow you to avoid this ambiguity, QuantiPhy accepts ‘_’ as the unity scale factor. In this way ‘1_m’ is unambiguously 1 meter. You can instruct QuantiPhy to output ‘_’ as the unity scale factor by specifying the unity_sf argument to set_preferences:

>>> Quantity.set_preferences(unity_sf='_')
>>> l = Quantity(1, 'm')
>>> print(l)
1_m

If you need to interpret numbers that have units and are known not to have scale factors, you can specify the ignore_sf preference:

>>> Quantity.set_preferences(ignore_sf=True, unity_sf='')
>>> l = Quantity('1000m')
>>> l.as_tuple()
(1000.0, 'm')

>>> print(l)
1km

Subclassing Quantity

By subclassing Quantity you can create difference sets of default behaviors that are active simultaneously. For example:

>>> class ConventionalQuantity(Quantity):
...     pass
>>> ConventionalQuantity.set_preferences(si=False, units=False)
>>> period1 = Quantity(1e-9, 's')
>>> period2 = ConventionalQuantity(1e-9, 's')
>>> print(period1, period2)
1ns 1e-9

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

quantiphy-0.2.0.tar.gz (25.3 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page