Search code examples
pythonpint

Setup a conversion rate in pint in python


I have this problem I'd like to solve with python pint.

  • 0.29 points / minutes
  • 118 points / (17.35 usd)
  • How much is 5 minutes in usd ?

I have then 2 new abstract units "points" and "usd" and reuse the "time" unit "minute".

I can use simple mathematical operations for that :

import pint
units = pint.UnitRegistry()

units.define("usd = [usd]")
units.define("points = [points]")

a = 0.29 * units.points / units.minutes
b = 118 * units.points / (17.35 * units.usd)

Now I was to have 5 minutes to usd but of course I need to to the computation myself, do I multiply or divide by a ? Same for b ? The answer is one of those line:

5 * units.minutes * a * b
5 * units.minutes * a / b
5 * units.minutes / a * b
5 * units.minutes / a / b

Of course, only one of those line gives unit "usd". So I could compute all the possible ones and keep the one where the unit is usd. Turns out it's the second one.

I wish the library would find a path to convert my "5 minutes" using those relations in this context.

I have read about pint contexts but I didn't find any working solution. I was hoping to be able to write something like this:

(5 * units.minutes).to(units.usd, mycontext_with_the_two_equations)

Solution

  • Manual

    From that conversion table, you need to find "usd per minute" which is a/b:

    a/b = (0.29 * units.points / units.minutes) / (118 * units.points / (17.35 * units.usd)) 
    = (0.29 * (17.35 * units.usd)) / (units.minutes * 118) 
    = 0.0426*units.usd / units.minutes
    

    Then you can multiply it with 5, here is the full code:

    import pint
    units = pint.UnitRegistry()
    
    units.define("usd = [usd]")
    units.define("points = [points]")
    
    points_per_minute = 0.29 * units.points / units.minute
    points_per_usd = 118 * units.points / (17.35 * units.usd)
    usd_per_minute = points_per_minute / points_per_usd
    
    value_in_usd = (5 * units.minute) * usd_per_minute
    print(f"5 minutes is worth approximately: {value_in_usd:.2f}")
    # 5 minutes is worth approximately: 0.21 usd
    

    Context A

    You create context, register you conversion table to that context. Then you add that context into unit registry and now you can use unit registry like units().to()

    import pint
    
    units = pint.UnitRegistry()
    units.define("usd = [usd]")
    units.define("points = [points]")
    
    context = pint.Context("minute-points-usd")
    context.add_transformation("minute", "points", lambda ureg, x: x * 0.29 * ureg.points / ureg.minute)
    context.add_transformation("points", "usd", lambda ureg, x: x / (118 * ureg.points / (17.35 * ureg.usd)))
    units.add_context(context)
    a = units("5 min").to("usd", "minute-points-usd")
    print(a)
    

    Context B

    Same like above example, but this time you enter context using with statement:

    import pint
    
    units = pint.UnitRegistry()
    units.define("usd = [usd]")
    units.define("points = [points]")
    
    c = pint.Context("minute-points-usd")
    c.add_transformation("minute", "points", lambda ureg, x: x * 0.29 * ureg.points / ureg.minute)
    c.add_transformation("points", "usd", lambda ureg, x: x / (118 * ureg.points / (17.35 * ureg.usd)))
    units.add_context(c)
    
    with units.context("minute-points-usd"):
        value_in_usd = (5 * units.minute).to("usd")
        #value_in_usd = units("5 min").to("usd")