Skip to content

Commit

Permalink
Merge pull request #198 from Project-OMOTES/193-set-priority-based-on…
Browse files Browse the repository at this point in the history
…-marginal-costs

193 set priority based on marginal costs
  • Loading branch information
vanmeerkerk authored Nov 7, 2024
2 parents 0585e52 + 824de45 commit 02d096c
Show file tree
Hide file tree
Showing 12 changed files with 296 additions and 43 deletions.
3 changes: 3 additions & 0 deletions doc/controller/main_controller_class.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ The update_setpoints method retrieves controller settings for a specific time st
To create a custom controller, implement a new class with your desired logic and inherit from
the NetworkControllerAbstract class.

In the current implementation of the controller the priority of the sources is set based on the
marginal costs.

.. autoclass:: omotes_simulator_core.entities.network_controller_abstract.NetworkControllerAbstract
:members:
:no-index:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def to_entity(self, esdl_asset: EsdlAssetObject) -> ControllerProducer:
power = result[0]
else:
raise ValueError("No power found for asset: " + esdl_asset.esdl_asset.name)
marginal_costs = esdl_asset.get_marginal_costs()
temperature_supply = esdl_asset.get_supply_temperature("Out")
temperature_return = esdl_asset.get_return_temperature("In")
contr_producer = ControllerProducer(
Expand All @@ -94,6 +95,7 @@ def to_entity(self, esdl_asset: EsdlAssetObject) -> ControllerProducer:
temperature_supply=temperature_supply,
temperature_return=temperature_return,
power=power,
marginal_costs=marginal_costs,
)
return contr_producer

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def __init__(
temperature_supply: float,
temperature_return: float,
power: float,
marginal_costs: float,
priority: int = 1,
):
"""Constructor for the source.
Expand All @@ -38,10 +39,12 @@ def __init__(
:param float temperature_supply: Supply temperature of the source.
:param float temperature_return: Return temperature of the source.
:param float power: Power of the source.
:param float marginal_costs: Marginal costs of the source.
:param int priority: Priority of the source.
"""
super().__init__(name, identifier)
self.temperature_return: float = temperature_return
self.temperature_supply: float = temperature_supply
self.power: float = power
self.marginal_costs: float = marginal_costs
self.priority: int = priority
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def set_setpoints(self, setpoints: Dict) -> None:
raise ValueError(
f"The setpoints {necessary_setpoints.difference(setpoints_set)} are missing."
)
self.thermal_power_allocation = setpoints[PROPERTY_HEAT_DEMAND]
self.thermal_power_allocation = -setpoints[PROPERTY_HEAT_DEMAND]
self.temperature_return_target = setpoints[PROPERTY_TEMPERATURE_RETURN]
self.temperature_supply = setpoints[PROPERTY_TEMPERATURE_SUPPLY]
adjusted_mass_flowrate = heat_demand_and_temperature_to_mass_flow(
Expand Down
16 changes: 16 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 @@ -101,6 +101,22 @@ def get_port_type(self, port_type: str) -> Type[esdl.Port]:
else:
raise ValueError(f"Port type not recognized: {port_type}")

def get_marginal_costs(self) -> float:
"""Get the marginal costs of the asset."""
if self.esdl_asset.costInformation is None:
logger.warning(
f"No cost information found for asset, Marginal costs set to 0 for: "
f"{self.esdl_asset.name}"
)
return 0
if self.esdl_asset.costInformation.marginalCosts is None:
logger.warning(
f"No marginal costs found for asset, Marginal costs set to 0 for: "
f"{self.esdl_asset.name}"
)
return 0
return float(self.esdl_asset.costInformation.marginalCosts.value)


def get_return_temperature(esdl_port: esdl.Port) -> float:
"""Get the temperature of the port."""
Expand Down
81 changes: 64 additions & 17 deletions src/omotes_simulator_core/entities/network_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import datetime
import logging
from typing import List

from omotes_simulator_core.entities.network_controller_abstract import NetworkControllerAbstract
from omotes_simulator_core.entities.assets.asset_defaults import (
PROPERTY_TEMPERATURE_SUPPLY,
Expand All @@ -40,10 +41,32 @@ def __init__(
consumers: List[ControllerConsumer],
storages: List[ControllerStorage],
) -> None:
"""Constructor for controller for a heat network."""
"""Constructor for controller for a heat network.
The priority of the producers is set based on the marginal costs. The lowest marginal
costs has the highest priority.
:param List[ControllerProducer] producers: List of producers in the network.
:param List[ControllerConsumer] consumers: List of consumers in the network.
:param List[ControllerStorage] storages: List of storages in the network.
"""
self.producers = producers
self.consumers = consumers
self.storages = storages
self._set_priority_from_marginal_costs()

def _set_priority_from_marginal_costs(self) -> None:
"""This method sets the priority of the producers based on the marginal costs.
The priority of the producers is set based on the marginal costs. The producer with the
lowest marginal costs has the highest priority (lowest value).
"""
# Created a sorted list of unique marginal costs.
unique_sorted_values = sorted(set([producer.marginal_costs for producer in self.producers]))

# set the priority based on the index of the marginal cost in the sorted list.
for producer in self.producers:
producer.priority = unique_sorted_values.index(producer.marginal_costs) + 1

def update_setpoints(self, time: datetime.datetime) -> dict:
"""Method to get the controller inputs for the network.
Expand All @@ -52,9 +75,13 @@ def update_setpoints(self, time: datetime.datetime) -> dict:
:return: dict with the key the asset id and the heat demand for that asset.
"""
# TODO add also the possibility to return mass flow rate instead of heat demand.
if (self.get_total_supply() + (
-1 * self.get_total_discharge_storage())) <= self.get_total_demand(time):
logger.warning(f"Total supply + storage is lower than total demand at time: {time}")
if (
self.get_total_supply() + (-1 * self.get_total_discharge_storage())
) <= self.get_total_demand(time):
logger.warning(
f"Total supply + storage is lower than total demand at time: {time}"
f"Consumers are capped to the available power."
)
producers = self._set_producers_to_max()
storages = self._set_all_storages_discharge_to_max()
consumers = self._set_consumer_capped(time)
Expand Down Expand Up @@ -138,6 +165,21 @@ def _set_producers_to_max(self) -> dict:
producers[self.producers[0].id][PROPERTY_SET_PRESSURE] = True
return producers

def _get_basic_producers(self) -> dict:
"""Method to get the basic dict with setting for the producers.
:return dict: Dict with key= asset-id and value=setpoints for the producers.
"""
producers = {}
for source in self.producers:
producers[source.id] = {
PROPERTY_HEAT_DEMAND: 0.0,
PROPERTY_TEMPERATURE_RETURN: source.temperature_return,
PROPERTY_TEMPERATURE_SUPPLY: source.temperature_supply,
PROPERTY_SET_PRESSURE: False,
}
return producers

def _set_all_storages_discharge_to_max(self) -> dict:
"""Method to set all the storages to the max discharge power.
Expand Down Expand Up @@ -218,30 +260,35 @@ def _set_producers_based_on_priority(self, time: datetime.datetime) -> dict:
:return: dict with the key the asset id and the value a dict with the set points for the
producers.
"""
producers = self._set_producers_to_max()
producers = self._get_basic_producers()
total_demand = self.get_total_demand(time) + self.get_total_charge_storage()
if total_demand > self.get_total_supply():
raise ValueError(
"Total demand is higher than total supply. "
"Cannot set producers based on priority."
logger.warning(
"Total demand is higher than total supply. Cannot set producers based on priority."
)

priority = 0
set_pressure = True
while total_demand > 0:
priority += 1
total_supply = self.get_total_supply_priority(priority)
priority_producers = [
producer for producer in self.producers if producer.priority == priority
]
if total_supply > total_demand:
factor = total_demand / total_supply
for source in self.producers:
if source.priority == priority:
producers[source.id][PROPERTY_HEAT_DEMAND] = source.power * factor
if set_pressure:
producers[source.id][PROPERTY_SET_PRESSURE] = True
set_pressure = False
for source in priority_producers:
producers[source.id][PROPERTY_HEAT_DEMAND] = source.power * factor
if set_pressure:
producers[source.id][PROPERTY_SET_PRESSURE] = True
set_pressure = False
total_demand = 0
else:
for source in self.producers:
if source.priority == priority:
producers[source.id][PROPERTY_HEAT_DEMAND] = source.power
for source in priority_producers:
producers[source.id][PROPERTY_HEAT_DEMAND] = source.power
total_demand -= total_supply
if set_pressure:
# set pressure has not been set and it needs to be set. This is set for the first
# producer
producers[self.producers[0].id][PROPERTY_SET_PRESSURE] = True
return producers
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def get_thermal_equations(self, connection_point: int) -> EquationObject:
"mass_flow_rate", connection_point=connection_point, use_relative_indexing=True
)
]
> 0
> 1e-5
):
return self.get_internal_energy_equation()
else:
Expand Down
Loading

0 comments on commit 02d096c

Please sign in to comment.