Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -34,10 +37,29 @@
: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(

Check failure on line 57 in src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/producer_mapper.py

View workflow job for this annotation

GitHub Actions / Typecheck (3.11)

error: Incompatible types in assignment (expression has type "ProductionCluster", variable has type "ElecBoiler") [assignment]
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
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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] = {
Expand Down
90 changes: 90 additions & 0 deletions src/omotes_simulator_core/entities/assets/elec_boiler.py
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.

"""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.
10 changes: 10 additions & 0 deletions src/omotes_simulator_core/entities/assets/esdl_asset_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
):
Expand Down
93 changes: 93 additions & 0 deletions src/omotes_simulator_core/entities/assets/gas_heater.py
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.

"""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.
49 changes: 49 additions & 0 deletions testdata/test1_eboiler.esdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version='1.0' encoding='UTF-8'?>
<esdl:EnergySystem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:esdl="http://www.tno.nl/esdl" id="fdbbf5ee-6e86-4c82-9926-4b59de482378_with_return_network" description="" esdlVersion="v2207" name="Untitled EnergySystem with return network" version="4">
<energySystemInformation xsi:type="esdl:EnergySystemInformation" id="c615f17e-c077-48c4-8a78-6ae05f8a908f">
<quantityAndUnits xsi:type="esdl:QuantityAndUnits" id="f61a1799-bf04-416a-b15e-93097722ada7">
<quantityAndUnit xsi:type="esdl:QuantityAndUnitType" physicalQuantity="POWER" id="e9405fc8-5e57-4df5-8584-4babee7cdf1b" multiplier="MEGA" unit="WATT" description="Power in MW"/>
<quantityAndUnit xsi:type="esdl:QuantityAndUnitType" physicalQuantity="ENERGY" id="12c481c0-f81e-49b6-9767-90457684d24a" multiplier="KILO" unit="WATTHOUR" description="Energy in kWh"/>
</quantityAndUnits>
<carriers xsi:type="esdl:Carriers" id="c27258b1-f4f6-4e09-a77a-ce466dbd82d2">
<carrier xsi:type="esdl:HeatCommodity" supplyTemperature="80.0" name="HeatSupply" id="0bd9cb08-2f69-4e97-8ac8-bd87b07e466a"/>
<carrier xsi:type="esdl:HeatCommodity" returnTemperature="40.0" name="HeatReturn" id="0bd9cb08-2f69-4e97-8ac8-bd87b07e466a_ret"/>
</carriers>
</energySystemInformation>
<instance xsi:type="esdl:Instance" id="a357cbbe-f277-42b1-8456-cbbadc8ceb2e" name="Untitled Instance">
<area xsi:type="esdl:Area" name="Untitled Area" id="e4002c22-abd5-43f6-81a8-e6b5f960bfa5">
<asset xsi:type="esdl:HeatingDemand" id="48f3e425-2143-4dcd-9101-c7e22559e82b" name="HeatingDemand_48f3">
<port xsi:type="esdl:InPort" id="af0904f7-ba1f-4e79-9040-71e08041601b" name="In" connectedTo="3f2dc09a-0cee-44bd-a337-cea55461a334" carrier="0bd9cb08-2f69-4e97-8ac8-bd87b07e466a"/>
<port xsi:type="esdl:OutPort" id="e890f65f-80e7-46fa-8c52-5385324bf686" name="Out" carrier="0bd9cb08-2f69-4e97-8ac8-bd87b07e466a_ret" connectedTo="422cb921-23d2-4410-9072-aaa5796a0620">
<profile xsi:type="esdl:InfluxDBProfile" endDate="2019-12-31T22:00:00.000000+0000" id="b77e41bc-a5ca-4823-b467-09872f2b6772" port="443" host="profiles.warmingup.info" filters="" startDate="2018-12-31T23:00:00.000000+0000" database="energy_profiles" measurement="WarmingUp default profiles" field="demand4_MW">
<profileQuantityAndUnit xsi:type="esdl:QuantityAndUnitReference" reference="e9405fc8-5e57-4df5-8584-4babee7cdf1b"/>
</profile>
</port>
<geometry xsi:type="esdl:Point" CRS="WGS84" lon="4.63726043701172" lat="52.158769628869045"/>
</asset>
<asset xsi:type="esdl:Pipe" id="Pipe1" length="6267.0" name="Pipe1" innerDiameter="0.1" related="Pipe1_ret">
<port xsi:type="esdl:InPort" id="a9793a5e-df4f-4795-8079-015dfaf57f82" name="In" connectedTo="f8545f8c-4632-4d07-b8d5-2f01d8f7f51b" carrier="0bd9cb08-2f69-4e97-8ac8-bd87b07e466a"/>
<port xsi:type="esdl:OutPort" id="3f2dc09a-0cee-44bd-a337-cea55461a334" name="Out" connectedTo="af0904f7-ba1f-4e79-9040-71e08041601b" carrier="0bd9cb08-2f69-4e97-8ac8-bd87b07e466a"/>
<geometry xsi:type="esdl:Line" CRS="WGS84">
<point xsi:type="esdl:Point" lon="4.558639526367188" lat="52.148869383489114"/>
<point xsi:type="esdl:Point" lon="4.594688415527345" lat="52.16740421514521"/>
<point xsi:type="esdl:Point" lon="4.63726043701172" lat="52.158769628869045"/>
</geometry>
</asset>
<asset xsi:type="esdl:Pipe" id="Pipe1_ret" length="6267.0" name="Pipe1_ret" innerDiameter="0.1" related="Pipe1">
<port xsi:type="esdl:InPort" id="422cb921-23d2-4410-9072-aaa5796a0620" name="In_ret" connectedTo="e890f65f-80e7-46fa-8c52-5385324bf686" carrier="0bd9cb08-2f69-4e97-8ac8-bd87b07e466a_ret"/>
<port xsi:type="esdl:OutPort" id="935fb733-9f76-4a8d-8899-1ad8689a4b12" name="Out_ret" connectedTo="125e42f0-74fd-4262-a8cc-2cbc51230ce5" carrier="0bd9cb08-2f69-4e97-8ac8-bd87b07e466a_ret"/>
<geometry xsi:type="esdl:Line">
<point xsi:type="esdl:Point" CRS="WGS84" lon="4.636858896813017" lat="52.15885962895904"/>
<point xsi:type="esdl:Point" CRS="WGS84" lon="4.5942969754153795" lat="52.16749421523521"/>
<point xsi:type="esdl:Point" CRS="WGS84" lon="4.558225705568235" lat="52.14895938357911"/>
</geometry>
</asset>
<asset xsi:type="esdl:ElectricBoiler" id="a813c4d3-8eab-48b4-ae2a-78198ba8476a" name="ElectricBoiler_a813" power="5000000.0">
<geometry xsi:type="esdl:Point" lat="52.148472630793485" lon="4.557490563392067" CRS="WGS84"/>
<port xsi:type="esdl:InPort" id="125e42f0-74fd-4262-a8cc-2cbc51230ce5" name="In" connectedTo="935fb733-9f76-4a8d-8899-1ad8689a4b12" carrier="0bd9cb08-2f69-4e97-8ac8-bd87b07e466a_ret"/>
<port xsi:type="esdl:OutPort" id="f8545f8c-4632-4d07-b8d5-2f01d8f7f51b" name="Out" connectedTo="a9793a5e-df4f-4795-8079-015dfaf57f82" carrier="0bd9cb08-2f69-4e97-8ac8-bd87b07e466a"/>
</asset>
</area>
</instance>
</esdl:EnergySystem>
Loading