Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.

"""Module containing the Esdl to asset mapper class."""
from typing import Optional

import pandas as pd

from omotes_simulator_core.entities.assets.controller.asset_controller_abstract import (
AssetControllerAbstract,
)
from omotes_simulator_core.entities.assets.controller.controller_producer import ControllerProducer
from omotes_simulator_core.entities.assets.controller.profile_interpolation import (
ProfileInterpolator,
)
from omotes_simulator_core.entities.assets.esdl_asset_object import EsdlAssetObject
from omotes_simulator_core.simulation.mappers.mappers import EsdlMapperAbstract

Expand All @@ -29,7 +36,9 @@ def to_esdl(self, entity: AssetControllerAbstract) -> EsdlAssetObject:
"""Map an Entity to a EsdlAsset."""
raise NotImplementedError("EsdlAssetControllerProducerMapper.to_esdl()")

def to_entity(self, esdl_asset: EsdlAssetObject) -> ControllerProducer:
def to_entity(
self, esdl_asset: EsdlAssetObject, timestep: Optional[int] = None
) -> ControllerProducer:
"""Method to map an esdl asset to a producer entity class.

:param EsdlAssetObject model: Object to be converted to an asset entity.
Expand All @@ -41,13 +50,27 @@ def to_entity(self, esdl_asset: EsdlAssetObject) -> ControllerProducer:
temperature_in = esdl_asset.get_temperature("In", "Return")
temperature_out = esdl_asset.get_temperature("Out", "Supply")
strategy_priority = esdl_asset.get_strategy_priority()

if esdl_asset.has_constraint():
profile = esdl_asset.get_constraint_max_profile()
self.profile_interpolator = ProfileInterpolator(
profile=profile,
sampling_method=esdl_asset.get_sampling_method(),
interpolation_method=esdl_asset.get_interpolation_method(),
timestep=timestep,
)
resampled_profile = self.profile_interpolator.get_resampled_profile()
else:
resampled_profile = pd.DataFrame()

contr_producer = ControllerProducer(
name=esdl_asset.esdl_asset.name,
identifier=esdl_asset.esdl_asset.id,
temperature_in=temperature_in,
temperature_out=temperature_out,
power=power,
marginal_costs=marginal_costs,
profile=resampled_profile, # Empty DataFrame is added if there is no profile.
priority=strategy_priority,
)
return contr_producer
Original file line number Diff line number Diff line change
Expand Up @@ -115,24 +115,24 @@ def get_total_charge_storage(self) -> float:
* self.factor_to_first_network
)

def get_total_supply(self) -> float:
def get_total_supply(self, time: datetime.datetime) -> float:
"""Method to get the total heat supply of the network.

:return float: Total heat supply of all producers.
"""
return (
float(sum([producer.power for producer in self.producers]))
float(sum([producer.get_max_power(time) for producer in self.producers]))
* self.factor_to_first_network
)

def set_supply_to_max(self, priority: int = 0) -> dict:
def set_supply_to_max(self, time: datetime.datetime, priority: int = 0) -> dict:
"""Method to set the producers to the max power.

:return dict: Dict with key= asset-id and value=setpoints for the producers.
"""
return self.set_supply(factor=1, priority=priority)
return self.set_supply(time, factor=1, priority=priority)

def set_supply(self, factor: float = 1, priority: int = 0) -> dict:
def set_supply(self, time: datetime.datetime, factor: float = 1, priority: int = 0) -> dict:
"""Method to set the producers with the given priority to max power times the factor.

:param float factor: Factor to multiply the max power with.
Expand All @@ -147,7 +147,7 @@ def set_supply(self, factor: float = 1, priority: int = 0) -> dict:
elif source.priority != priority:
continue
producers[source.id] = {
PROPERTY_HEAT_DEMAND: source.power * factor,
PROPERTY_HEAT_DEMAND: source.get_max_power(time) * factor,
PROPERTY_TEMPERATURE_OUT: source.temperature_out,
PROPERTY_TEMPERATURE_IN: source.temperature_in,
PROPERTY_SET_PRESSURE: False,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Module containing the classes for the controller."""

import datetime

import pandas as pd

from omotes_simulator_core.entities.assets.controller.asset_controller_abstract import (
AssetControllerAbstract,
)
Expand All @@ -30,6 +34,7 @@ def __init__(
temperature_out: float,
power: float,
marginal_costs: float,
profile: pd.DataFrame,
priority: None | int = 1,
):
"""Constructor for the source.
Expand All @@ -48,3 +53,20 @@ def __init__(
self.power: float = power
self.marginal_costs: float = marginal_costs
self.priority: None | int = priority
self.profile: pd.DataFrame = profile.set_index("date") if not profile.empty else profile

def get_max_power(self, time: datetime.datetime) -> float:
"""Gets the maximum producer power at the given timestep.

If there is a profile, it will look it up, otherwise it returns the
maximum power defined in the esdl parameter.
"""
if self.profile.empty:
max_power = self.power
else:
try:
max_power = float(self.profile.loc[time, "values"])
except KeyError:
max_power = self.power

return max_power
25 changes: 24 additions & 1 deletion src/omotes_simulator_core/entities/assets/esdl_asset_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def get_property(self, esdl_property_name: str, default_value: Any) -> Any:
return getattr(self.esdl_asset, esdl_property_name, default_value)

def get_profile(self) -> pd.DataFrame:
"""Get the profile of the asset."""
"""Get the profile of the asset's ports."""
for esdl_port in self.esdl_asset.port:
if esdl_port.profile:
return get_data_from_profile(esdl_port.profile[0])
Expand All @@ -100,6 +100,15 @@ def get_profile(self) -> pd.DataFrame:
)
raise ValueError(f"No profile found for asset: {self.esdl_asset.name}")

def get_constraint_max_profile(self) -> pd.DataFrame:
"""Get the profile from the asset's maximum constraint."""
for constraint in self.esdl_asset.constraint:
if constraint.maximum:
profile = constraint.maximum
else:
return pd.DataFrame()
return get_data_from_profile(profile)

def get_sampling_method(self) -> ProfileSamplingMethod:
"""Get the interpolation method of the asset."""
# TODO: Get sampling method from ESDL properties if available
Expand Down Expand Up @@ -194,6 +203,20 @@ def is_heat_transfer_asset(self) -> bool:
self.esdl_asset, esdl.HeatExchange
)

