diff --git a/ResSimpy/Units/Units.py b/ResSimpy/Units/Units.py index ac19eb8be..7e8eb328d 100644 --- a/ResSimpy/Units/Units.py +++ b/ResSimpy/Units/Units.py @@ -72,6 +72,14 @@ def unit_system_enum_to_variable(self, unit_system: UnitSystem) -> str: case _: raise ValueError(f'Unit system {unit_system} not recognised.') + def english_to_metbar(self, value: float) -> float: + """Converts the value from English to metbar.""" + return value + + def metbar_to_english(self, value: float) -> float: + """Converts the value from metbar to English.""" + return value + class Area(UnitDimension): """Units for area.""" @@ -340,6 +348,14 @@ class SurfaceRatesLiquid(UnitDimension): lab = 'stcc/hour' metric_atm = 'stm3/day' + def english_to_metbar(self, value: float) -> float: + """Converts the value from English to metbar.""" + return value / 6.28981077 + + def metbar_to_english(self, value: float) -> float: + """Converts the value from metbar to English.""" + return value * 6.28981077 + class SurfaceRatesGas(UnitDimension): """Units for surface rates gas.""" @@ -480,6 +496,14 @@ class Diameter(UnitDimension): lab = 'cm' metric_atm = 'cm' + def english_to_metbar(self, value: float) -> float: + """Converts the value from English to metbar.""" + return value * 2.54 + + def metbar_to_english(self, value: float) -> float: + """Converts the value from metbar to English.""" + return value / 2.54 + class HeatTransfer(UnitDimension): """Heat transfer units.""" diff --git a/ResSimpy/Units/convert_units.py b/ResSimpy/Units/convert_units.py new file mode 100644 index 000000000..8e9c5f47f --- /dev/null +++ b/ResSimpy/Units/convert_units.py @@ -0,0 +1,65 @@ +from typing import Callable, Optional, Protocol, TypeVar + +from ResSimpy.Enums.UnitsEnum import UnitSystem +from ResSimpy.Units.AttributeMappings.BaseUnitMapping import BaseUnitMapping +from ResSimpy.Units.Units import UnitDimension + + +# Set up typing and generics for the convert_units function +class UnitConvertableObject(Protocol): + """Protocol for objects that can be converted between unit systems. + Implements _unit_system and units properties. + """ + _unit_system: UnitSystem + + @property + def units(self) -> BaseUnitMapping: ... + + @property + def unit_system(self) -> UnitSystem: ... + + +T = TypeVar('T', bound=UnitConvertableObject) + + +def get_map(from_unit: UnitSystem, to_unit: UnitSystem, unit_dim: UnitDimension) -> Optional[Callable]: + mapping = {'ENGLISH_METBAR': 'english_to_metbar', + 'METBAR_ENGLISH': 'metbar_to_english', + } + + function_name = mapping[from_unit.value + '_' + to_unit.value] + + function_call = getattr(unit_dim, function_name, None) + if function_call is None: + return None + return function_call + + +def convert_units(from_unit: UnitSystem, to_unit: UnitSystem, unit_dim: UnitDimension, value: float) -> float: + unit_mapping_function = get_map(from_unit, to_unit, unit_dim) + if unit_mapping_function is None: + return value + return unit_mapping_function(value) + + +def convert_object_units(obj: T, to_unit: UnitSystem) -> T: + """Inplace conversion of all attributes on the provided object to the new unit system. + + Args: + obj (UnitConvertableObject): The object to convert attributes of. + to_unit (UnitSystem): The unit system to convert to. + """ + for attr, value in obj.__dict__.items(): + # TODO: use the typing in the dict rather than checking if it is a float + if isinstance(value, float): + + attribute_unit_dims = obj.units.attribute_map.get(attr, None) + if attribute_unit_dims is None: + continue + converted_value = convert_units(obj.unit_system, + to_unit, + attribute_unit_dims, + value) + setattr(obj, attr, converted_value) + obj._unit_system = to_unit + return obj diff --git a/tests/Units/test_unit_conversion.py b/tests/Units/test_unit_conversion.py new file mode 100644 index 000000000..da44696a9 --- /dev/null +++ b/tests/Units/test_unit_conversion.py @@ -0,0 +1,22 @@ +from ResSimpy.Nexus.DataModels.Network.NexusWellConnection import NexusWellConnection +from ResSimpy.Nexus.NexusEnums.DateFormatEnum import DateFormat +from ResSimpy.Units.convert_units import convert_object_units +from ResSimpy.Enums.UnitsEnum import UnitSystem + + +def test_unit_object_conversion(mocker): + # Arrange + + test_obj = NexusWellConnection( + properties_dict=dict(diameter=100.1), + unit_system=UnitSystem.ENGLISH, date='01/01/2020', date_format=DateFormat.DD_MM_YYYY + ) + + to_unit = UnitSystem.METBAR + + # Act + result = convert_object_units(test_obj, to_unit) + + # Assert + assert result.diameter == 100.1 * 2.54 + assert result.units.unit_system == to_unit