3

TLDR: I'd like to have pint quantities, that are in a certain (derived) dimension, to be converted into a pre-set unit by default.


Details:

I deal with 5 dimensions, as specified below. Note that [power] (usually in MW) and [price] (usually in Eur/MWh) are the derived dimensions.

# units.txt

# Base dimensions and their units
hour = [time] = h = hr
megawatthour = [energy] = MWh
euro = [currency] = Eur = €

# Derived dimensions and their units
[power] = [energy] / [time]
megawatt = megawatthour / hour = MW
[price] = [currency] / [energy]
euro_per_MWh = euro / megawatthour  = Eur/MWh

My question: is it possible to specify that calculated quantities with dimension [power] are by default to be converted to MW?


Here's an example:

import pint

ureg = pint.UnitRegistry("units.txt",)
ureg.default_format = ".0f~P"

energy = 100 * ureg.MWh
time = 4 * ureg.h
revenue = 4000 * ureg.euro

price = revenue / energy
power = energy / time

print(energy, time, revenue, price, power) 
# 100 MWh 4 h 4000 Eur 40 Eur/MWh 25 MWh/h

Here, the power is expressed in MWh/h, because of the way it is calculated, and I can 'convert' it to MW by calling power.to('MW'). Is it possible to automatically do this every time a quantity in this dimension is calculated?

Note that doing a blanket .to_base_units() on all quantities reverts it back to MWh/h.

Remarks:

  • I don't want to use [power] as a base dimension. It moves the problem to the price anyway.
  • I'm aware of prefixes; I just left them out here to keep the example as short as possible.
ElRudi
  • 2,122
  • 2
  • 18
  • 33

1 Answers1

0

To my knowledge Pint doesn't currently support setting desired conversion preferences. There has been some chatter about this on the project repo and this issue links to a string of related issues.

What you want can be achieved with some custom code with the caveat that the output needs to be wrapped in a function.

# units-2.txt

# Base dimensions and their units
hour = [time] = h = hr
megawatt = [power] = MW 
euro = [currency] = Eur = €

# Derived dimensions and their units
[energy] = [power] * [time]
megawatthour = megawatt * hour = MWh
[price] = [currency] / [energy]
euro_per_MWh = euro / megawatthour  = Eur/MWh
import pint

preference = ["Eur/MWh", 'MW']

def beautify(x, preference=preference):
    for preferred_unit in preference:
        try:
            return x.to(preferred_unit)
        except pint.DimensionalityError:
            pass
    # if no preferred unit fits, leave it as it is
    return x

def test_defs(file):
    print(file)
    ureg = pint.UnitRegistry(file,)
    ureg.default_format = ".0f~P"
    energy = 100 * ureg.MWh
    time = 4 * ureg.h
    revenue = 4000 * ureg.euro
    price = revenue / energy
    power = energy / time
    print(time.to_base_units())
    print(f"beautified time: {beautify(time)}")  # Units with no preference remain the same.
    print(power.to_base_units())
    print(f"beautified power: {beautify(power)}")  # Units with preference can be changed.
    print(price.to_base_units())
    print(f"beautified price: {beautify(price)}")  # Units with preference can be changed.

test_defs("units.txt")
test_defs("units-2.txt")

This will print

units.txt
4 h
beautified time: 4 h
25 MWh/h
beautified power: 25 MW
40 Eur/MWh
beautified price: 40 Eur/MWh
units-2.txt
4 h
beautified time: 4 h
25 MW
beautified power: 25 MW
40 Eur/MW/h
beautified price: 40 Eur/MWh
fabianegli
  • 2,056
  • 1
  • 18
  • 35
  • Thanks for that answer fabian, that's the route I'm currently using, but that's really quite cumbersome, and it does not work when the value isn't explicitly printed like this, as in e.g. `matplotlib` or `pandas.DataFrame`.    The `UnitRegistry` initialisation function already has a `preprocessors` parameter; adding a `postprocessors` parameter would be a good general way of addressing the issue, AFAICS. – ElRudi Nov 06 '21 at 18:09
  • I realize that what you would need is something in the spirit of a `.to_defined_units()` function that does take all defined units into account analog to how `.to_base_units()` operates. I made some experiments and extended `.to_reduced_units()` to accept the priority list which in combination with setting `ureg.auto_reduce_dimensions = True` does the trick. It's a first shot, but it works in the case the unit can be reduced to a priority unit. See [here](https://github.com/fabianegli/pint/tree/reduce-to-prioritized-units). It still needs a way to set the priorities `ureg`-wide. – fabianegli Nov 06 '21 at 23:47
  • I added some example usage code to the [commit](https://github.com/fabianegli/pint/commit/d93c2e0fef148d2dbe2f84d987577583af5249a8). It would be great to get your take on that. – fabianegli Nov 07 '21 at 00:15
  • And here it is with registry wide defaults: https://github.com/fabianegli/pint/commit/9d7f5475aa0355c59539ae581db5eee63fc50200 – fabianegli Nov 07 '21 at 00:37
  • Hey, that's great, many thanks! I did have some comments/suggestions, which I've added to the commit. It's gotten very long, sorry about that. I hope they are useful. – ElRudi Nov 07 '21 at 05:39