summaryrefslogtreecommitdiff
path: root/docs/contexts.rst
blob: 4221fe718caa7332a1045e6a3880f962f52eef65 (plain)
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
.. _contexts:

Contexts
========

If you work frequently on certain topics, you will probably find the need to
convert between dimensions based on some pre-established (physical)
relationships. For example, in spectroscopy you need to transform from
wavelength to frequency. These are incompatible units and therefore Pint will
raise an error if you do this directly:

.. doctest::

    >>> import pint
    >>> ureg = pint.UnitRegistry()
    >>> q = 500 * ureg.nm
    >>> q.to('Hz')
    Traceback (most recent call last):
    ...
    DimensionalityError: Cannot convert from 'nanometer' ([length]) to 'hertz' (1 / [time])


You probably want to use the relation `frequency = speed_of_light / wavelength`:

.. doctest::

    >>> (ureg.speed_of_light / q).to('Hz')
    <Quantity(5.99584916e+14, 'hertz')>


To make this task easy, Pint has the concept of `contexts` which provides
conversion rules between dimensions. For example, the relation between
wavelength and frequency is defined in the `spectroscopy` context (abbreviated
`sp`). You can tell pint to use this context when you convert a quantity to
different units.

.. doctest::

    >>> q.to('Hz', 'spectroscopy')
    <Quantity(5.99584916e+14, 'hertz')>

or with the abbreviated form:

.. doctest::

    >>> q.to('Hz', 'sp')
    <Quantity(5.99584916e+14, 'hertz')>

Contexts can be also enabled for blocks of code using the `with` statement:

.. doctest::

    >>> with ureg.context('sp'):
    ...     q.to('Hz')
    <Quantity(5.99584916e+14, 'hertz')>

If you need a particular context in all your code, you can enable it for all
operations with the registry::

    >>> ureg.enable_contexts('sp')

To disable the context, just call::

    >>> ureg.disable_contexts()


Enabling multiple contexts
--------------------------

You can enable multiple contexts:

    >>> q.to('Hz', 'sp', 'boltzmann')
    <Quantity(5.99584916e+14, 'hertz')>

This works also using the `with` statement:

    >>> with ureg.context('sp', 'boltzmann'):
    ...     q.to('Hz')
    <Quantity(5.99584916e+14, 'hertz')>

or in the registry:

    >>> ureg.enable_contexts('sp', 'boltzmann')
    >>> q.to('Hz')
    <Quantity(5.99584916e+14, 'hertz')>

If a conversion rule between two dimensions appears in more than one context,
the one in the last context has precedence. This is easy to remember if you
think that the previous syntax is equivalent to nest contexts:

    >>> with ureg.context('sp'):
    ...     with ureg.context('boltzmann') :
    ...         q.to('Hz')
    <Quantity(5.99584916e+14, 'hertz')>


Parameterized contexts
----------------------

Contexts can also take named parameters. For example, in the spectroscopy you
can specify the index of refraction of the medium (`n`). In this way you can
calculate, for example, the wavelength in water of a laser which on air is 530 nm.

.. doctest::

    >>> wl = 530. * ureg.nm
    >>> f = wl.to('Hz', 'sp')
    >>> f.to('nm', 'sp', n=1.33)
    <Quantity(398.496240602, 'nanometer')>

Contexts can also accept Pint Quantity objects as parameters. For example, the
'chemistry' context accepts the molecular weight of a substance (as a Quantity
with dimensions of [mass]/[substance]) to allow conversion between moles and
mass.

.. doctest::

    >>> substance = 95 * ureg('g')
    >>> substance.to('moles', 'chemistry', mw = 5 * ureg('g/mol'))
    <Quantity(19.0, 'mole')>


Ensuring context when calling a function
----------------------------------------

Pint provides a decorator to make sure that a function called is done within a given
context. Just like before, you have to provide as argument the name (or alias) of the
context and the parameters that you wish to set.


.. doctest::

    >>> wl = 530. * ureg.nm
    >>> @ureg.with_context('sp', n=1.33)
    ... def f(wl):
    ...     return wl.to('Hz').magnitude
    >>> f(wl)
    398.496240602


This decorator can be combined with **wraps** or **check** decorators described in
:doc:`wrapping`.


Defining contexts in a file
---------------------------

Like all units and dimensions in Pint, `contexts` are defined using an easy to
read text syntax. For example, the definition of the spectroscopy
context is::

    @context(n=1) spectroscopy = sp
        # n index of refraction of the medium.
        [length] <-> [frequency]: speed_of_light / n / value
        [frequency] -> [energy]: planck_constant * value
        [energy] -> [frequency]: value / planck_constant
    @end

The `@context` directive indicates the beginning of the transformations which
are finished by the `@end` statement. You can optionally specify parameters for
the context in parenthesis. All parameters are named and default values are
mandatory. Multiple parameters are separated by commas (like in a python
function definition). Finally, you provide the name of the context (e.g.
spectroscopy) and, optionally, a short version of the name (e.g. sp) separated
by an equal sign. See the definition of the 'chemistry' context in
default_en.txt for an example of a multiple-parameter context.

Conversions rules are specified by providing source and destination dimensions
separated using a colon (`:`) from the equation. A special variable named
`value` will be replaced by the source quantity. Other names will be looked
first in the context arguments and then in registry.

A single forward arrow (`->`) indicates that the equations is used to transform
from the first dimension to the second one. A double arrow (`<->`) is used to
indicate that the transformation operates both ways.

Context definitions are stored and imported exactly like custom units
definition file (and can be included in the same file as unit definitions). See
"Defining units" for details.

Defining contexts programmatically
----------------------------------

You can create `Context` object, and populate the conversion rules using python
functions. For example:

.. doctest::

    >>> ureg = pint.UnitRegistry()
    >>> c = pint.Context('ab')
    >>> c.add_transformation('[length]', '[time]',
    ...                      lambda ureg, x: x / ureg.speed_of_light)
    >>> c.add_transformation('[time]', '[length]',
    ...                      lambda ureg, x: x * ureg.speed_of_light)
    >>> ureg.add_context(c)
    >>> ureg("1 s").to("km", "ab")
    299792.458 kilometer

It is also possible to create anonymous contexts without invoking add_context:

   >>> c = pint.Context()
   ...
   >>> ureg("1 s").to("km", c)
   299792.458 kilometer