Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade pint==0.19.1 #415

Merged
merged 8 commits into from Apr 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions documentation/changelog.rst
Expand Up @@ -15,6 +15,7 @@ Bugfixes

Infrastructure / Support
----------------------
* Unit conversion prefers shorter units in general [see `PR #415 <http://www.github.com/FlexMeasures/flexmeasures/pull/415>`_]


v0.9.2 | April XX, 2022
Expand Down
4 changes: 3 additions & 1 deletion flexmeasures/utils/tests/test_unit_utils.py
Expand Up @@ -70,7 +70,7 @@ def test_convert_unit(
("m³", None, "m³/h"),
("kWh", None, "kW"),
("km", "h", "km/h"),
("m", "s", "km/h"),
("m", "s", "m/s"),
],
)
def test_determine_flow_unit(
Expand All @@ -88,6 +88,8 @@ def test_determine_flow_unit(
"unit, time_unit, expected_unit",
[
("m³/h", None, "m³"),
("km³/h", None, "km³"),
# ("hm³/h", None, "hm³"), # todo: uncomment after switching to decimal unit registry
("kW", None, "kWh"),
("m/s", "s", "m"),
("m/s", "h", "km"),
Expand Down
54 changes: 27 additions & 27 deletions flexmeasures/utils/unit_utils.py
@@ -1,44 +1,36 @@
"""Utility module for unit conversion

FlexMeasures stores units as strings in short scientific notation (such as 'kWh' to denote kilowatt-hour).
We use the pint library to convert data between compatible units (such as 'm/s' to 'km/h').
Three-letter currency codes (such as 'KRW' to denote South Korean Won) are valid units.
Note that converting between currencies requires setting up a sensor that registers conversion rates over time.
The preferred compact form for combinations of units can be derived automatically (such as 'kW*EUR/MWh' to 'EUR/h').
Time series with fixed resolution can be converted from units of flow to units of stock (such as 'kW' to 'kWh'), and vice versa.
Percentages can be converted to units of some physical capacity if a capacity is known (such as '%' to 'kWh').
"""

from datetime import timedelta
from typing import List, Optional, Union

from moneyed import list_all_currencies
import importlib.resources as pkg_resources
import numpy as np
import pandas as pd
import pint
import timely_beliefs as tb

# Edit constants template to stop using h to represent planck_constant
constants_template = (
pkg_resources.read_text(pint, "constants_en.txt")
.replace("= h ", " ")
.replace(" h ", " planck_constant ")
)

# Edit units template to use h to represent hour instead of planck_constant
units_template = (
pkg_resources.read_text(pint, "default_en.txt")
.replace("@import constants_en.txt", "")
.replace(" h ", " planck_constant ")
.replace("hour = 60 * minute = hr", "hour = 60 * minute = h = hr")
)

# Create custom template
custom_template = [f"{c} = [currency_{c}]" for c in list_all_currencies()]

# Join templates as iterable object
full_template = (
constants_template.split("\n") + units_template.split("\n") + custom_template
)

# Set up UnitRegistry with abbreviated scientific format
ur = pint.UnitRegistry(
full_template,
# non_int_type=decimal.Decimal, # todo: switch to decimal unit registry, after https://github.com/hgrecco/pint/issues/1505
preprocessors=[
lambda s: s.replace("%", " percent "),
lambda s: s.replace("‰", " permille "),
],
)
ur.load_definitions(custom_template)
ur.default_format = "~P" # short pretty
ur.define("percent = 1 / 100 = %")
ur.define("permille = 1 / 1000 = ‰")
Expand All @@ -57,6 +49,8 @@
"V",
"A",
"dimensionless",
] + [
str(c) for c in list_all_currencies()
] # todo: move to config setting, with these as a default (NB prefixes do not matter here, this is about SI base units, so km/h is equivalent to m/h)
PREFERRED_UNITS_DICT = dict(
[(ur.parse_expression(x).dimensionality, x) for x in PREFERRED_UNITS]
Expand All @@ -67,7 +61,16 @@ def to_preferred(x: pint.Quantity) -> pint.Quantity:
"""From https://github.com/hgrecco/pint/issues/676#issuecomment-689157693"""
dim = x.dimensionality
if dim in PREFERRED_UNITS_DICT:
return x.to(PREFERRED_UNITS_DICT[dim]).to_compact()

compact_unit = x.to(PREFERRED_UNITS_DICT[dim]).to_compact()

# todo: switch to decimal unit registry and then swap out the if statements below
# if len(f"{compact_unit.magnitude}" + "{:~P}".format(compact_unit.units)) < len(
# f"{x.magnitude}" + "{:~P}".format(x.units)
# ):
# return compact_unit
if len("{:~P}".format(compact_unit.units)) < len("{:~P}".format(x.units)):
return compact_unit
return x


Expand Down Expand Up @@ -126,11 +129,8 @@ def determine_stock_unit(flow_unit: str, time_unit: str = "h"):
>>> determine_stock_unit("m³/h") # m³
>>> determine_stock_unit("kW") # kWh
"""
stock = ur.Quantity(flow_unit) * ur.Quantity(time_unit)
return min(
["{:~P}".format(stock.units), "{:~P}".format(to_preferred(stock).units)],
key=len,
)
stock = to_preferred(ur.Quantity(flow_unit) * ur.Quantity(time_unit))
return "{:~P}".format(stock.units)


def units_are_convertible(
Expand Down
2 changes: 1 addition & 1 deletion requirements/app.in
Expand Up @@ -6,7 +6,7 @@ pscript
pandas
# pandas-bokeh 0.5 requires bokeh>=2.0, but bokeh still doesn't support sharing a legend across plots
pandas-bokeh==0.4.3
pint
pint>=0.19.1
py-moneyed
iso8601
xlrd
Expand Down
2 changes: 1 addition & 1 deletion requirements/app.txt
Expand Up @@ -241,7 +241,7 @@ pillow==9.0.1
# via
# bokeh
# matplotlib
pint==0.18
pint==0.19.1
# via -r requirements/app.in
ply==3.11
# via pyomo
Expand Down