From 2ca75905d18f745cfda6d0e116e8a38b9ecca986 Mon Sep 17 00:00:00 2001 From: Femke Janssen Date: Fri, 5 Jun 2026 15:03:54 +0200 Subject: [PATCH 1/4] fix to ensure that the electricity price profile is not used for heat assets which have a connection to the electricity grid modelled --- src/mesido/esdl/esdl_heat_model.py | 4 ++-- src/mesido/financial_mixin.py | 7 ++++--- .../component_library/milp/heat/air_water_heat_pump.py | 9 +++++++++ .../milp/multicommodity/airwater_heat_pump_elec.py | 5 ----- tests/utils_tests.py | 5 ++--- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/mesido/esdl/esdl_heat_model.py b/src/mesido/esdl/esdl_heat_model.py index b16c2bf49..33d02e760 100644 --- a/src/mesido/esdl/esdl_heat_model.py +++ b/src/mesido/esdl/esdl_heat_model.py @@ -1384,11 +1384,11 @@ def convert_heat_pump(self, asset: Asset) -> Tuple[ # TODO: the power filled in at the heatpmp should always be the electric power, thus, # the max heat supply should be power*cop _, modifiers = self.convert_heat_source(asset) + modifiers.update(elec_power_nominal=modifiers["Heat_source"]["max"]) return AirWaterHeatPump, modifiers # In this case we only have the secondary side ports, here we assume a air-water HP elec if len(asset.in_ports) == 2 and len(asset.out_ports) == 1: - _, modifiers = self.convert_air_water_heat_pump_elec(asset) - return AirWaterHeatPumpElec, modifiers + return self.convert_air_water_heat_pump_elec(asset) if not asset.attributes["COP"]: raise _ESDLInputException( diff --git a/src/mesido/financial_mixin.py b/src/mesido/financial_mixin.py index 83480406c..14f1cfdb2 100644 --- a/src/mesido/financial_mixin.py +++ b/src/mesido/financial_mixin.py @@ -1055,10 +1055,11 @@ def __variable_operational_cost_constraints(self, ensemble_member): if (len(self.get_electricity_carriers().keys()) > 0) and s in [ *self.energy_system_components.get("heat_source_elec", []), - *self.energy_system_components.get("elec_heat_source_elec", []), - *self.energy_system_components.get("air_water_heat_pump_elec", []), - *self.energy_system_components.get("heat_pump_elec", []), + *self.energy_system_components.get("air_water_heat_pump", []), ]: + # Electricity priceprofile calculations on heat produced should only be performed + # if there is no electricity port at the asset, e.g. the asset is not connected + # to an electricity network in the model. sum_ += ( ca.sum1(price_profile[1:] * nominator_vector[1:] * timesteps_hr) / denominator ) diff --git a/src/mesido/pycml/component_library/milp/heat/air_water_heat_pump.py b/src/mesido/pycml/component_library/milp/heat/air_water_heat_pump.py index 88ee3e0d6..92f0f0e97 100644 --- a/src/mesido/pycml/component_library/milp/heat/air_water_heat_pump.py +++ b/src/mesido/pycml/component_library/milp/heat/air_water_heat_pump.py @@ -1,3 +1,6 @@ +from numpy import nan + +from mesido.pycml import Variable from mesido.pycml.pycml_mixin import add_variables_documentation_automatically from .heat_source import HeatSource @@ -21,3 +24,9 @@ def __init__(self, name, **modifiers): super().__init__(name, **modifiers) self.cop = modifiers["cop"] self.component_subtype = "air_water_heat_pump" + + self.elec_power_nominal = nan + + self.add_variable(Variable, "Power_elec", min=0.0, nominal=self.elec_power_nominal) + + self.add_equation(((self.Power_elec * self.cop - self.Heat_source) / self.Heat_nominal)) diff --git a/src/mesido/pycml/component_library/milp/multicommodity/airwater_heat_pump_elec.py b/src/mesido/pycml/component_library/milp/multicommodity/airwater_heat_pump_elec.py index bcf352b91..eee10b0df 100644 --- a/src/mesido/pycml/component_library/milp/multicommodity/airwater_heat_pump_elec.py +++ b/src/mesido/pycml/component_library/milp/multicommodity/airwater_heat_pump_elec.py @@ -1,4 +1,3 @@ -from mesido.pycml import Variable from mesido.pycml.component_library.milp.electricity.electricity_base import ElectricityPort from mesido.pycml.component_library.milp.heat.air_water_heat_pump import AirWaterHeatPump from mesido.pycml.pycml_mixin import add_variables_documentation_automatically @@ -37,13 +36,9 @@ def __init__(self, name, **modifiers): self.id_mapping_carrier = -1 self.min_voltage = nan - self.elec_power_nominal = nan # Assumption: heat in/out and added is nonnegative # Heat in the return (i.e. cold) line is zero self.add_variable(ElectricityPort, "ElectricityIn") - self.add_variable(Variable, "Power_elec", min=0.0, nominal=self.elec_power_nominal) self.add_equation(((self.ElectricityIn.Power - self.Power_elec) / self.elec_power_nominal)) - - self.add_equation(((self.Power_elec * self.cop - self.Heat_source) / self.Heat_nominal)) diff --git a/tests/utils_tests.py b/tests/utils_tests.py index 136f3b729..e9eb501b1 100644 --- a/tests/utils_tests.py +++ b/tests/utils_tests.py @@ -961,9 +961,8 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato if (len(solution.get_electricity_carriers().keys()) > 0) and asset in [ *solution.energy_system_components.get("heat_source_elec", []), - *solution.energy_system_components.get("elec_heat_source_elec", []), - *solution.energy_system_components.get("air_water_heat_pump_elec", []), - *solution.energy_system_components.get("heat_pump_elec", []), + *solution.energy_system_components.get("air_water_heat_pump", []), + *solution.energy_system_components.get("heat_pump", []), ]: variable_operational_cost += sum( price_profile.values[1:] * nominator_vector[1:] * timesteps_hr / denominator From 99f8dd02d27af412fc0ebd9c8aa4b95df32a37a7 Mon Sep 17 00:00:00 2001 From: KobusVanRooyen Date: Tue, 9 Jun 2026 16:16:00 +0200 Subject: [PATCH 2/4] code comment added in example case --- examples/heating_and_cooling/src/run_case.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/heating_and_cooling/src/run_case.py b/examples/heating_and_cooling/src/run_case.py index 30eb042dd..8c3ced4c1 100644 --- a/examples/heating_and_cooling/src/run_case.py +++ b/examples/heating_and_cooling/src/run_case.py @@ -87,6 +87,11 @@ def heating_cooling_case(self): np.testing.assert_array_less(1e3, results[f"{a_1_id}__fixed_operational_cost"]) np.testing.assert_array_less(1e3, results[f"{hp_1_id}__investment_cost"]) np.testing.assert_array_less(1e3, results[f"{hp_1_id}__installation_cost"]) + # TODO: The heat pump __variable_operational_cost check below will fail due the heat pump + # variable cost not including the electricty profile. The intend of this example case + # is to include a elect cost profile which still has to be + # accounted for via the "Import" asset. Once this asset is catered for in MESIDO it has to + # replace the elect producer in this example. np.testing.assert_array_less(1e3, results[f"{hp_1_id}__variable_operational_cost"]) np.testing.assert_array_less(1e3, results[f"{hp_1_id}__fixed_operational_cost"]) np.testing.assert_array_less(1e3, results[f"{ac_1_id}__investment_cost"]) From ce5bf64e70159626c6ca01e61167da56234375e6 Mon Sep 17 00:00:00 2001 From: Femke Janssen Date: Tue, 9 Jun 2026 17:48:25 +0200 Subject: [PATCH 3/4] Changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c693e5c2e..26af174ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - xxx ## Fixed -- xxx +- Removing the use of the electricity price profile for heat assets that have an ElectricityPort connecting to an electricity network. # [0.1.19] - 2026-06-08 From a0b34fcd9118f57c3440de22f7223c2f415e11d7 Mon Sep 17 00:00:00 2001 From: Femke Janssen Date: Tue, 9 Jun 2026 17:52:12 +0200 Subject: [PATCH 4/4] style --- .../pycml/component_library/milp/heat/air_water_heat_pump.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesido/pycml/component_library/milp/heat/air_water_heat_pump.py b/src/mesido/pycml/component_library/milp/heat/air_water_heat_pump.py index 92f0f0e97..0d75ca4db 100644 --- a/src/mesido/pycml/component_library/milp/heat/air_water_heat_pump.py +++ b/src/mesido/pycml/component_library/milp/heat/air_water_heat_pump.py @@ -1,8 +1,8 @@ -from numpy import nan - from mesido.pycml import Variable from mesido.pycml.pycml_mixin import add_variables_documentation_automatically +from numpy import nan + from .heat_source import HeatSource