0

I am trying to use pint in a situation where I need to do transformations between dimensions, eg. fluid ounces to grams.

The values I need for my transformation are in a database and change for various substances (eg. different densities for different liquids), so I'm using the Context.add_transformation() method to dynamically create my transformation. Here's my test program:

#!/opt/env/upgrade/bin/python
import pint

ureg = pint.UnitRegistry()
c = pint.Context('ab')

def vtom(ureg, x):
    # here I print some output just to see if this function is ever called
    # and what arguments are passed.  It is not being called.
    print "vtom(%s, %s)" % (ureg, x)

    # right now I just return the input, so it's 1:1.  I know this is 
    # not right, I am just trying to get pint to find the transform and use it.
    return x

c.add_transformation('[volume]', '[mass]', vtom)
ureg.add_context(c)

PQ = ureg.Quantity

# this works fine    
a = PQ(1 * ureg.oz)
print '%s = %s' % (a, a.to('gram'))

b = PQ(10 * ureg.floz)
c = PQ(10 * ureg.gram)

# both these fail, see exception output below
print '%s = %s' % (c, c.to('floz'))
print '%s = %s' % (b, b.to('gram'))

The output:

1 ounce = 28.349523125 gram
Traceback (most recent call last):
  File "./p.py", line 26, in <module>
    print '%s = %s' % (b, b.to('gram'))
  File "/opt/env/upgrade/local/lib/python2.7/site-packages/pint/quantity.py", line 332, in to
    magnitude = self._convert_magnitude_not_inplace(other, *contexts, **ctx_kwargs)
  File "/opt/env/upgrade/local/lib/python2.7/site-packages/pint/quantity.py", line 300, in _convert_magnitude_not_inplace
    return self._REGISTRY.convert(self._magnitude, self._units, other)
  File "/opt/env/upgrade/local/lib/python2.7/site-packages/pint/registry.py", line 686, in convert
    return self._convert(value, src, dst, inplace)
  File "/opt/env/upgrade/local/lib/python2.7/site-packages/pint/registry.py", line 1214, in _convert
    return super(ContextRegistry, self)._convert(value, src, dst, inplace)
  File "/opt/env/upgrade/local/lib/python2.7/site-packages/pint/registry.py", line 937, in _convert
    return super(NonMultiplicativeRegistry, self)._convert(value, src, dst, inplace)
  File "/opt/env/upgrade/local/lib/python2.7/site-packages/pint/registry.py", line 708, in _convert
    raise DimensionalityError(src, dst, src_dim, dst_dim)
pint.errors.DimensionalityError: Cannot convert from 'fluid_ounce' ([length] ** 3) to 'gram' ([mass])

It seems that pint is not finding my tranformation. In the units text file (the default one that comes with pint), '[volume] = [length] ** 3' is defined, so it should be able to walk the graph and find '[mass]'.. or so I thought...

Thanks!

little_birdie
  • 5,600
  • 3
  • 23
  • 28

1 Answers1

0

It looks like you need to specify what context you need to use when you call .to since your context is called 'ab' you should write c.to('floz', 'ab'). This solves the problem of why your function vtom isn't being called.

It also looks like you have to define the transformation in both directions:

#!/opt/env/upgrade/bin/python
import pint

ureg = pint.UnitRegistry()
c = pint.Context('ab')

def vtom(ureg, x):
    print ("vtom(%s, %s)" % (ureg, x))
    density = 0.9 * ureg.gram / ureg.ml
    return x/density

def vtom2(ureg, x):
    print ("vtom2(%s, %s)" % (ureg, x))
    density = 0.9 * ureg.gram / ureg.ml
    return x*density

c.add_transformation('[volume]', '[mass]', vtom2)
c.add_transformation('[mass]', '[volume]', vtom)
ureg.add_context(c)
#ureg.enable_contexts('ab')
PQ = ureg.Quantity

# this works fine    
a = PQ(1 * ureg.oz)
print ('%s = %s' % (a, a.to('gram')))

b = PQ(10 * ureg.floz)
c = PQ(10 * ureg.gram)

# both these fail, see exception output below
print ('%s = %s' % (c, c.to('floz', 'ab')))
print ('%s = %s' % (b, b.to('gram','ab')))

(I guess you'll need different functions for each direction).

Output

1 ounce = 28.349523125 gram
vtom(<pint.registry.UnitRegistry object at 0x0390CDB0>, 10 gram)
10 gram = 0.37571136335381117 fluid_ounce
vtom2(<pint.registry.UnitRegistry object at 0x0390CDB0>, 10 fluid_ounce)
10 fluid_ounce = 266.16176606249996 gram

Edit: As mentioned in the comments below, I think the original error is most likely because the library does some dimensional analysis on the returned value to check that it makes sense and raises an error if it doesn't. So for example, a mass can be a density times a volume, but it can't be a volume times a scalar because that would just be a bigger/smaller volume, not a mass.

tim-mccurrach
  • 6,395
  • 4
  • 23
  • 41
  • Ahh I have misunderstood `add_context`, now I get it. In my case, there are potentially thousands of contexts so I may have to see about removing the context from the ureg when I am done with it. – little_birdie Jun 14 '18 at 23:07
  • So let's say I know that `1ml = 0.9g`. So in `vtom` for volume to mass, I convert x to ml and multiply by 0.9, eg. `x.to('ml') * Decimal('0.9')`.. now I have my answer.. but in units of `ml`, that is to say, it is a Quanity object with a unit of 'ml', which is not right. I wish to return that same number with a unit of `g`. How do I do that? Thanks! – little_birdie Jun 14 '18 at 23:12
  • I've updated my answer to reflect those requirements. I'm not sure if this is the best way to do it as I've never used this library before but this seems to work. – tim-mccurrach Jun 14 '18 at 23:18
  • (Also bare in mind that if, like me, you're based in the UK - pint is using US fluid ounces, which caused me considerable confusion for a bit). – tim-mccurrach Jun 14 '18 at 23:30
  • Thank you! I'm not sure why dividing quantities of dissimilar units works the way it does, but it does seem to work. – little_birdie Jun 14 '18 at 23:36
  • Your welcome. My guess is the library does some dimensional analysis on the returned value to check that it makes sense and raises an error if it doesn't. So for example, a mass can be a density times a volume, but it can't be a volume times a scalar because that would just be a bigger/smaller volume, not a mass. – tim-mccurrach Jun 14 '18 at 23:41