hgrecco / pint

Operate and manipulate physical quantities in Python
http://pint.readthedocs.org/
Other
2.41k stars 473 forks source link

Contexts: a new feature to simplify common conversions in specialized situations #65

Closed hgrecco closed 10 years ago

hgrecco commented 11 years ago

`If you work in certain topics, you usually need to convert from certain units to others based some pre-established relationships.

For example, you are working with EM fields in vacuum and you need to transform wavelength to frequency. If you try this:

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

You need to use the relation wavelength = speed_of_light * frequency.

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

The plan is to provide a context (abbreviated ctx) to facilitate this:

>>> q.to('Hz', ctx='m = c * s')
<Quantity(5.99584916e+14, 'hertz')>

or alternative as a context manager:

>>> with ureg.ctx('m = c * s'):
>>>     q.to('Hz')
<Quantity(5.99584916e+14, 'hertz')>

Some context will be predefined in the definitions file. The proposed syntax is:

@context spectroscopy = sp
    m = c * s

which then can be used as:

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

or with the abbreviated form:

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

You can define multiple relations in a single context (one per line in the definition file or in an tuple when you define it in situ)

Open points:

cbrunet commented 11 years ago

I hope you will define the relation as wavelength = speed_of_light / frequency instead... ;-)

It could be useful also to be able to parametrize the context. For example, the speed of light depends on the refractive index of the material. For such situation, I propose something like:

@context(n = 1) spectroscopy = sp
    m = c / n / Hz

then we could use it like

q.to('Hz', ctx='spectroscopy', n=1.444)

and towould be defined like

def to(self, other=None, ctx=None, **kwargs):

Off course, parameters would be optionals, and have default values.

It would also be useful to have a way to define a default context. For example:

ur = UnitRegistry() 
ur.context('spectroscopy', n=1.444)
hgrecco commented 11 years ago

I cannot really define wavelength = speed_of_light / frequency because pint is about units and dimensions. It knows about meter and second, about [length], [time], [frequency] but not about wavelength. You measure wavelengths in meters which and they have length dimension.

I understand that as a user this might not be the most straight forward way to declare. m = c * s looks strange. Maybe [length] = c * [time]. I am open to ideas.

I really like the idea of parametrization of the context. We just need find a (pythonic) way to indicate that n is a parameter, no something to take from the registry.

I also like the idea of a default context. I am actually looking into the idea of setting several defaults including format and base units. Context should be included as well.

cbrunet commented 11 years ago

Another proposition for definition of context could be something like:

@context spectroscopy = sp:
    @parameter n = []; default: 1 # unitless, with default value
    speed_of_light = 299792458 / n * meter / second = c # redefinition of a constant inside the context
    meter = c / hertz # wavelength <-> frequency
hgrecco commented 11 years ago

I think I like your original proposal of parametrization:

@context(n = 1) spectroscopy = sp
    m = c / n / Hz

and:

q.to('Hz', ctx='spectroscopy', n=1.444)

Let's try this and see how it works.

hgrecco commented 11 years ago

Implemented Context Parsing 4aba2db47707cffd4c7f49cfe0d423f32f16de4c