Skip to content
Open
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
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# [Unreleased-main] - 2026-02-05

## Added
- xxx
- Addition of heat buffer asset with electric charging (i.e. HeatBufferElec) is added

## Changed
- xxx
Expand All @@ -28,7 +28,7 @@
- Clean up of old code and removing duplicates.
- Minimize TCO objective in the grow_workflow is now only based on capex and opex that can be influenced.
- Removed the requirement of "_ret" for the return network pipes, for ESDLversion 21.10 and later. The relation between supply and return pipes is now based on the "related" attribute in the esdl.
- Addtion of cooling assests (airco and low_temperature_ates) in the grow_workflow for heating and cooling networks
- Addition of cooling assests (airco and low_temperature_ates) in the grow_workflow for heating and cooling networks
- Inclusion of airco and low_temperature_ates in write_output
- New data structute for specifying database connection inputs
- Costs of available pipe classes are updated based on the asset measures and templates if they are provided.
Expand Down
1 change: 1 addition & 0 deletions src/mesido/esdl/asset_to_component_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,7 @@ def _get_connected_i_nominal_and_max(self, asset: Asset) -> Tuple[float, float]:
or asset.asset_type == "Electrolyzer"
or asset.asset_type == "ElectricBoiler"
or asset.asset_type == "HeatPump"
or asset.asset_type == "HeatStorage"
):
for port in asset.in_ports:
if isinstance(port.carrier, esdl.ElectricityCommodity):
Expand Down
61 changes: 53 additions & 8 deletions src/mesido/esdl/esdl_heat_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
GasTankStorage,
GeothermalSource,
HeatBuffer,
HeatBufferElec,
HeatDemand,
HeatExchanger,
HeatPipe,
Expand Down Expand Up @@ -323,7 +324,22 @@ def _generic_heat_modifiers(min_heat=None, max_heat=None, q_nominal=None) -> Dic

return modifiers

def convert_heat_buffer(self, asset: Asset) -> Tuple[Type[HeatBuffer], MODIFIERS]:
def _get_min_voltage(self, asset: Asset) -> float:
"""
Args:
asset: mesido common asset with all attributes

Returns:
value: minimum voltage of electric carrier in V
"""
for port in asset.in_ports:
if isinstance(port.carrier, esdl.ElectricityCommodity):
min_voltage = port.carrier.voltage
return min_voltage

