diff --git a/flexmeasures/utils/tests/test_unit_utils.py b/flexmeasures/utils/tests/test_unit_utils.py index 140a1fec2..0061a1a57 100644 --- a/flexmeasures/utils/tests/test_unit_utils.py +++ b/flexmeasures/utils/tests/test_unit_utils.py @@ -25,7 +25,12 @@ ("m³", "m³/h", 4, None), ("MW", "kW", 1000, None), ("kWh", "kW", 4, None), + ("-W", "W", -1, None), + ("l/(100km)", "l/km", 0.01, None), ("°C", "K", None, [273.15, 283.15, 284.15]), + # no support for combining an offset unit with a scaling factor, but this is also overly specific + # ("-°C", "K", None, [273.15, 263.15, 262.15]), + # ("l/(10°C)", "l/(°C)", 0.1, None), ], ) def test_convert_unit( diff --git a/flexmeasures/utils/unit_utils.py b/flexmeasures/utils/unit_utils.py index 3aed31249..264415659 100644 --- a/flexmeasures/utils/unit_utils.py +++ b/flexmeasures/utils/unit_utils.py @@ -3,6 +3,7 @@ from moneyed import list_all_currencies import importlib.resources as pkg_resources +import numpy as np import pandas as pd import pint @@ -161,25 +162,31 @@ def convert_units( """Updates data values to reflect the given unit conversion.""" if from_unit != to_unit: + from_magnitudes = ( + data.to_numpy() if isinstance(data, pd.Series) else np.asarray(data) + ) try: - if isinstance(data, pd.Series): - data = pd.Series( - pint.Quantity(data.values, from_unit) - .to(pint.Quantity(to_unit)) - .magnitude, - index=data.index, - name=data.name, - ) + from_quantities = ur.Quantity(from_magnitudes, from_unit) + except ValueError as e: + # Catch units like "-W" and "100km" + if str(e) == "Unit expression cannot have a scaling factor.": + from_quantities = ur.Quantity(from_unit) * from_magnitudes else: - data = list( - pint.Quantity(data, from_unit).to(pint.Quantity(to_unit)).magnitude - ) + raise e # reraise + try: + to_magnitudes = from_quantities.to(ur.Quantity(to_unit)).magnitude except pint.errors.DimensionalityError: + # Catch multiplicative conversions that use the resolution, like "kWh/15min" to "kW" multiplier = determine_unit_conversion_multiplier( from_unit, to_unit, event_resolution ) - if isinstance(data, pd.Series): - data = multiplier * data - else: - data = [multiplier * value for value in data] + to_magnitudes = from_magnitudes * multiplier + if isinstance(data, pd.Series): + data = pd.Series( + to_magnitudes, + index=data.index, + name=data.name, + ) + else: + data = list(to_magnitudes) return data