diff --git a/src/omotes_simulator_core/adapter/transforms/controller_mapper.py b/src/omotes_simulator_core/adapter/transforms/controller_mapper.py
index 672e6550..9abd1ee7 100644
--- a/src/omotes_simulator_core/adapter/transforms/controller_mapper.py
+++ b/src/omotes_simulator_core/adapter/transforms/controller_mapper.py
@@ -121,8 +121,17 @@ def to_entity(
ControllerProducerMapper().to_entity(esdl_asset=esdl_asset)
for esdl_asset in esdl_object.get_all_assets_of_type(OmotesAssetLabels.HEAT_PUMP)
if esdl_asset.get_number_of_ports() == 2
+ ] + [
+ ControllerProducerMapper().to_entity(esdl_asset=esdl_asset)
+ for esdl_asset in esdl_object.get_all_assets_of_type(OmotesAssetLabels.GAS_HEATER)
+ ] + [
+ ControllerProducerMapper().to_entity(esdl_asset=esdl_asset)
+ for esdl_asset in esdl_object.get_all_assets_of_type(OmotesAssetLabels.ELECTRIC_BOILER)
]
+ # ELECTRIC_BOILER = "electric_boiler"
+ # GAS_HEATER = "gas_heater"
+
storages = self.convert_heat_storages_and_ates(esdl_object)
# if there are no heat transfer assets, all assets can be stored into one network.
diff --git a/src/omotes_simulator_core/adapter/transforms/esdl_asset_mapper.py b/src/omotes_simulator_core/adapter/transforms/esdl_asset_mapper.py
index bda33c94..d0ba11b2 100644
--- a/src/omotes_simulator_core/adapter/transforms/esdl_asset_mapper.py
+++ b/src/omotes_simulator_core/adapter/transforms/esdl_asset_mapper.py
@@ -47,6 +47,7 @@
esdl.ResidualHeatSource: EsdlAssetProducerMapper,
esdl.SolarCollector: EsdlAssetProducerMapper,
esdl.GasHeater: EsdlAssetProducerMapper,
+ esdl.ElectricBoiler: EsdlAssetProducerMapper,
esdl.Consumer: EsdlAssetConsumerMapper,
esdl.GenericConsumer: EsdlAssetConsumerMapper,
esdl.HeatingDemand: EsdlAssetConsumerMapper,
diff --git a/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/producer_mapper.py b/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/producer_mapper.py
index c365deea..1b483bb9 100644
--- a/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/producer_mapper.py
+++ b/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/producer_mapper.py
@@ -15,7 +15,10 @@
"""Module containing the Esdl to Producer asset mapper class."""
+import esdl
+
from omotes_simulator_core.entities.assets.asset_abstract import AssetAbstract
+from omotes_simulator_core.entities.assets.elec_boiler import ElecBoiler
from omotes_simulator_core.entities.assets.esdl_asset_object import EsdlAssetObject
from omotes_simulator_core.entities.assets.production_cluster import ProductionCluster
from omotes_simulator_core.simulation.mappers.mappers import EsdlMapperAbstract
@@ -34,10 +37,29 @@ def to_entity(self, esdl_asset: EsdlAssetObject) -> AssetAbstract:
:param EsdlAssetObject esdl_asset: Object to be converted to a producer entity.
:return: Producer object.
"""
- producer_entity = ProductionCluster(
- asset_name=esdl_asset.esdl_asset.name,
- asset_id=esdl_asset.esdl_asset.id,
- port_ids=esdl_asset.get_port_ids(),
- )
+
+ if type(esdl_asset.esdl_asset) == esdl.ElectricBoiler:
+ producer_entity = ElecBoiler(
+ asset_name=esdl_asset.esdl_asset.name,
+ asset_id=esdl_asset.esdl_asset.id,
+ port_ids=esdl_asset.get_port_ids(),
+ )
+
+ elif type(esdl_asset.esdl_asset) == esdl.GasHeater:
+ # producer_entity = ElecBoiler( # type:ignore
+ # asset_name=esdl_asset.esdl_asset.name,
+ # asset_id=esdl_asset.esdl_asset.id,
+ # port_ids=esdl_asset.get_port_ids(),
+ # )
+ pass # TODO: create and link the gas heater mapper here.
+
+ else:
+ producer_entity = ProductionCluster(
+ asset_name=esdl_asset.esdl_asset.name,
+ asset_id=esdl_asset.esdl_asset.id,
+ port_ids=esdl_asset.get_port_ids(),
+ )
+
+ # TODO: call eboiler or gas heater entities from here.
return producer_entity
diff --git a/src/omotes_simulator_core/adapter/transforms/string_to_esdl.py b/src/omotes_simulator_core/adapter/transforms/string_to_esdl.py
index 83b3519e..72abf9ac 100644
--- a/src/omotes_simulator_core/adapter/transforms/string_to_esdl.py
+++ b/src/omotes_simulator_core/adapter/transforms/string_to_esdl.py
@@ -34,6 +34,8 @@ class OmotesAssetLabels(str, Enum):
ATES = "ates"
HEAT_PUMP = "heat_pump"
HEAT_EXCHANGER = "heat_exchanger"
+ ELECTRIC_BOILER = "electric_boiler"
+ GAS_HEATER = "gas_heater"
class StringEsdlAssetMapper:
@@ -69,6 +71,8 @@ def __init__(self) -> None:
OmotesAssetLabels.ATES: [esdl.ATES],
OmotesAssetLabels.HEAT_PUMP: [esdl.HeatPump],
OmotesAssetLabels.HEAT_EXCHANGER: [esdl.HeatExchange],
+ OmotesAssetLabels.ELECTRIC_BOILER: [esdl.ElectricBoiler],
+ OmotesAssetLabels.GAS_HEATER: [esdl.GasHeater],
}
self.type_to_label_map: dict[Type[esdl.Asset], OmotesAssetLabels] = {
diff --git a/src/omotes_simulator_core/entities/assets/elec_boiler.py b/src/omotes_simulator_core/entities/assets/elec_boiler.py
new file mode 100644
index 00000000..3dcb03d2
--- /dev/null
+++ b/src/omotes_simulator_core/entities/assets/elec_boiler.py
@@ -0,0 +1,90 @@
+# Copyright (c) 2026. Deltares & TNO
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+"""ElecBoiler class."""
+import logging
+
+from omotes_simulator_core.entities.assets.asset_defaults import (
+ PROPERTY_ELECTRICITY_CONSUMPTION,
+ PROPERTY_HEAT_SUPPLIED,
+ PROPERTY_HEAT_SUPPLY_SET_POINT,
+ HeatPumpDefaults,
+)
+from omotes_simulator_core.entities.assets.production_cluster import ProductionCluster
+from omotes_simulator_core.solver.network.assets.production_asset import HeatBoundary
+
+logger = logging.getLogger(__name__)
+
+
+class ElecBoiler(ProductionCluster):
+ """An electric boiler asset.
+
+ It represents a two port electric boiler that adds heat to the network by consuming electricity.
+ """
+
+ efficiency: float
+ """The efficiency of the electric boiler [-]."""
+
+ def __init__(
+ self,
+ asset_name: str,
+ asset_id: str,
+ port_ids: list[str],
+ efficiency: float = HeatPumpDefaults.coefficient_of_performance, #TODO: Make sure this takes a default of 1.0.
+ ) -> None:
+ """
+ Initialize the ElecBoiler asset.
+
+ :param str asset_name: The name of the asset.
+ :param str asset_id: The unique identifier of the asset.
+ :param List[str] port_ids: List of ids of the connected ports.
+ """
+ super().__init__(
+ asset_name=asset_name,
+ asset_id=asset_id,
+ port_ids=port_ids,
+ )
+ self.efficiency = efficiency
+ self.solver_asset = HeatBoundary(
+ name=self.name,
+ _id=self.asset_id,
+ pre_scribe_mass_flow=False,
+ set_pressure=self.pressure_supply,
+ )
+
+ def get_electric_power_consumption(self) -> float:
+ """Calculate the electric power consumption of the electric boiler.
+
+ The electric power consumption is calculated as the ratio between the heat
+ supplied by the boiler to the network and the efficiency of the boiler.
+
+ :return: float
+ The electric power consumption of the electric boiler.
+ """
+
+ return abs(self.get_actual_heat_supplied()) / self.efficiency
+
+ def write_to_output(self) -> None:
+ """Method to write time step results to the output dict.
+
+ The output list is a list of dictionaries, where each dictionary
+ represents the output of the asset for a specific timestep.
+ """
+ output_dict_temp = {
+ PROPERTY_HEAT_SUPPLY_SET_POINT: self.heat_demand_set_point,
+ PROPERTY_HEAT_SUPPLIED: self.get_actual_heat_supplied(),
+ PROPERTY_ELECTRICITY_CONSUMPTION: (self.get_electric_power_consumption()),
+ }
+ self.outputs[1][-1].update(output_dict_temp) # Outputs appended to the out port.
\ No newline at end of file
diff --git a/src/omotes_simulator_core/entities/assets/esdl_asset_object.py b/src/omotes_simulator_core/entities/assets/esdl_asset_object.py
index f1d1902b..8a3019ad 100644
--- a/src/omotes_simulator_core/entities/assets/esdl_asset_object.py
+++ b/src/omotes_simulator_core/entities/assets/esdl_asset_object.py
@@ -186,6 +186,16 @@ def get_temperatures_asset(self, side: str | None = None) -> Temperatures:
in_flow=self.get_temperature("In", "Return"),
out_flow=self.get_temperature("Out", "Supply"),
)
+ elif self.get_esdl_type() == OmotesAssetLabels.GAS_HEATER:
+ temperatures = Temperatures(
+ in_flow=self.get_temperature("In", "Return"),
+ out_flow=self.get_temperature("Out", "Supply"),
+ )
+ elif self.get_esdl_type() == OmotesAssetLabels.ELECTRIC_BOILER:
+ temperatures = Temperatures(
+ in_flow=self.get_temperature("In", "Return"),
+ out_flow=self.get_temperature("Out", "Supply"),
+ )
elif (self.get_esdl_type() == OmotesAssetLabels.ATES) | (
self.get_esdl_type() == OmotesAssetLabels.STORAGE
):
diff --git a/src/omotes_simulator_core/entities/assets/gas_heater.py b/src/omotes_simulator_core/entities/assets/gas_heater.py
new file mode 100644
index 00000000..d69e68d5
--- /dev/null
+++ b/src/omotes_simulator_core/entities/assets/gas_heater.py
@@ -0,0 +1,93 @@
+# Copyright (c) 2026. Deltares & TNO
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+"""GasHeater class."""
+import logging
+
+from omotes_simulator_core.entities.assets.asset_defaults import (
+ PROPERTY_ELECTRICITY_CONSUMPTION,
+ PROPERTY_HEAT_SUPPLIED,
+ PROPERTY_HEAT_SUPPLY_SET_POINT,
+ HeatPumpDefaults,
+)
+from omotes_simulator_core.entities.assets.production_cluster import ProductionCluster
+from omotes_simulator_core.solver.network.assets.production_asset import HeatBoundary
+
+logger = logging.getLogger(__name__)
+
+
+class GasHeater(ProductionCluster):
+ """A gas heater asset.
+
+ It represents a two port gas heater that adds heat to the network by consuming gas.
+ """
+
+ efficiency: float
+ """The efficiency of the gas heater [-]."""
+
+ def __init__(
+ self,
+ asset_name: str,
+ asset_id: str,
+ port_ids: list[str],
+ efficiency: float = HeatPumpDefaults.coefficient_of_performance, #TODO: Make sure this takes a default of 1.0.
+ ) -> None:
+ """
+ Initialize the GasHeater asset.
+
+ :param str asset_name: The name of the asset.
+ :param str asset_id: The unique identifier of the asset.
+ :param List[str] port_ids: List of ids of the connected ports.
+ """
+ super().__init__(
+ asset_name=asset_name,
+ asset_id=asset_id,
+ port_ids=port_ids,
+ )
+ self.efficiency = efficiency
+ self.solver_asset = HeatBoundary(
+ name=self.name,
+ _id=self.asset_id,
+ pre_scribe_mass_flow=False,
+ set_pressure=self.pressure_supply,
+ )
+
+ def get_gas_consumption(self) -> float:
+ """Calculate the gas power consumption of the gas heater.
+
+ The gas power consumption is calculated as the ratio between the heat
+ supplied by the heater to the network and the efficiency of the heater.
+
+ :return: float
+ The gas power consumption of the gas heater.
+ """
+
+ # self.Gas_demand_mass_flow / 1000.0 * self.energy_content * self.efficiency
+ # - self.Heat_source
+
+ return 1.0 # TODO: Fix this, it needs a gas energy content value. Check if it is already used somewhere else.
+
+ def write_to_output(self) -> None:
+ """Method to write time step results to the output dict.
+
+ The output list is a list of dictionaries, where each dictionary
+ represents the output of the asset for a specific timestep.
+ """
+ output_dict_temp = {
+ PROPERTY_HEAT_SUPPLY_SET_POINT: self.heat_demand_set_point,
+ PROPERTY_HEAT_SUPPLIED: self.get_actual_heat_supplied(),
+ PROPERTY_ELECTRICITY_CONSUMPTION: (self.get_gas_consumption()),
+ }
+ self.outputs[1][-1].update(output_dict_temp) # Outputs appended to the out port.
\ No newline at end of file
diff --git a/testdata/test1_eboiler.esdl b/testdata/test1_eboiler.esdl
new file mode 100644
index 00000000..bb6a587a
--- /dev/null
+++ b/testdata/test1_eboiler.esdl
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+