def convert_heat_buffer(
self, asset: Asset
) -> Tuple[Union[Type[HeatBufferElec], Type[HeatBuffer]], MODIFIERS]:
"""
This function converts the buffer object in esdl to a set of modifiers that can be used in
a pycml object. Most important:
Expand Down Expand Up @@ -429,6 +445,8 @@ def convert_heat_buffer(self, asset: Asset) -> Tuple[Type[HeatBuffer], MODIFIERS
)

q_nominal = self._get_connected_q_nominal(asset)
if isinstance(q_nominal, dict):
q_nominal = q_nominal["Q_nominal"]

modifiers = dict(
height=r,
Expand All @@ -439,12 +457,43 @@ def convert_heat_buffer(self, asset: Asset) -> Tuple[Type[HeatBuffer], MODIFIERS
Heat_buffer=dict(min=-hfr_discharge_max, max=hfr_charge_max),
init_Heat=min_heat,
**self._generic_modifiers(asset),
**self._generic_heat_modifiers(-hfr_discharge_max, hfr_charge_max, q_nominal),
**self._supply_return_temperature_modifiers(asset),
**self._rho_cp_modifiers,
**self._get_cost_figure_modifiers(asset),
**self._generic_heat_modifiers(-hfr_discharge_max, hfr_charge_max, q_nominal),
)
if len(asset.in_ports) == 2 and len(asset.out_ports) == 1:

# TODO: CO2 coefficient

min_voltage = self._get_min_voltage(asset)
i_max, i_nom = self._get_connected_i_nominal_and_max(asset)

# ToDo: Check if max_supply (and also elec_power_nominal)
# can be defined via hfr_charge_max.
max_supply = hfr_charge_max
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this max_supply is the maximum heatflow

charging_efficiency = 1.0
if asset.attributes["chargeEfficiency"]:
charging_efficiency = asset.attributes["chargeEfficiency"]
else:
logger.error(
f"'chargeEfficiency' attribute is not defined in esdl for {asset.name}."
f" 1.0 is taken as default."
)
charging_efficiency = 1.0

modifiers.update(
dict(
elec_power_nominal=max_supply,
ElectricityIn=dict(
Power=dict(min=0.0, max=max_supply, nominal=max_supply / 2.0),
I=dict(min=0.0, max=i_max, nominal=i_nom),
V=dict(min=min_voltage, nominal=min_voltage),
),
charging_efficiency=charging_efficiency,
)
)
return HeatBufferElec, modifiers
return HeatBuffer, modifiers

def convert_heat_demand(self, asset: Asset) -> Tuple[Type[HeatDemand], MODIFIERS]:
Expand Down Expand Up @@ -2582,9 +2631,7 @@ def convert_heat_source_elec(
# TODO: CO2 coefficient

q_nominal = self._get_connected_q_nominal(asset)
for port in asset.in_ports:
if isinstance(port.carrier, esdl.ElectricityCommodity):
min_voltage = port.carrier.voltage
min_voltage = self._get_min_voltage(asset)
i_max, i_nom = self._get_connected_i_nominal_and_max(asset)

modifiers.update(
Expand Down Expand Up @@ -2661,9 +2708,7 @@ def convert_air_water_heat_pump_elec(
# TODO: CO2 coefficient

q_nominal = self._get_connected_q_nominal(asset)
for port in asset.in_ports:
if isinstance(port.carrier, esdl.ElectricityCommodity):
min_voltage = port.carrier.voltage
min_voltage = self._get_min_voltage(asset)
i_max, i_nom = self._get_connected_i_nominal_and_max(asset)
cop = asset.attributes["COP"] if asset.attributes["COP"] else 1.0

Expand Down
6 changes: 3 additions & 3 deletions src/mesido/esdl/esdl_model_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def __set_primary_secondary_heat_ports():
f"Heat out_ports "
)
elif (
asset.asset_type == "ElectricBoiler"
(asset.asset_type == "ElectricBoiler" or asset.asset_type == "HeatStorage")
and len(asset.out_ports) == 1
and len(asset.in_ports) == 2
):
Expand All @@ -239,8 +239,8 @@ def __set_primary_secondary_heat_ports():
port_map[p.id] = getattr(component, out_suf)
else:
raise Exception(
f"{asset.name} has does not have 1 electricity in_port 1 gas in port "
f"and 1 Heat out_ports "
f"{asset.name} has does not have 1 electricity in_port 1 electric "
f"in port and 1 Heat out_ports "
)
elif asset.asset_type == "Electrolyzer":
if len(asset.out_ports) == 1 and len(asset.in_ports) == 1:
Expand Down
2 changes: 2 additions & 0 deletions src/mesido/pycml/component_library/milp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@
from .multicommodity.elec_heat_source_elec import ElecHeatSourceElec
from .multicommodity.electrolyzer import Electrolyzer
from .multicommodity.gas_heat_source_gas import GasHeatSourceGas
from .multicommodity.heat_buffer_elec import HeatBufferElec

__all__ = [
"Airco",
"AirWaterHeatPump",
"AirWaterHeatPumpElec",
"ATES",
"HeatBuffer",
"HeatBufferElec",
"CheckValve",
"ColdDemand",
"Compressor",
Expand Down
21 changes: 20 additions & 1 deletion src/mesido/pycml/component_library/milp/heat/heat_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


@add_variables_documentation_automatically
class HeatBuffer(_StorageComponent):
class _HeatBufferComponent(_StorageComponent):
"""
The buffer component is to model milp storage in a tank. This means that we model a tank of hot
water being filled and radiating milp away (heat loss) over the hot surfaces. We assume that the
Expand Down Expand Up @@ -94,4 +94,23 @@ def __init__(self, name, **modifiers):
(self.Heat_loss - self.Stored_heat * self.heat_loss_coeff) / self._nominal_heat_loss
)