def has_profile(self) -> bool:
"""Checks if an asset has a profile assigned to any of its ports."""
for esdl_port in self.esdl_asset.port:
if esdl_port.profile:
return True
return False

def has_constraint(self) -> bool:
"""Checks if an asset has a constraint assigned to it."""
if self.esdl_asset.constraint.items:
return True
else:
return False


def get_return_temperature(esdl_port: esdl.Port) -> float:
"""Get the temperature of the port."""
Expand Down
22 changes: 12 additions & 10 deletions src/omotes_simulator_core/entities/network_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def update_setpoints(self, time: datetime.datetime) -> dict:
"""
self.update_networks_factor()
total_demand = sum([network.get_total_heat_demand(time) for network in self.networks])
total_supply = sum([network.get_total_supply() for network in self.networks])
total_supply = sum([network.get_total_supply(time) for network in self.networks])
total_charge_storage = sum(
[network.get_total_charge_storage() for network in self.networks]
)
Expand All @@ -97,7 +97,7 @@ def update_setpoints(self, time: datetime.datetime) -> dict:
f"Consumers are capped to the available power."
)
factor = (total_supply + total_discharge_storage) / total_demand
producers = self._set_producers_to_max()
producers = self._set_producers_to_max(time)
producers.update(self._set_all_storages_discharge_to_max())
producers.update(self._set_consumer_to_demand(time, factor=factor))
else:
Expand All @@ -107,19 +107,19 @@ def update_setpoints(self, time: datetime.datetime) -> dict:
surplus_supply = total_supply - total_demand
if surplus_supply <= total_charge_storage:
storages = self._set_storages_charge_power(surplus_supply)
producers = self._set_producers_to_max()
producers = self._set_producers_to_max(time)
else:
# need to cap the power of the source based on priority
storages = self._set_storages_charge_power(total_charge_storage)
producers = self._set_producers_based_on_priority(
total_demand + total_charge_storage
time, total_demand + total_charge_storage
)
else:
# there is a deficit of supply we can discharge the storage, storage becomes
# producer.
deficit_supply = total_demand - total_supply
storages = self._set_storages_discharge_power(deficit_supply)
producers = self._set_producers_to_max()
producers = self._set_producers_to_max(time)
producers.update(consumers)
producers.update(storages)
# Getting the settings for the heat transfer assets
Expand Down Expand Up @@ -153,10 +153,10 @@ def update_setpoints(self, time: datetime.datetime) -> dict:
producers[pressure_set_asset][PROPERTY_SET_PRESSURE] = True
return producers

def _set_producers_to_max(self) -> dict:
def _set_producers_to_max(self, time: datetime.datetime) -> dict:
result = {}
for network in self.networks:
result.update(network.set_supply_to_max())
result.update(network.set_supply_to_max(time))
return result

def _set_all_storages_discharge_to_max(self) -> dict:
Expand Down Expand Up @@ -199,7 +199,9 @@ def _set_storages_discharge_power(self, power: float) -> dict:
results.update(network.set_storage_discharge_power(factor=factor))
return results

def _set_producers_based_on_priority(self, required_supply: float) -> dict:
def _set_producers_based_on_priority(
self, time: datetime.datetime, required_supply: float
) -> dict:
"""Method to set the producers based on the priority of the source."""
producers = {}
priority = 0
Expand All @@ -210,12 +212,12 @@ def _set_producers_based_on_priority(self, required_supply: float) -> dict:
if required_supply > 0:
# set the producers with the priority to the max
for network in self.networks:
producers.update(network.set_supply_to_max(priority))
producers.update(network.set_supply_to_max(time, priority))
else:
# set the producers with the priority with a factor.
factor = 1 + required_supply / max_supply_priority
for network in self.networks:
producers.update(network.set_supply(factor=factor, priority=priority))
producers.update(network.set_supply(time, factor=factor, priority=priority))
if len(producers) < sum([len(network.producers) for network in self.networks]):
# not al producers are set need to set the remaining to zero.
for network in self.networks:
Expand Down
2 changes: 1 addition & 1 deletion src/omotes_simulator_core/infrastructure/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def run(file_path: str | None = None) -> pd.DataFrame:
name="test run",
timestep=3600,
start=datetime.strptime("2019-01-01T00:00:00", "%Y-%m-%dT%H:%M:%S"),
stop=datetime.strptime("2019-01-01T01:00:00", "%Y-%m-%dT%H:%M:%S"),
stop=datetime.strptime("2019-01-08T01:00:00", "%Y-%m-%dT%H:%M:%S"),
)

esdl_file_path = sys.argv[1] if file_path is None else file_path
Expand Down
54 changes: 54 additions & 0 deletions testdata/test1_prod_profile.esdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?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="10">
<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:GenericProducer" power="5000000.0" id="cf3d4b5e-437f-4c1b-a7f9-7fd7e8a269b4" name="GenericProducer_cf3d">
<port xsi:type="esdl:InPort" id="9c258b9d-3149-4720-8931-f4bef1080ec1" name="In" connectedTo="935fb733-9f76-4a8d-8899-1ad8689a4b12" carrier="0bd9cb08-2f69-4e97-8ac8-bd87b07e466a_ret"/>
<port xsi:type="esdl:OutPort" id="2d818e3d-8a39-4cec-afa0-f6dbbfd50696" name="Out" carrier="0bd9cb08-2f69-4e97-8ac8-bd87b07e466a" connectedTo="a9793a5e-df4f-4795-8079-015dfaf57f82"/>
<geometry xsi:type="esdl:Point" CRS="WGS84" lon="4.558639526367188" lat="52.148869383489114"/>
<constraint xsi:type="esdl:ProfileConstraint" id="c78d01bc-d2ba-4ef3-934d-3876a98ceadd" name="test_prof" description="" attributeReference="power">
<maximum xsi:type="esdl:InfluxDBProfile" multiplier="0.1" measurement="WarmingUp default profiles" field="demand1_MW" host="https://profiles.warmingup.info" database="energy_profiles" filters="" startDate="2018-12-31T23:00:00.000000+0000" endDate="2019-12-31T22:00:00.000000+0000" id="7c1057e0-9493-47d0-9f79-2e17ca4f2a0d">
<profileQuantityAndUnit xsi:type="esdl:QuantityAndUnitReference" reference="e9405fc8-5e57-4df5-8584-4babee7cdf1b"/>
</maximum>
</constraint>
</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="2d818e3d-8a39-4cec-afa0-f6dbbfd50696" 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="9c258b9d-3149-4720-8931-f4bef1080ec1" 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>
</area>
</instance>
</esdl:EnergySystem>
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,16 @@
class TestControllerProducerMapper(unittest.TestCase):
"""Test ControllerProducerMapper."""

def setUp(self) -> None:
"""Set up test case."""
def test_to_entity_method(self):
"""Test settings of controller for producer."""
# Arrange
# Load the test esdl file
esdl_file_path = (
Path(__file__).parent / ".." / ".." / ".." / ".." / "testdata" / "test_ates.esdl"
)
self.esdl_object = EsdlObject(pyesdl_from_file(esdl_file_path))
# Create a ControllerProducerMapper object
self.mapper = ControllerProducerMapper()

def test_to_entity_method(self):
"""Test settings of controller for producer."""
# Arrange
producer_assets = self.esdl_object.get_all_assets_of_type("producer")

# Act
Expand All @@ -49,3 +46,26 @@ def test_to_entity_method(self):
self.assertEqual(controller_producer.temperature_out, 353.15)
self.assertEqual(controller_producer.power, 1e8)
self.assertEqual(controller_producer.marginal_costs, 0)

def test_get_max_constraint(self):
"""Test to check if the maximum constraint is grabbed correctly."""
# Arrange
esdl_file_path_constraint = (
Path(__file__).parent
/ ".."
/ ".."
/ ".."
/ ".."
/ "testdata"
/ "test1_prod_profile.esdl"
)
self.esdl_object = EsdlObject(pyesdl_from_file(esdl_file_path_constraint))
self.mapper = ControllerProducerMapper()
producer_assets = self.esdl_object.get_all_assets_of_type("producer")

# Act
constraint_profile = producer_assets[0].get_constraint_max_profile()

# Assert
self.assertEqual(producer_assets[0].has_constraint(), True)
self.assertEqual(constraint_profile.iloc[0].values[1], 143277.6298)
Loading