From a15e381d4810e0598dac426e46b55a2e36f18338 Mon Sep 17 00:00:00 2001 From: Femke Janssen Date: Wed, 10 Jun 2026 15:12:14 +0200 Subject: [PATCH 1/6] create electricity import as a subclass of electricity producer and allow a priceprofile to be used on it --- src/mesido/esdl/esdl_heat_model.py | 5 +++- src/mesido/financial_mixin.py | 3 +++ .../milp/electricity/electricity_import.py | 24 +++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/mesido/pycml/component_library/milp/electricity/electricity_import.py diff --git a/src/mesido/esdl/esdl_heat_model.py b/src/mesido/esdl/esdl_heat_model.py index 33d02e760..4f1559c4e 100644 --- a/src/mesido/esdl/esdl_heat_model.py +++ b/src/mesido/esdl/esdl_heat_model.py @@ -66,6 +66,8 @@ from scipy.optimize import fsolve +from mesido.pycml.component_library.milp.electricity.electricity_import import ElectricityImport + logger = logging.getLogger("mesido") @@ -1903,7 +1905,8 @@ def convert_import(self, asset: Asset) -> Tuple[Any, MODIFIERS]: if isinstance(asset.out_ports[0].carrier, esdl.esdl.GasCommodity): return self.convert_gas_source(asset) elif isinstance(asset.out_ports[0].carrier, esdl.esdl.ElectricityCommodity): - return self.convert_electricity_source(asset) + _, modifiers = self.convert_electricity_source(asset) + return ElectricityImport, modifiers else: raise RuntimeError( f"Commodity of type {type(asset.out_ports[0].carrier)} for asset Import " diff --git a/src/mesido/financial_mixin.py b/src/mesido/financial_mixin.py index 14f1cfdb2..5774056f3 100644 --- a/src/mesido/financial_mixin.py +++ b/src/mesido/financial_mixin.py @@ -1166,6 +1166,9 @@ def __variable_operational_cost_constraints(self, ensemble_member): sum_ = ca.sum1( variable_operational_cost_coefficient * elec_produced_w[1:] * timesteps_hr ) # [euro/Wh] * [W] * [hr] + if es in self.energy_system_components.get("electricity_import", []): + sum_ += ca.sum1(price_profile[1:] * elec_produced_w[1:] * timesteps_hr) + constraints.append(((variable_operational_cost - sum_) / nominal, 0.0, 0.0)) # for a in self.heat_network_components.get("ates", []): diff --git a/src/mesido/pycml/component_library/milp/electricity/electricity_import.py b/src/mesido/pycml/component_library/milp/electricity/electricity_import.py new file mode 100644 index 000000000..4dae733ed --- /dev/null +++ b/src/mesido/pycml/component_library/milp/electricity/electricity_import.py @@ -0,0 +1,24 @@ +from mesido.pycml.pycml_mixin import add_variables_documentation_automatically + +from .. import ElectricitySource + + +@add_variables_documentation_automatically +class ElectricityImport(ElectricitySource): + """ + The electricity import is an electricity source component used to import electrical power and + provide that to the energy system, which can handle production profiles and electricity price + profiles. + + Variables created: + {add_variable_names_for_documentation_here} + + Parameters: + name : The name of the asset. \n + modifiers : Dictionary with asset information. + """ + + def __init__(self, name, **modifiers): + super().__init__(name, **modifiers) + + self.component_subtype = "electricity_import" \ No newline at end of file From dd37cf925f9f0aba4d86ff1c788d96336de9446f Mon Sep 17 00:00:00 2001 From: tolga-akan Date: Thu, 9 Jul 2026 14:52:21 +0200 Subject: [PATCH 2/6] wip --- src/mesido/esdl/esdl_heat_model.py | 3 +- src/mesido/financial_mixin.py | 3 ++ .../milp/electricity/electricity_import.py | 2 +- .../input/timeseries_with_e_price.csv | 4 +++ .../model/electricity_import_and_e_price.esdl | 33 +++++++++++++++++++ tests/test_electric_source_sink.py | 33 +++++++++++++++++++ 6 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 tests/models/unit_cases_electricity/source_sink_cable/input/timeseries_with_e_price.csv create mode 100644 tests/models/unit_cases_electricity/source_sink_cable/model/electricity_import_and_e_price.esdl diff --git a/src/mesido/esdl/esdl_heat_model.py b/src/mesido/esdl/esdl_heat_model.py index 4f1559c4e..d445dd2e8 100644 --- a/src/mesido/esdl/esdl_heat_model.py +++ b/src/mesido/esdl/esdl_heat_model.py @@ -66,7 +66,8 @@ from scipy.optimize import fsolve -from mesido.pycml.component_library.milp.electricity.electricity_import import ElectricityImport +from mesido.pycml.component_library.milp.electricity.electricity_import import ( + ElectricityImport) logger = logging.getLogger("mesido") diff --git a/src/mesido/financial_mixin.py b/src/mesido/financial_mixin.py index 5774056f3..14f86c417 100644 --- a/src/mesido/financial_mixin.py +++ b/src/mesido/financial_mixin.py @@ -1163,6 +1163,9 @@ def __variable_operational_cost_constraints(self, ensemble_member): variable_operational_cost_coefficient = parameters[ # euro / Wh f"{es}.variable_operational_cost_coefficient" ] + + price_profile = self.__get_electricity_price_profile_or_zero() + sum_ = ca.sum1( variable_operational_cost_coefficient * elec_produced_w[1:] * timesteps_hr ) # [euro/Wh] * [W] * [hr] diff --git a/src/mesido/pycml/component_library/milp/electricity/electricity_import.py b/src/mesido/pycml/component_library/milp/electricity/electricity_import.py index 4dae733ed..d3b378eab 100644 --- a/src/mesido/pycml/component_library/milp/electricity/electricity_import.py +++ b/src/mesido/pycml/component_library/milp/electricity/electricity_import.py @@ -21,4 +21,4 @@ class ElectricityImport(ElectricitySource): def __init__(self, name, **modifiers): super().__init__(name, **modifiers) - self.component_subtype = "electricity_import" \ No newline at end of file + self.component_subtype = "electricity_import" diff --git a/tests/models/unit_cases_electricity/source_sink_cable/input/timeseries_with_e_price.csv b/tests/models/unit_cases_electricity/source_sink_cable/input/timeseries_with_e_price.csv new file mode 100644 index 000000000..a1f08c1a5 --- /dev/null +++ b/tests/models/unit_cases_electricity/source_sink_cable/input/timeseries_with_e_price.csv @@ -0,0 +1,4 @@ +DateTime,ElectricityDemand_2af6,Electricity +1-1-2019 00:00,500,1.00E-04 +1-1-2019 01:00,1000,2.00E-04 +1-1-2019 02:00,1500,1.00E-04 diff --git a/tests/models/unit_cases_electricity/source_sink_cable/model/electricity_import_and_e_price.esdl b/tests/models/unit_cases_electricity/source_sink_cable/model/electricity_import_and_e_price.esdl new file mode 100644 index 000000000..5b5835746 --- /dev/null +++ b/tests/models/unit_cases_electricity/source_sink_cable/model/electricity_import_and_e_price.esdl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test_electric_source_sink.py b/tests/test_electric_source_sink.py index 618534c8f..ba051ff8d 100644 --- a/tests/test_electric_source_sink.py +++ b/tests/test_electric_source_sink.py @@ -76,6 +76,39 @@ def test_source_sink_pv_csv_profile(self): # Check electricity producers max sizes are equal np.testing.assert_allclose(results[f"{pv_id}__max_size"], results[f"{e_prod_id}__max_size"]) + def test_electricity_import_sink(self): + """ + Tests for an electricity network that consist out of an electricity import, a cable and a sink. + + Checks: + - Check that... + + """ + + import models.unit_cases_electricity.source_sink_cable.src.example as example + from models.unit_cases_electricity.source_sink_cable.src.example import ElectricityProblem + + base_folder = Path(example.__file__).resolve().parent.parent + tol = 1e-10 + + solution = run_esdl_mesido_optimization( + ElectricityProblem, + base_folder=base_folder, + esdl_file_name="electricity_import_and_e_price.esdl", + esdl_parser=ESDLFileParser, + profile_reader=ProfileReaderFromFile, + input_timeseries_file="timeseries_with_e_price.csv", + ) + results = solution.extract_results() + parameters = solution.parameters(0) + name_to_id_map = solution.esdl_asset_name_to_id_map + + demand_id = name_to_id_map["ElectricityDemand_2af6"] + cable_id = name_to_id_map["ElectricityCable_238f"] + + # Test energy conservation + electric_power_conservation_test(solution, results) + def test_source_sink(self): """ Tests for an electricity network that consist out of a source, a cable and a sink. From ae562a3591705a203e4f2cc1f8bf97eff2ac9b89 Mon Sep 17 00:00:00 2001 From: tolga-akan Date: Thu, 9 Jul 2026 17:54:03 +0200 Subject: [PATCH 3/6] wip --- src/mesido/esdl/asset_to_component_base.py | 7 +++++++ src/mesido/esdl/esdl_heat_model.py | 4 +--- tests/test_electric_source_sink.py | 15 +++++++-------- tests/utils_tests.py | 15 ++++----------- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/mesido/esdl/asset_to_component_base.py b/src/mesido/esdl/asset_to_component_base.py index ced0e9aec..2ac845b76 100644 --- a/src/mesido/esdl/asset_to_component_base.py +++ b/src/mesido/esdl/asset_to_component_base.py @@ -295,6 +295,11 @@ class _AssetToComponentBase: "fixedMaintenanceCosts": "optional", "fixedOperationalCosts": "optional", }, + "electricity_import": { + "investmentCosts": "optional", + "installationCosts": "optional", + "variableOperationalCosts": "optional", + }, "heat_source": { # Includes GeothermalSource, ResidualHeatSource, HeatProducer, # GasHeater, ElectricBoiler "investmentCosts": "required", @@ -349,6 +354,8 @@ class _AssetToComponentBase: "CoolingDemand": "heat_demand", "Electrolyzer": "electrolyzer", "ElectricBoiler": "heat_source", + "Import": "electricity_import", + "ElectricityProducer": "electricity_source", "GasDemand": "gas_demand", "GasHeater": "heat_source", "GasStorage": "gas_tank_storage", diff --git a/src/mesido/esdl/esdl_heat_model.py b/src/mesido/esdl/esdl_heat_model.py index d445dd2e8..ef9f2a8a7 100644 --- a/src/mesido/esdl/esdl_heat_model.py +++ b/src/mesido/esdl/esdl_heat_model.py @@ -60,15 +60,13 @@ Transformer, WindPark, ) +from mesido.pycml.component_library.milp.electricity.electricity_import import ElectricityImport # Importing workflow utilities at module import time can create circular # imports when workflows import ESDL mixins. Import locally where needed. from scipy.optimize import fsolve -from mesido.pycml.component_library.milp.electricity.electricity_import import ( - ElectricityImport) - logger = logging.getLogger("mesido") diff --git a/tests/test_electric_source_sink.py b/tests/test_electric_source_sink.py index ba051ff8d..149db4c58 100644 --- a/tests/test_electric_source_sink.py +++ b/tests/test_electric_source_sink.py @@ -7,7 +7,11 @@ import numpy as np -from utils_tests import demand_matching_test, electric_power_conservation_test +from utils_tests import ( + cost_calculation_test, + demand_matching_test, + electric_power_conservation_test, +) # TODO: still have to make test where elecitricity direction is switched: # e.g. 2 nodes, with at each node a producer and consumer, first one node medium demand, second @@ -78,7 +82,7 @@ def test_source_sink_pv_csv_profile(self): def test_electricity_import_sink(self): """ - Tests for an electricity network that consist out of an electricity import, a cable and a sink. + Tests for an electricity network that consist out of electricity import, cable and sink. Checks: - Check that... @@ -89,7 +93,6 @@ def test_electricity_import_sink(self): from models.unit_cases_electricity.source_sink_cable.src.example import ElectricityProblem base_folder = Path(example.__file__).resolve().parent.parent - tol = 1e-10 solution = run_esdl_mesido_optimization( ElectricityProblem, @@ -100,14 +103,10 @@ def test_electricity_import_sink(self): input_timeseries_file="timeseries_with_e_price.csv", ) results = solution.extract_results() - parameters = solution.parameters(0) - name_to_id_map = solution.esdl_asset_name_to_id_map - - demand_id = name_to_id_map["ElectricityDemand_2af6"] - cable_id = name_to_id_map["ElectricityCable_238f"] # Test energy conservation electric_power_conservation_test(solution, results) + cost_calculation_test(solution, results) def test_source_sink(self): """ diff --git a/tests/utils_tests.py b/tests/utils_tests.py index e9eb501b1..c6c855e55 100644 --- a/tests/utils_tests.py +++ b/tests/utils_tests.py @@ -12,8 +12,6 @@ import numpy as np -from rtctools.optimization.timeseries import Timeseries - def __get_out_port_temp_profile(solution, asset_name, asset_type): """ @@ -749,6 +747,7 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato *solution.energy_system_components.get("airco", []), *solution.energy_system_components.get("heat_demand", []), *solution.energy_system_components.get("cold_demand", []), + *solution.energy_system_components.get("electricity_source", []), *transport_assets, ] @@ -761,15 +760,7 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato edr_pipes = json.load(open(Path(__file__).parent.parent / "src/mesido/esdl/_edr_pipes.json")) - if len(solution.get_electricity_carriers().keys()) == 1: - try: - price_profile = solution.get_timeseries( - f"{list(solution.get_electricity_carriers().values())[0]['name']}.price_profile" - ) - except KeyError: - price_profile = Timeseries(solution.times(), np.zeros(len(solution.times()))) - else: - price_profile = Timeseries(solution.times(), np.zeros(len(solution.times()))) + price_profile = solution._FinancialMixin__get_electricity_price_profile_or_zero() total_investment_cost = 0.0 total_installation_cost = 0.0 @@ -947,6 +938,8 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato nominator_vector = results[f"{asset}.Heat_demand"] elif asset in solution.energy_system_components.get("cold_demand", []): nominator_vector = results[f"{asset}.Cold_demand"] + elif asset in solution.energy_system_components.get("electricity_source", []): + nominator_vector = results[f"{asset}.Electricity_source"] # [W] else: raise AssertionError( f"Asset '{esdl_asset.name}' is not handled in the variable operational" From 517c34da06399e18d2ef652e3ea6e039234f91e4 Mon Sep 17 00:00:00 2001 From: tolga-akan Date: Fri, 10 Jul 2026 09:44:25 +0200 Subject: [PATCH 4/6] wip --- .../model/electricity_import_and_e_price.esdl | 2 +- .../source_sink_cable/src/example.py | 9 ++++++++ tests/test_electric_source_sink.py | 22 ++++++++++++++----- tests/utils_tests.py | 3 ++- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/tests/models/unit_cases_electricity/source_sink_cable/model/electricity_import_and_e_price.esdl b/tests/models/unit_cases_electricity/source_sink_cable/model/electricity_import_and_e_price.esdl index 5b5835746..d40a30618 100644 --- a/tests/models/unit_cases_electricity/source_sink_cable/model/electricity_import_and_e_price.esdl +++ b/tests/models/unit_cases_electricity/source_sink_cable/model/electricity_import_and_e_price.esdl @@ -1,5 +1,5 @@ - + diff --git a/tests/models/unit_cases_electricity/source_sink_cable/src/example.py b/tests/models/unit_cases_electricity/source_sink_cable/src/example.py index 296e85bb9..a955c34be 100644 --- a/tests/models/unit_cases_electricity/source_sink_cable/src/example.py +++ b/tests/models/unit_cases_electricity/source_sink_cable/src/example.py @@ -1,3 +1,4 @@ +from mesido.esdl.esdl_additional_vars_mixin import ESDLAdditionalVarsMixin from mesido.esdl.esdl_mixin import ESDLMixin from mesido.esdl.esdl_parser import ESDLFileParser from mesido.esdl.profile_parser import ProfileReaderFromFile @@ -149,6 +150,14 @@ class ElectricityProblem( pass +class ElectricityProblemPriceProfile(ESDLAdditionalVarsMixin, ElectricityProblem): + """ + Problem to check the behaviour of a electricity import, cable, demand network. + ESDLAdditionalVarsMixin is needed to read e-price profile in input csv + """ + pass + + class ElectricityProblemMaxCurr( PhysicsMixin, LinearizedOrderGoalProgrammingMixin, diff --git a/tests/test_electric_source_sink.py b/tests/test_electric_source_sink.py index 149db4c58..1cd7b1e06 100644 --- a/tests/test_electric_source_sink.py +++ b/tests/test_electric_source_sink.py @@ -85,17 +85,20 @@ def test_electricity_import_sink(self): Tests for an electricity network that consist out of electricity import, cable and sink. Checks: - - Check that... - + - Check for energy conservation with consumed power, lost power and imported power. + - Check that variable operation cost of electricity import is calculated via electricity + price profile, variable operational cost coefficient and imported power. """ import models.unit_cases_electricity.source_sink_cable.src.example as example - from models.unit_cases_electricity.source_sink_cable.src.example import ElectricityProblem + from models.unit_cases_electricity.source_sink_cable.src.example import ( + ElectricityProblemPriceProfile, + ) base_folder = Path(example.__file__).resolve().parent.parent solution = run_esdl_mesido_optimization( - ElectricityProblem, + ElectricityProblemPriceProfile, base_folder=base_folder, esdl_file_name="electricity_import_and_e_price.esdl", esdl_parser=ESDLFileParser, @@ -103,9 +106,18 @@ def test_electricity_import_sink(self): input_timeseries_file="timeseries_with_e_price.csv", ) results = solution.extract_results() + name_to_id_map = solution.esdl_asset_name_to_id_map - # Test energy conservation + import_id = name_to_id_map["Import"] + + # Check energy conservation electric_power_conservation_test(solution, results) + + # Check variable operation cost calculation + np.testing.assert_array_less(0.2, results[f"{import_id}__variable_operational_cost"]) + np.testing.assert_array_less( + 0.00005, solution.get_timeseries("Electricity.price_profile").values + ) cost_calculation_test(solution, results) def test_source_sink(self): diff --git a/tests/utils_tests.py b/tests/utils_tests.py index c6c855e55..d1e72e2cc 100644 --- a/tests/utils_tests.py +++ b/tests/utils_tests.py @@ -956,9 +956,10 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato *solution.energy_system_components.get("heat_source_elec", []), *solution.energy_system_components.get("air_water_heat_pump", []), *solution.energy_system_components.get("heat_pump", []), + *solution.energy_system_components.get("electricity_import", []), ]: variable_operational_cost += sum( - price_profile.values[1:] * nominator_vector[1:] * timesteps_hr / denominator + price_profile[1:] * nominator_vector[1:] * timesteps_hr / denominator ) np.testing.assert_allclose( From fcdcee4b90a3d8d7d4c195c2c5401d6a686ab4ee Mon Sep 17 00:00:00 2001 From: tolga-akan Date: Fri, 10 Jul 2026 14:01:28 +0200 Subject: [PATCH 5/6] wip --- ...etwork with return network with costs.esdl | 12 ++-- examples/heating_and_cooling/src/run_case.py | 13 ++-- .../source_sink_cable/src/example.py | 1 + tests/utils_tests.py | 62 ++++++++++++------- 4 files changed, 53 insertions(+), 35 deletions(-) diff --git a/examples/heating_and_cooling/model/Heating and cooling and elec network with return network with costs.esdl b/examples/heating_and_cooling/model/Heating and cooling and elec network with return network with costs.esdl index ae90d942a..d141c7618 100644 --- a/examples/heating_and_cooling/model/Heating and cooling and elec network with return network with costs.esdl +++ b/examples/heating_and_cooling/model/Heating and cooling and elec network with return network with costs.esdl @@ -1,5 +1,5 @@ - + @@ -287,10 +287,6 @@ - - - - @@ -298,9 +294,13 @@ - + + + + + diff --git a/examples/heating_and_cooling/src/run_case.py b/examples/heating_and_cooling/src/run_case.py index 8c3ced4c1..6e60c38e4 100644 --- a/examples/heating_and_cooling/src/run_case.py +++ b/examples/heating_and_cooling/src/run_case.py @@ -32,7 +32,7 @@ def heating_cooling_case(self): cost_calculation_test, demand_matching_test, energy_conservation_test, - heat_to_discharge_test, + # heat_to_discharge_test, ) base_folder = Path(__file__).resolve().parent.parent @@ -52,7 +52,9 @@ def heating_cooling_case(self): demand_matching_test(solution, results) energy_conservation_test(solution, results) - heat_to_discharge_test(solution, results) + # TODO: Fix this test, it is not working properly yet. Because Theoretical HeatOut + # of "Pipe8_ret" and "Pipe9_ret" are smallar tha nrealized value in some instances + # heat_to_discharge_test(solution, results) name_to_id_map = solution.esdl_asset_name_to_id_map @@ -87,12 +89,7 @@ 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(2, 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"]) np.testing.assert_array_less(1e3, results[f"{ac_1_id}__installation_cost"]) diff --git a/tests/models/unit_cases_electricity/source_sink_cable/src/example.py b/tests/models/unit_cases_electricity/source_sink_cable/src/example.py index a955c34be..1d66e0295 100644 --- a/tests/models/unit_cases_electricity/source_sink_cable/src/example.py +++ b/tests/models/unit_cases_electricity/source_sink_cable/src/example.py @@ -155,6 +155,7 @@ class ElectricityProblemPriceProfile(ESDLAdditionalVarsMixin, ElectricityProblem Problem to check the behaviour of a electricity import, cable, demand network. ESDLAdditionalVarsMixin is needed to read e-price profile in input csv """ + pass diff --git a/tests/utils_tests.py b/tests/utils_tests.py index d1e72e2cc..4a65674ce 100644 --- a/tests/utils_tests.py +++ b/tests/utils_tests.py @@ -216,7 +216,7 @@ def heat_to_discharge_test(solution, results, atol=1e-2, rtol=1.0e-4): if temp_profile is not None: supply_t = temp_profile.values supply_temp_profiles.append(temp_profile.values) - print(d, max(abs(results[f"{d}.HeatOut.Heat"] - results[f"{d}.Q"] * rho * cp * supply_t))) + # print(d, max(abs(results[f"{d}.HeatOut.Heat"] - results[f"{d}.Q"] * rho * cp * supply_t))) np.testing.assert_allclose( results[f"{d}.HeatOut.Heat"], @@ -739,7 +739,6 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato assets = [ *solution.energy_system_components.get("heat_source", []), *solution.energy_system_components.get("ates", []), - *solution.energy_system_components.get("low_temperature_ates", []), *solution.energy_system_components.get("heat_pump", []), *solution.energy_system_components.get("pump", []), *solution.energy_system_components.get("heat_exchanger", []), @@ -758,6 +757,17 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato for name, edr_class_name in _AssetToComponentBase.STEEL_S1_PIPE_EDR_ASSETS.items() ] + assets_may_have_electricity_price_profile = [ + *solution.energy_system_components.get("heat_source_elec", []), + *solution.energy_system_components.get("air_water_heat_pump", []), + *[ + hp_asset + for hp_asset in solution.energy_system_components.get("heat_pump", []) + if hp_asset not in solution.energy_system_components.get("heat_pump_elec", []) + ], + *solution.energy_system_components.get("electricity_import", []), + ] + edr_pipes = json.load(open(Path(__file__).parent.parent / "src/mesido/esdl/_edr_pipes.json")) price_profile = solution._FinancialMixin__get_electricity_price_profile_or_zero() @@ -776,7 +786,10 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato ): pass elif costs_esdl_asset is None: - continue + if asset in assets_may_have_electricity_price_profile: + pass # These assets may have only e-rpice, but no attributes in esdl + elif asset not in assets_may_have_electricity_price_profile: + continue # Investment Cost investment_cost = 0.0 @@ -789,8 +802,10 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato edr_pipes, parameters[f"{asset}.diameter"] ) else: - investment_cost_info = _get_esdl_scaled_cost( - costs_esdl_asset.investmentCosts, esdl.UnitEnum.WATT + investment_cost_info = ( + _get_esdl_scaled_cost(costs_esdl_asset.investmentCosts, esdl.UnitEnum.WATT) + if costs_esdl_asset is not None + else 0.0 ) if asset in solution.energy_system_components.get("heat_buffer", []): np.testing.assert_allclose( @@ -838,8 +853,8 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato else: if results[f"{asset}__max_size"] > 1e-8: installation_cost = ( - costs_esdl_asset.installationCosts.value - if costs_esdl_asset.installationCosts is not None + _get_esdl_scaled_cost(costs_esdl_asset.installationCosts, esdl.UnitEnum.WATT) + if costs_esdl_asset is not None else 0.0 ) np.testing.assert_allclose(installation_cost, results[f"{asset}__installation_cost"]) @@ -850,11 +865,15 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato if asset in transport_assets: np.testing.assert_allclose(0.0, results[f"{asset}__fixed_operational_cost"]) else: - fix_op_costs_esdl = _get_esdl_scaled_cost( - costs_esdl_asset.fixedOperationalCosts, esdl.UnitEnum.WATT + fix_op_costs_esdl = ( + _get_esdl_scaled_cost(costs_esdl_asset.fixedOperationalCosts, esdl.UnitEnum.WATT) + if costs_esdl_asset is not None + else 0.0 ) - fix_maint_costs_esdl = _get_esdl_scaled_cost( - costs_esdl_asset.fixedMaintenanceCosts, esdl.UnitEnum.WATT + fix_maint_costs_esdl = ( + _get_esdl_scaled_cost(costs_esdl_asset.fixedMaintenanceCosts, esdl.UnitEnum.WATT) + if costs_esdl_asset is not None + else 0.0 ) np.testing.assert_allclose( parameters[f"{asset}.fixed_operational_cost_coefficient"], @@ -875,8 +894,12 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato if asset in transport_assets: np.testing.assert_allclose(0.0, results[f"{asset}__variable_operational_cost"]) else: - var_op_costs_esdl = _get_esdl_scaled_cost( - costs_esdl_asset.variableOperationalCosts, esdl.UnitEnum.WATTHOUR + var_op_costs_esdl = ( + _get_esdl_scaled_cost( + costs_esdl_asset.variableOperationalCosts, esdl.UnitEnum.WATTHOUR + ) + if costs_esdl_asset is not None + else 0.0 ) np.testing.assert_allclose( parameters[f"{asset}.variable_operational_cost_coefficient"], var_op_costs_esdl @@ -890,7 +913,7 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato if parameters[f"{asset}.include_head_loss_variables"]: pump_power = results[f"{asset}.Pump_power"] eff = parameters[f"{asset}.pump_efficiency"] - pump_cost = sum(price_profile.values[1:] * pump_power[1:] * timesteps_hr / eff) + pump_cost = sum(price_profile[1:] * pump_power[1:] * timesteps_hr / eff) else: pump_cost = 0.0 @@ -904,7 +927,7 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato if parameters[f"{asset}.include_head_loss_variables"]: pump_power = results[f"{asset}.Pump_power"] eff = parameters[f"{asset}.pump_efficiency"] - pump_cost = sum(price_profile.values[1:] * pump_power[1:] * timesteps_hr / eff) + pump_cost = sum(price_profile[1:] * pump_power[1:] * timesteps_hr / eff) else: pump_cost = 0.0 @@ -952,12 +975,9 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato variable_operational_cost += pump_cost - if (len(solution.get_electricity_carriers().keys()) > 0) and asset in [ - *solution.energy_system_components.get("heat_source_elec", []), - *solution.energy_system_components.get("air_water_heat_pump", []), - *solution.energy_system_components.get("heat_pump", []), - *solution.energy_system_components.get("electricity_import", []), - ]: + if ( + len(solution.get_electricity_carriers().keys()) > 0 + ) and asset in assets_may_have_electricity_price_profile: variable_operational_cost += sum( price_profile[1:] * nominator_vector[1:] * timesteps_hr / denominator ) From a2c1c40858b32c8c6088e2864794099d69da0fad Mon Sep 17 00:00:00 2001 From: tolga-akan Date: Mon, 13 Jul 2026 18:16:48 +0200 Subject: [PATCH 6/6] completed --- CHANGELOG.md | 4 ++-- ...ing and elec network with return network with costs.esdl | 2 +- examples/heating_and_cooling/src/run_case.py | 6 ++---- tests/utils_tests.py | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de26aeb83..cd9d77570 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ -# [Unreleased-main] - 2026-07-09 +# [Unreleased-main] - 2026-07-13 ## Added -- xxx +- Import (a subclass of electricity source) is supported in Mesido. ## Changed - Code generalization in asset sizing mixin diff --git a/examples/heating_and_cooling/model/Heating and cooling and elec network with return network with costs.esdl b/examples/heating_and_cooling/model/Heating and cooling and elec network with return network with costs.esdl index d141c7618..c10a26002 100644 --- a/examples/heating_and_cooling/model/Heating and cooling and elec network with return network with costs.esdl +++ b/examples/heating_and_cooling/model/Heating and cooling and elec network with return network with costs.esdl @@ -3,7 +3,7 @@ - + diff --git a/examples/heating_and_cooling/src/run_case.py b/examples/heating_and_cooling/src/run_case.py index 6e60c38e4..7261eb845 100644 --- a/examples/heating_and_cooling/src/run_case.py +++ b/examples/heating_and_cooling/src/run_case.py @@ -32,7 +32,7 @@ def heating_cooling_case(self): cost_calculation_test, demand_matching_test, energy_conservation_test, - # heat_to_discharge_test, + heat_to_discharge_test, ) base_folder = Path(__file__).resolve().parent.parent @@ -52,9 +52,7 @@ def heating_cooling_case(self): demand_matching_test(solution, results) energy_conservation_test(solution, results) - # TODO: Fix this test, it is not working properly yet. Because Theoretical HeatOut - # of "Pipe8_ret" and "Pipe9_ret" are smallar tha nrealized value in some instances - # heat_to_discharge_test(solution, results) + heat_to_discharge_test(solution, results) name_to_id_map = solution.esdl_asset_name_to_id_map diff --git a/tests/utils_tests.py b/tests/utils_tests.py index 4a65674ce..81fd1ad0f 100644 --- a/tests/utils_tests.py +++ b/tests/utils_tests.py @@ -787,7 +787,7 @@ def cost_calculation_test(solution, results, check_objective_function=False, ato pass elif costs_esdl_asset is None: if asset in assets_may_have_electricity_price_profile: - pass # These assets may have only e-rpice, but no attributes in esdl + pass # Assets include e-price in var-opex, but no esdl cost attributes are defined elif asset not in assets_may_have_electricity_price_profile: continue