@add_variables_documentation_automatically
class HeatBuffer(_HeatBufferComponent):
"""
HeatBuffer component represents the operational behavior of conventional heat buffer.
It sets heat_flow equal to heat_buffer.

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.add_equation((self.Heat_flow - self.Heat_buffer) / self.Heat_nominal)
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
from .elec_heat_source_elec import ElecHeatSourceElec
from .electrolyzer import Electrolyzer
from .gas_heat_source_gas import GasHeatSourceGas
from .heat_buffer_elec import HeatBufferElec

__all__ = [
"AirWaterHeatPumpElec",
"ElecHeatSourceElec",
"Electrolyzer",
"GasHeatSourceGas",
"HeatBufferElec",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from mesido.pycml import Variable
from mesido.pycml.component_library.milp.electricity.electricity_base import ElectricityPort
from mesido.pycml.component_library.milp.heat.heat_buffer import _HeatBufferComponent
from mesido.pycml.pycml_mixin import add_variables_documentation_automatically

from numpy import nan


@add_variables_documentation_automatically
class HeatBufferElec(_HeatBufferComponent):
"""
The HeatBufferElec component represents a heat buffer that can be charged exclusively
using electricity. This asset cannot be charged by water flow through the inlet/outlet pipes

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 = "heat_buffer_elec"
self.elec_power_nominal = nan
self.charging_efficiency = nan

# # Assumption: heat in/out is nonnegative
self.add_variable(ElectricityPort, "ElectricityIn")
self.add_variable(Variable, "Power_elec", min=0.0, nominal=self.elec_power_nominal)
self.add_variable(Variable, "Heat_elec_charging", min=0.0, nominal=self.elec_power_nominal)
#
self.add_equation(((self.ElectricityIn.Power - self.Power_elec) / self.elec_power_nominal))
#
self.add_equation(
(
(self.Power_elec * self.charging_efficiency - self.Heat_elec_charging)
/ self.elec_power_nominal
)
)

# Heat flow balance of the buffer
self.add_equation(
(self.Heat_flow + self.Heat_elec_charging - self.Heat_buffer) / self.Heat_nominal
)

# Buffer can only discharge into heat network. It cannot be charged by heat network
self.Heat_flow.max = 0.0
46 changes: 46 additions & 0 deletions tests/models/simple_buffer/input/timeseries_import_ebuffer.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
DateTime,demand,elec
19-5-2013 22:00,150000,5.00E-05
19-5-2013 23:00,150000,5.00E-05
20-5-2013 00:00,150000,5.00E-05
20-5-2013 01:00,150000,5.00E-05
20-5-2013 02:00,150000,5.00E-05
20-5-2013 03:00,150000,5.00E-05
20-5-2013 04:00,150000,5.00E-05
20-5-2013 05:00,150000,5.00E-05
20-5-2013 06:00,150000,5.00E-05
20-5-2013 07:00,150000,5.00E-05
20-5-2013 08:00,150000,5.00E-05
20-5-2013 09:00,150000,5.00E-05
20-5-2013 10:00,150000,5.00E-05
20-5-2013 11:00,150000,5.00E-05
20-5-2013 12:00,150000,5.00E-05
20-5-2013 13:00,150000,5.00E-05
20-5-2013 14:00,100000,5.00E-05
20-5-2013 15:00,100000,5.00E-05
20-5-2013 16:00,100000,5.00E-05
20-5-2013 17:00,100000,5.00E-05
20-5-2013 18:00,100000,0.00015
20-5-2013 19:00,100000,0.00015
20-5-2013 20:00,100000,0.00015
20-5-2013 21:00,100000,0.00015
20-5-2013 22:00,100000,0.00015
20-5-2013 23:00,100000,0.00015
21-5-2013 00:00,100000,0.00015
21-5-2013 01:00,100000,0.00015
21-5-2013 02:00,100000,0.00015
21-5-2013 03:00,70000,0.00015
21-5-2013 04:00,70000,0.00015
21-5-2013 05:00,70000,0.00015
21-5-2013 06:00,70000,0.00015
21-5-2013 07:00,70000,0.00015
21-5-2013 08:00,70000,0.00015
21-5-2013 09:00,70000,0.00015
21-5-2013 10:00,70000,0.00015
21-5-2013 11:00,70000,0.00015
21-5-2013 12:00,70000,0.00015
21-5-2013 13:00,70000,0.00015
21-5-2013 14:00,70000,0.00015
21-5-2013 15:00,70000,0.00015
21-5-2013 16:00,70000,0.00015
21-5-2013 17:00,70000,0.00015
21-5-2013 18:00,70000,0.00015
Loading
Loading