Before we get started, we'll establish settings for this IPython notebook.
%matplotlib inline
%precision 4
u'%.4f'
First, we'll import the metre (m):
from natu.units import m
It's a scalar unit with a value of 1 (since SI is the default unit system) and dimension of length ('L'
). Its display unit is itself ('m'
) and it is prefixable (prefixable = True
):
m
ScalarUnit(1, 'L', 'm', True) (m)
When we multiply a number by a unit, we get a quantity:
length = 0.0254*m
length
Quantity(0.0254, 'L', 'm') (0.0254 m)
The value is 0.0254, the dimension is still length ('L'
), and the display unit is the metre ('m'
). The contents of the second parentheses (0.0254 m) is the default string format:
print(length)
0.0254 m
Let's change the display unit to the inch:
length.display = 'inch'
length
Quantity(0.0254, 'L', 'inch') (1.0 inch)
Notice that the value did not change, but it will now display as one inch. We could also express the length in inches by dividing it by the inch:
from natu.units import inch
length/inch
1.0000
Nothing all that special has happened here. The value of the length
quantity was divided by the value of the inch unit, and the dimensions were handled accordingly. The result was dimensionless, so it was returned as a float. Since the result was 1.0, we expect the value of the inch to be 0.0254, and it is:
from natu.units import inch
inch
ScalarUnit(0.0254, 'L', 'inch', False) (inch)
natu doesn't use conversion factors internally, but we can use natu to determine them. We would expect to use division:
inch/m
0.0254
but the result may seem counterintuitive at first. We divided the inch by the metre, but we got the conversion factor from inches to metres. The conversion factor from unit A to unit B is the number of units B in one unit A. Mathematically, this is x*B = 1*A, where x is the conversion factor. The solution is x = A/B. In this case A is the inch and B is the metre, so we have the conversion factor from inches to metres (which happens to be the number we used in the previous section).
Remember that in natu we deal with quantities, not numbers. Numbers are unit-dependent, but quantities are not. A quantity is expressed as the product of a number and a unit (q = n*U). When we say "in unit," we generally mean "divided by unit." So "quantity in unit" is q/U or n, the number.
If a unit a prefixable, we can import it with a prefix. For example,
from natu.units import km
km/m
1000.0000
We can access units directly from the units module, with or without prefixes:
from natu import units as U
U.km/U.m
1000.0000
However, if we use a wildcard import (from natu.units import *
), we only get the units which are explicitly defined in the INI files. Typically this does not include prefixed units.
Nonscalar units such as Celsius, Fahrenheit, and the decibel are available:
from natu.units import degC, degF, dB
These units are called lambda units because they involve invertible functions that are not limited to multiplication and division. For convenience, however, lambda units are overloaded to use the multiplication and division operators (*
and /
). The number must always be on the left side of the multiplication operator:
temperature = 25*degC
We can convert this temperature to kelvin (a scalar unit):
from natu.units import K
temperature/K
298.1500
or to Fahrenheit (another lambda unit):
temperature/degF
77.0000
Notice that the quantity (temperature) is on the left side of the operator. This is important.
We can add temperatures that have been created from different units:
print(0*degC + 100*K)
100.0 degC
The display unit of the quantity on the left side of the addition takes precedence. natu checks that the dimensions are compatible when performing arithmetic, so a temperature can only be added to another temperature. Note, however, that temperatures are absolute quantities, regardless of the unit used to express them. The sum of 25 ℃ and 25 ℃ is not 50 ℃, but 323.15 ℃ (roughly 600 K).
print(25*degC + 25*degC)
323.15 degC
To demonstrate the decibel, we will multiply two numbers by adding their logarithms:
(10/dB + 10/dB)*dB
100.0000
from natu.units import mdegC
100*mdegC/K
273.2500
By default, units are simplified using coherent relations:
from natu.units import m, kg, s
print(kg*m**2/s**2)
J
The level of simplification can be adjusted using simplification_level
in natu.config.
For convenience, the units from natu.units are copied and sorted into submodules within natu.groups. There are submodules for constants (constants), SI units (si), and units of various dimensions (length, time, pressure, etc.). Let's look at the length submodule:
from natu.groups import length
help(length)
Help on package natu.groups.length in natu.groups: NAME natu.groups.length - Aliases for constants and units of length, with support for prefixes FILE /usr/local/lib/python2.7/dist-packages/natu-0.1.0_a-py2.7.egg/natu/groups/length.py DESCRIPTION Default contents: M, a_0, angstrom, au, cm, ft, inch, l_H, ly, m, mi, pc, pica, point, yd Prefixable subset: ly, m PACKAGE CONTENTS DATA __all__ = ['ft', 'cm', 'a_0', 'mi', 'point', 'M', 'yd', 'pc', 'm', 'au...
It contains 15 units and constants, two of which are prefixable. We can import these just as from natu.units: individually (from natu.groups.length import m
), by access (from natu.groups import length; m = length.m
), or by wildcard (from natu.groups.length import *
).
Quantities can be used in numpy arrays:
from numpy import arange
lengths = arange(4)*m
lengths
array([Quantity(0, 'L', 'm') (0.0 m), Quantity(1, 'L', 'm') (1.0 m), Quantity(2, 'L', 'm') (2.0 m), Quantity(3, 'L', 'm') (3.0 m)], dtype=object)
Notice that this is an array of quantities, not a quantity with a value that is an array. The value of a quantity can be complex:
(1 + 1j)*m
Quantity(1+1j, 'L', 'm') ((1+1j) m)
By default, units and dimensions are formatted with '*
' as the multiplication operator. Exponents directly follow the units and dimensions (without '**
' or '^
'). If necessary, a division sign is used (instead of negative exponents) and the denominator is placed in parentheses if it has more than one factor. Let's see how this looks for the Ampere constant (k_A):
from natu.units import k_A
k_A.display = 'N/A2'
print(u'The Ampere constant is {0} (dimension {0.dimension}).'.format(k_A))
The Ampere constant is 1e-07 N/A2 (dimension L*M/(I2*T2)).
We can change the format using advanced string formatting:
print(u'Modelica format: {0:M} (dimension {0.dimension:M})'.format(k_A))
print(u'Verbose format: {0:V} (dimension {0.dimension:V})'.format(k_A))
print(u'HTML format: {0:H} (dimension {0.dimension:H})'.format(k_A))
print(u'LaTeX format: ${0:L}$ (dimension ${0.dimension:L}$)'.format(k_A))
print(u'Pretty format: {0:P} (dimension {0.dimension:P})'.format(k_A))
Modelica format: 1e-07 N/A2 (dimension L.M/(I2.T2)) Verbose format: 1e-07 N / A**2 (dimension L * M / (I**2 * T**2)) HTML format: 1×10<sup>-7</sup> N A<sup>-2</sup> (dimension L M I<sup>-2</sup> T<sup>-2</sup>) LaTeX format: $1 \times 10^{-7}\,\mathrm{N}\,\mathrm{A}^{-2}$ (dimension $\mathrm{L}\,\mathrm{M}\,\mathrm{I}^{-2}\,\mathrm{T}^{-2}$) Pretty format: 1✕10⁻⁷ N A⁻² (dimension L M I⁻² T⁻²)
The HTML output renders as 1×10-7 N A-2 (dimension L M I-2 T-2), and the LaTeX output renders as $1 \times 10^{-7}\,\mathrm{N}\,\mathrm{A}^{-2}$ (dimension $\mathrm{L}\,\mathrm{M}\,\mathrm{I}^{-2}\,\mathrm{T}^{-2}$). Notice that we explicitly added $...$ around the LaTeX output because the math mode is required. The pretty format can only be used with integer exponents.
We can also change the format of the number using Python's built-in formatting. For example,
print(u'Pretty format with 7 decimal places: {0:.7fP}'.format(k_A))
Pretty format with 7 decimal places: 0.0000001 N A⁻²
As we saw earlier, each unit has a display unit. It defaults to the unit itself, but it can be changed. The display unit is propagated to a quantity when it is generated from a unit. We can use this feature to create a quantity using one unit that displays in another. For example, to enter length in yards but display it in meters:
from natu.units import yd
yd.display = 'm'
print(1*yd)
0.9144 m
Besides the display unit, a unit is essentially immutable. Its value is a protected attribute (_value
) and its dimension and prefixable attributes cannot be changed.
The best way to introduce new units is to swap or add to the INI files that define the units. However, we can also create units programmatically.
If the new unit is a coherently derived unit, then we can can create it directly from existing units since the product or quotient of two units is generally another unit (as discussed here). For example, we can create a unit for the cubic inch as follows:
cinch = inch**3
cinch
ScalarUnit(1.63871e-05, 'L3', 'inch3', False) (inch3)
However, notice that the display unit is not the new unit but rather inch3:
print(10*cinch)
10.0 inch3
We can update the display unit, but the new unit will not actually be available for display until we insert it into the unit space:
cinch.display = 'cinch'
from natu import units
units.cinch = cinch
The unit will exist in the unit space until the next Python session. Now we get the desired result:
print(10*cinch)
10.0 cinch
If the expression of the new unit involves a numerical factor (not coherently derived), then the result is a quantity that we need to explicitly cast as a unit:
from natu.types import ScalarUnit
from natu.units import ns
shake = ScalarUnit.fromQuantity(10*ns, 'shake')
The display unit has been set, but we still need to insert the unit into the unit space before we can use it:
units.shake = shake
Now it is ready for use:
time = 500*ns
time.display = 'shake'
print(time)
50.0 shake