omotes-simulator-core 0.0.3.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. omotes_simulator_core-0.0.3.dev0.dist-info/LICENSE +621 -0
  2. omotes_simulator_core-0.0.3.dev0.dist-info/METADATA +55 -0
  3. omotes_simulator_core-0.0.3.dev0.dist-info/RECORD +59 -0
  4. omotes_simulator_core-0.0.3.dev0.dist-info/WHEEL +5 -0
  5. omotes_simulator_core-0.0.3.dev0.dist-info/top_level.txt +1 -0
  6. simulator_core/__init__.py +15 -0
  7. simulator_core/__main__.py +21 -0
  8. simulator_core/adapter/__init__.py +16 -0
  9. simulator_core/adapter/presentation/__init__.py +16 -0
  10. simulator_core/adapter/transforms/__init__.py +16 -0
  11. simulator_core/adapter/transforms/esdl_asset_mapper.py +53 -0
  12. simulator_core/adapter/transforms/mappers.py +226 -0
  13. simulator_core/adapter/transforms/string_to_esdl.py +66 -0
  14. simulator_core/adapter/transforms/transform_utils.py +34 -0
  15. simulator_core/entities/__init__.py +16 -0
  16. simulator_core/entities/assets/__init__.py +16 -0
  17. simulator_core/entities/assets/asset_abstract.py +116 -0
  18. simulator_core/entities/assets/asset_defaults.py +68 -0
  19. simulator_core/entities/assets/controller_classes.py +81 -0
  20. simulator_core/entities/assets/demand_cluster.py +117 -0
  21. simulator_core/entities/assets/esdl_asset_object.py +60 -0
  22. simulator_core/entities/assets/junction.py +64 -0
  23. simulator_core/entities/assets/pipe.py +174 -0
  24. simulator_core/entities/assets/production_cluster.py +222 -0
  25. simulator_core/entities/assets/utils.py +154 -0
  26. simulator_core/entities/esdl_object.py +90 -0
  27. simulator_core/entities/heat_network.py +82 -0
  28. simulator_core/entities/network_controller.py +67 -0
  29. simulator_core/entities/simulation_configuration.py +31 -0
  30. simulator_core/entities/utility/__init__.py +16 -0
  31. simulator_core/entities/utility/influxdb_reader.py +129 -0
  32. simulator_core/infrastructure/__init__.py +16 -0
  33. simulator_core/infrastructure/app.py +55 -0
  34. simulator_core/infrastructure/simulation_manager.py +55 -0
  35. simulator_core/infrastructure/utils.py +45 -0
  36. simulator_core/py.typed +0 -0
  37. simulator_core/simulation/__init__.py +18 -0
  38. simulator_core/simulation/mappers/__init__.py +16 -0
  39. simulator_core/simulation/mappers/mappers.py +35 -0
  40. simulator_core/simulation/networksimulation.py +63 -0
  41. simulator_core/solver/__init__.py +15 -0
  42. simulator_core/solver/matrix/__init__.py +15 -0
  43. simulator_core/solver/matrix/core_enum.py +26 -0
  44. simulator_core/solver/matrix/equation_object.py +56 -0
  45. simulator_core/solver/matrix/matrix.py +125 -0
  46. simulator_core/solver/matrix/utility.py +38 -0
  47. simulator_core/solver/network/__init__.py +15 -0
  48. simulator_core/solver/network/assets/__init__.py +15 -0
  49. simulator_core/solver/network/assets/base_asset.py +303 -0
  50. simulator_core/solver/network/assets/base_item.py +63 -0
  51. simulator_core/solver/network/assets/base_node_item.py +67 -0
  52. simulator_core/solver/network/assets/boundary.py +83 -0
  53. simulator_core/solver/network/assets/fall_type.py +225 -0
  54. simulator_core/solver/network/assets/node.py +274 -0
  55. simulator_core/solver/network/assets/production_asset.py +171 -0
  56. simulator_core/solver/network/assets/solver_pipe.py +541 -0
  57. simulator_core/solver/network/network.py +398 -0
  58. simulator_core/solver/solver.py +80 -0
  59. simulator_core/solver/utils/fluid_properties.py +173 -0
@@ -0,0 +1,222 @@
1
+ # Copyright (c) 2023. Deltares & TNO
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ """ProductionCluster class."""
17
+ import uuid
18
+ from typing import Dict
19
+
20
+ from simulator_core.entities.assets.asset_abstract import AssetAbstract
21
+ from simulator_core.entities.assets.asset_defaults import (
22
+ DEFAULT_NODE_HEIGHT,
23
+ DEFAULT_PRESSURE,
24
+ DEFAULT_TEMPERATURE,
25
+ DEFAULT_TEMPERATURE_DIFFERENCE,
26
+ PROPERTY_HEAT_DEMAND,
27
+ PROPERTY_MASSFLOW,
28
+ PROPERTY_PRESSURE_RETURN,
29
+ PROPERTY_PRESSURE_SUPPLY,
30
+ PROPERTY_SET_PRESSURE,
31
+ PROPERTY_TEMPERATURE_RETURN,
32
+ PROPERTY_TEMPERATURE_SUPPLY,
33
+ )
34
+ from simulator_core.entities.assets.esdl_asset_object import EsdlAssetObject
35
+ from simulator_core.entities.assets.utils import (
36
+ heat_demand_and_temperature_to_mass_flow,
37
+ )
38
+ from simulator_core.solver.network.assets.production_asset import ProductionAsset
39
+
40
+
41
+ class ProductionCluster(AssetAbstract):
42
+ """A ProductionCluster represents an asset that produces heat."""
43
+
44
+ thermal_production_required: float | None
45
+ """The thermal production required by the asset [W]."""
46
+
47
+ temperature_supply: float
48
+ """The supply temperature of the asset [K]."""
49
+
50
+ temperature_return: float
51
+ """The return temperature of the asset [K]."""
52
+
53
+ pressure_supply: float
54
+ """The supply pressure of the asset [Pa]."""
55
+
56
+ control_mass_flow: bool
57
+ """Flag to indicate whether the mass flow rate is controlled.
58
+ If True, the mass flow rate is controlled. If False, the mass flow rate is not controlled
59
+ and the pressure is predescribed.
60
+ """
61
+
62
+ controlled_mass_flow: float | None
63
+ """The controlled mass flow of the asset [kg/s]."""
64
+
65
+ def __init__(self, asset_name: str, asset_id: str):
66
+ """Initialize a ProductionCluster object.
67
+
68
+ :param str asset_name: The name of the asset.
69
+ :param str asset_id: The unique identifier of the asset.
70
+ :param PandapipesNet pandapipe_net: Pandapipes network object to register asset to.
71
+ """
72
+ super().__init__(asset_name=asset_name, asset_id=asset_id)
73
+ self.height_m = DEFAULT_NODE_HEIGHT
74
+ # DemandCluster thermal and mass flow specifications
75
+ self.thermal_production_required = None
76
+ self.temperature_supply = DEFAULT_TEMPERATURE
77
+ self.temperature_return = DEFAULT_TEMPERATURE - DEFAULT_TEMPERATURE_DIFFERENCE
78
+ # DemandCluster pressure specifications
79
+ self.pressure_supply = DEFAULT_PRESSURE
80
+ self.control_mass_flow = False
81
+ # Controlled mass flow
82
+ self.controlled_mass_flow = None
83
+ self.solver_asset = ProductionAsset(
84
+ name=uuid.uuid4(),
85
+ pre_scribe_mass_flow=False,
86
+ set_pressure=self.pressure_supply,
87
+ )
88
+
89
+ def add_physical_data(self, esdl_asset: EsdlAssetObject) -> None:
90
+ """Method to add physical data to the asset.
91
+
92
+ :param EsdlAssetObject esdl_asset: The ESDL asset object containing the physical data.
93
+ :return:
94
+ """
95
+
96
+ def _set_supply_temperature(self, temperature_supply: float) -> None:
97
+ """Set the supply temperature of the asset.
98
+
99
+ :param float temperature_supply: The supply temperature of the asset.
100
+ The temperature should be supplied in Kelvin.
101
+ """
102
+ # Set the temperature of the circulation pump mass flow
103
+ self.temperature_supply = temperature_supply
104
+ self.solver_asset.supply_temperature = self.temperature_supply
105
+
106
+ def _set_return_temperature(self, temperature_return: float) -> None:
107
+ """Set the return temperature of the asset.
108
+
109
+ :param float temperature_return: The return temperature of the asset.
110
+ The temperature should be supplied in Kelvin.
111
+ """
112
+ # Set the return temperature of the asset
113
+ self.temperature_return = temperature_return
114
+
115
+ def _set_heat_demand(self, heat_demand: float) -> None:
116
+ """Set the heat demand of the asset.
117
+
118
+ :param float heat_demand: The heat demand of the asset.
119
+ The heat demand should be supplied in Watts.
120
+ """
121
+ # Calculate the mass flow rate
122
+ self.controlled_mass_flow = heat_demand_and_temperature_to_mass_flow(
123
+ thermal_demand=heat_demand,
124
+ temperature_supply=self.temperature_supply,
125
+ temperature_return=self.temperature_return,
126
+ )
127
+
128
+ # Check if the mass flow rate is positive
129
+ if self.controlled_mass_flow < 0.0:
130
+ raise ValueError(
131
+ f"The mass flow rate {self.controlled_mass_flow} of the asset {self.name}"
132
+ + " is negative."
133
+ )
134
+ else:
135
+ # Set the mass flow rate of the control valve
136
+ self.solver_asset.mass_flow_rate_set_point = self.controlled_mass_flow # type: ignore
137
+
138
+ def _set_pressure_or_mass_flow_control(self, pressure_supply: bool) -> None:
139
+ """Set the asset to predescribe either the pressure or the mass flow rate.
140
+
141
+ :param bool pressure_supply: True when the pressure needs to be set
142
+ """
143
+ if pressure_supply:
144
+ self.solver_asset.pre_scribe_mass_flow = False # type: ignore
145
+ else:
146
+ self.solver_asset.pre_scribe_mass_flow = True # type: ignore
147
+
148
+ def set_pressure_supply(self, pressure_supply: float) -> None:
149
+ """Set the supply pressure of the asset.
150
+
151
+ :param float pressure_supply: The supply pressure of the asset.
152
+ The pressure should be supplied in Pascal.
153
+ """
154
+ # Check if the pressure is positive
155
+ if pressure_supply < 0.0:
156
+ raise ValueError(
157
+ f"The pressure {pressure_supply} of the asset {self.name} can not be negative."
158
+ )
159
+ # Set the supply pressure of the asset
160
+ self.pressure_supply = pressure_supply
161
+ # Set the pressure of the solver asset
162
+ self.solver_asset.set_pressure = self.pressure_supply # type: ignore
163
+
164
+ def set_setpoints(self, setpoints: Dict) -> None:
165
+ """Set the setpoints of the asset.
166
+
167
+ :param Dict setpoints: The setpoints of the asset in a dictionary,
168
+ as "property_name": value pairs.
169
+
170
+ """
171
+ # Default keys required
172
+ necessary_setpoints = {
173
+ PROPERTY_TEMPERATURE_SUPPLY,
174
+ PROPERTY_TEMPERATURE_RETURN,
175
+ PROPERTY_HEAT_DEMAND,
176
+ PROPERTY_SET_PRESSURE,
177
+ }
178
+ # Dict to set
179
+ setpoints_set = set(setpoints.keys())
180
+ # Check if all setpoints are in the setpoints
181
+ if necessary_setpoints.issubset(setpoints_set):
182
+ # Set the setpoints
183
+ self._set_pressure_or_mass_flow_control(setpoints[PROPERTY_SET_PRESSURE])
184
+ self._set_supply_temperature(setpoints[PROPERTY_TEMPERATURE_SUPPLY])
185
+ self._set_return_temperature(setpoints[PROPERTY_TEMPERATURE_RETURN])
186
+ self._set_heat_demand(setpoints[PROPERTY_HEAT_DEMAND])
187
+ else:
188
+ # Print missing setpoints
189
+ raise ValueError(
190
+ f"The setpoints {necessary_setpoints.difference(setpoints_set)} are missing."
191
+ )
192
+
193
+ def update(self) -> None:
194
+ """Update the asset properties to the results from the previous (timestep) simulation.
195
+
196
+ Sets the values of the supply temperature, return temperature and heat demand
197
+ to the values of the previous simulation. In addition, the mass flow rate is set
198
+ to the value of the previous simulation.
199
+ """
200
+
201
+ def write_to_output(self) -> None:
202
+ """Write the output of the asset to the output list.
203
+
204
+ The output list is a list of dictionaries, where each dictionary
205
+ represents the output of its asset for a specific timestep.
206
+
207
+ The output of the asset is a dictionary with the following keys:
208
+ - PROPERTY_HEAT_DEMAND: The heat demand of the asset.
209
+ - PROPERTY_TEMPERATURE_SUPPLY: The supply temperature of the asset.
210
+ - PROPERTY_TEMPERATURE_RETURN: The return temperature of the asset.
211
+ - PROPERTY_PRESSURE_SUPPLY: The supply pressure of the asset.
212
+ - PROPERTY_PRESSURE_RETURN: The return pressure of the asset.
213
+ - PROPERTY_MASSFLOW: The mass flow rate of the asset.
214
+ """
215
+ output_dict = {
216
+ PROPERTY_MASSFLOW: self.solver_asset.get_mass_flow_rate(1),
217
+ PROPERTY_PRESSURE_SUPPLY: self.solver_asset.get_pressure(0),
218
+ PROPERTY_PRESSURE_RETURN: self.solver_asset.get_pressure(1),
219
+ PROPERTY_TEMPERATURE_SUPPLY: self.solver_asset.get_temperature(0),
220
+ PROPERTY_TEMPERATURE_RETURN: self.solver_asset.get_temperature(1),
221
+ }
222
+ self.output.append(output_dict)
@@ -0,0 +1,154 @@
1
+ # Copyright (c) 2023. Deltares & TNO
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ """Utility functions for assets."""
17
+ from enum import IntEnum
18
+
19
+ from pandapipes import pandapipesNet
20
+ import numpy as np
21
+ from simulator_core.entities.assets.esdl_asset_object import EsdlAssetObject
22
+ from typing import List, Tuple
23
+ from simulator_core.solver.utils.fluid_properties import fluid_props
24
+
25
+
26
+ def heat_demand_and_temperature_to_mass_flow(
27
+ thermal_demand: float,
28
+ temperature_supply: float,
29
+ temperature_return: float
30
+ ) -> float:
31
+ """Calculate the mass flow rate that is required to meet the thermal demand.
32
+
33
+ :param float thermal_demand: The thermal demand of the asset. The thermal demand should be
34
+ supplied in Watts.
35
+ :param float temperature_supply: The temperature that the asset delivers to the "to_junction".
36
+ The temperature should be supplied in Kelvin. The supply temperature is used to calculate
37
+ the specific heat capacity of the fluid.
38
+ :param float temperature_return: The temperature that the asset receives from the
39
+ "from_junction". The temperature should be supplied in Kelvin.
40
+ """
41
+ heat_capacity = fluid_props.get_heat_capacity(
42
+ (temperature_return + temperature_supply) / 2)
43
+ return thermal_demand / ((temperature_supply - temperature_return) * float(heat_capacity))
44
+
45
+
46
+ def mass_flow_and_temperature_to_heat_demand(
47
+ temperature_supply: float,
48
+ temperature_return: float,
49
+ mass_flow: float,
50
+ ) -> float:
51
+ """Calculate the thermal demand that is met by the mass flow rate.
52
+
53
+ :param float temperature_supply: The temperature that the asset delivers to the "to_junction".
54
+ The temperature should be supplied in Kelvin. The supply temperature is used to calculate
55
+ the specific heat capacity of the fluid.
56
+ :param float temperature_return: The temperature that the asset receives from the
57
+ "from_junction". The temperature should be supplied in Kelvin.
58
+ :param float mass_flow: The mass flow rate that is used to meet the thermal demand. The mass
59
+ flow rate should be supplied in kg/s.
60
+ """
61
+ internal_energy1 = fluid_props.get_ie(temperature_supply)
62
+ internal_energy2 = fluid_props.get_ie(temperature_return)
63
+ return mass_flow * (internal_energy1 - internal_energy2)
64
+
65
+
66
+ def get_thermal_conductivity_table(esdl_asset: EsdlAssetObject) -> Tuple[List[float], List[float]]:
67
+ """Retrieve the thermal conductivity table of the asset.
68
+
69
+ :param EsdlAssetObject esdl_asset: The asset of which the heat transfer table should be
70
+ retrieved.
71
+ :return: The diameters and thermal conductivities of the asset in a tuple with the diameters
72
+ as the first element and the thermal conductivities as the second element. Each element
73
+ represents a layer of the asset.
74
+ """
75
+ # create heat transfer table
76
+ layer_thicknesses = []
77
+ diameters = []
78
+ heat_coef = []
79
+ if esdl_asset.esdl_asset.material is not None:
80
+ if hasattr(esdl_asset.esdl_asset.material, "component"):
81
+ material_object = esdl_asset.esdl_asset.material.component
82
+ elif hasattr(esdl_asset.esdl_asset.material, "reference"):
83
+ material_object = esdl_asset.esdl_asset.material.reference.component
84
+ else:
85
+ raise NotImplementedError("Unknown material type or reference.")
86
+ # Append initial internal diameter
87
+ diameters.append(esdl_asset.esdl_asset.innerDiameter)
88
+ # Loop over material object
89
+ for layer in material_object:
90
+ layer_thicknesses.append(layer.layerWidth * 2)
91
+ diameters.append(sum(layer_thicknesses) + esdl_asset.esdl_asset.innerDiameter)
92
+ heat_coef.append(layer.matter.thermalConductivity)
93
+ return diameters, heat_coef
94
+
95
+
96
+ def calculate_inverse_heat_transfer_coefficient(
97
+ inner_diameter: np.ndarray, outer_diameter: np.ndarray, thermal_conductivity: np.ndarray
98
+ ) -> np.ndarray:
99
+ """Calculate the inverse heat transfer coefficient of a pipe.
100
+
101
+ :param inner_diameter: Inner diameter of the pipe in m
102
+ :param outer_diameter: Outer diameter of the pipe in m
103
+ :param thermal_conductivity: Thermal conductivity of the pipe material in W/(m K)
104
+ :return: Inverse heat transfer coefficient in W/(m^2 K)
105
+ """
106
+ return np.array((inner_diameter * np.log(outer_diameter / inner_diameter))
107
+ / (2.0 * thermal_conductivity))
108
+
109
+
110
+ def mass_flow_to_volume_flow(
111
+ mass_flow_rate: float,
112
+ temperature_fluid: float,
113
+ pandapipes_net: pandapipesNet,
114
+ ) -> float:
115
+ """Calculate the volume flow rate from mass flow rate.
116
+
117
+ :param float mass_flow_rate: the mass flow rate in kg/s
118
+ :param float temperature_fluid: the fluid temperature in K
119
+ :param pandapipesNet pandapipes_net: The pandapipes network used to calculate
120
+ the specific density.
121
+
122
+ :return float volume_flow rate: the volume flow rate in m3/s
123
+ """
124
+ density_fluid = pandapipes_net.fluid.get_density(temperature_fluid)
125
+ if (density_fluid == 0) | (density_fluid is None):
126
+ raise ValueError("The density of the fluid is zero or None.")
127
+ volume_flowrate = mass_flow_rate / density_fluid
128
+ return float(volume_flowrate)
129
+
130
+
131
+ def volume_flow_to_mass_flow(
132
+ volume_flowrate: float,
133
+ temperature_fluid: float,
134
+ pandapipes_net: pandapipesNet,
135
+ ) -> float:
136
+ """Calculate the mass flowrate from volume flowrate.
137
+
138
+ :param float volume_flowrate: the volume flowrate in m3/s
139
+ :param float temperature_fluid: the fluid temperature in K
140
+ :param pandapipesNet net: The pandapipes network used to calculate the specific density.
141
+
142
+ :return float mass_flowrate: the mass flowrate in kg/s
143
+ """
144
+ density_fluid = pandapipes_net.fluid.get_density(temperature_fluid)
145
+ mass_flowrate = volume_flowrate * density_fluid
146
+
147
+ return float(mass_flowrate)
148
+
149
+
150
+ class Port(IntEnum):
151
+ """Simple enumeration class to set if it is In (from) or out(to) port of asset."""
152
+
153
+ In = 0
154
+ Out = 1
@@ -0,0 +1,90 @@
1
+ # Copyright (c) 2023. Deltares & TNO
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ """Esdl asset wrapper class."""
17
+
18
+ import logging
19
+ from typing import List, Tuple
20
+
21
+ from esdl import InPort, OutPort
22
+ from esdl.esdl_handler import EnergySystemHandler
23
+
24
+ from simulator_core.adapter.transforms.string_to_esdl import StringEsdlAssetMapper
25
+ from simulator_core.entities.assets.esdl_asset_object import EsdlAssetObject
26
+ from simulator_core.entities.assets.utils import Port
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ class EsdlObject:
32
+ """EsdlObject class is a wrapper around PyEsdl."""
33
+
34
+ energy_system_handler: EnergySystemHandler
35
+
36
+ def __init__(self, esdl_energysystem_handler: EnergySystemHandler) -> None:
37
+ """
38
+ Constructor for EsdlObject.
39
+
40
+ :param esdl_energysystem: PyEsdl EnergySystem object
41
+ """
42
+ self.energy_system_handler = esdl_energysystem_handler
43
+
44
+ def __repr__(self) -> str:
45
+ """Returns a string describing the esdl file object."""
46
+ return str(self.energy_system_handler)
47
+
48
+ def get_all_assets_of_type(self, esdl_asset_type: str) -> list[EsdlAssetObject]:
49
+ """
50
+ Returns a list of all the esdl assets of the specified type in the esdl file.
51
+
52
+ If the type is not found an empty list is returned.
53
+ :param esdl_asset_type: str of the asset type assets need to be gathered.
54
+ """
55
+ output_list = []
56
+ for asset_type in StringEsdlAssetMapper().to_esdl(esdl_asset_type):
57
+ output_list += [
58
+ EsdlAssetObject(asset)
59
+ for asset in self.energy_system_handler.get_all_instances_of_type(asset_type)
60
+ ]
61
+ return output_list
62
+
63
+ def get_connected_assets(self, asset_id: str, port: Port) -> List[Tuple[str, Port]]:
64
+ """Method to get the id's of connected assets from the esdl.
65
+
66
+ This returns a list of list with the connected asset id and the port to which it is
67
+ connected to the asset.
68
+ First it is set if the request is an in or outport. Then the connected ports to the asset
69
+ are added to a list. In the final step all assets elong to the ports are listed together
70
+ with the type of port they are connected to.
71
+
72
+ :param str id: id of the asset for which we want to know the connected assets
73
+ :param Port port: port for which the connected assets need to be returned.
74
+ :return: List of list which the id of the connected assets and the connected port.
75
+ """
76
+ # TODO 1. Add support for components with multiple in and outports, like heat exchanger
77
+ # TODO 2. What if it is connected to a joint?
78
+ connected_assets = []
79
+ esdl_asset = self.energy_system_handler.get_by_id(asset_id)
80
+
81
+ type_port = OutPort if port == Port.Out else InPort
82
+ connected_port_ids = []
83
+ for esdl_port in esdl_asset.port:
84
+ if isinstance(esdl_port, type_port):
85
+ connected_port_ids = esdl_port.connectedTo
86
+ break
87
+ for connected_port_id in connected_port_ids:
88
+ connected_port_type = Port.Out if isinstance(connected_port_id, OutPort) else Port.In
89
+ connected_assets.append((connected_port_id.energyasset.id, connected_port_type))
90
+ return connected_assets
@@ -0,0 +1,82 @@
1
+ # Copyright (c) 2023. Deltares & TNO
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ """HeatNetwork entity class."""
17
+ import datetime
18
+ from typing import Callable, List, Tuple
19
+
20
+ import pandas as pd
21
+
22
+ from simulator_core.entities.assets.asset_abstract import AssetAbstract
23
+ from simulator_core.entities.assets.junction import Junction
24
+ from simulator_core.solver.network.network import Network
25
+ from simulator_core.solver.solver import Solver
26
+
27
+
28
+ class HeatNetwork:
29
+ """Class to store information on the heat network."""
30
+
31
+ def __init__(self, conversion_factory: Callable[[Network], Tuple[List[AssetAbstract],
32
+ List[Junction]]]) -> None:
33
+ """Constructor of heat network class.
34
+
35
+ :param conversion_factory: method to convert the esdl network to pandapipes
36
+ assets and junctions and returns list of both
37
+ """
38
+ self.network = Network()
39
+ self.assets, self.junctions = conversion_factory(self.network)
40
+ self.solver = Solver(self.network)
41
+
42
+ def run_time_step(self, time: datetime.datetime, controller_input: dict) -> None:
43
+ """Method to simulate a time step.
44
+
45
+ It first sets the controller input to the assets and then simulates the time step.
46
+
47
+ :param float time: Timestep for which to simulate the model
48
+ :param dict controller_input: Dict specifying the heat demand for the different assets.
49
+ :return: None
50
+ """
51
+ for py_asset in self.assets:
52
+ if py_asset.asset_id in controller_input:
53
+ py_asset.set_setpoints(controller_input[py_asset.asset_id])
54
+ self.solver.solve()
55
+
56
+ def plot_network(self) -> None:
57
+ """Method to plot the network.
58
+
59
+ plots the network in a simple plot all junctions are translated to a circle.
60
+ :return:
61
+ """
62
+ def store_output(self) -> None:
63
+ """Method to store the output data.
64
+
65
+ This method takes the data from the pandapipes dataframe and stores it into our own
66
+ dataframe. This is needed since we have the possibility to redo a timestep when results are
67
+ not converged for the input of the controller.
68
+ :return: None
69
+ """
70
+ for py_asset in self.assets:
71
+ py_asset.write_to_output()
72
+
73
+ def gather_output(self) -> pd.DataFrame:
74
+ """Method to gather output of all assets and return it as a dict.
75
+
76
+ :return: Dict with for all asset a dict with all outputs.
77
+ """
78
+ # Todo what do we do with the junction output do we need it?
79
+ result = pd.DataFrame()
80
+ for py_asset in self.assets:
81
+ result = pd.concat([result, py_asset.get_timeseries()], axis=1)
82
+ return result
@@ -0,0 +1,67 @@
1
+ # Copyright (c) 2023. Deltares & TNO
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ """NetworkController entity."""
17
+ import datetime
18
+
19
+ from simulator_core.entities.assets.asset_defaults import (PROPERTY_TEMPERATURE_SUPPLY,
20
+ PROPERTY_TEMPERATURE_RETURN,
21
+ PROPERTY_HEAT_DEMAND,
22
+ PROPERTY_SET_PRESSURE)
23
+ from simulator_core.entities.assets.controller_classes import ControllerSource, ControllerConsumer
24
+ from typing import List
25
+
26
+
27
+ class NetworkController:
28
+ """Class to store the network controller."""
29
+
30
+ def __init__(self, consumers: List[ControllerConsumer], sources: List[ControllerSource]) \
31
+ -> None:
32
+ """Constructor for controller for a heat network."""
33
+ self.sources = sources
34
+ self.consumers = consumers
35
+
36
+ def run_time_step(self, time: datetime.datetime) -> dict:
37
+ """Method to get the controller inputs for the network.
38
+
39
+ :param float time: Time step for which to run the controller.
40
+ :return: dict with the key the asset id and the heat demand for that asset.
41
+ """
42
+ # TODO add also the possibility to return mass flow rate instead of heat demand.
43
+
44
+ controller_input = {}
45
+ for consumer in self.consumers:
46
+ controller_input[consumer.id] = {PROPERTY_HEAT_DEMAND: consumer.get_heat_demand(time),
47
+ PROPERTY_TEMPERATURE_RETURN:
48
+ consumer.temperature_return,
49
+ PROPERTY_TEMPERATURE_SUPPLY:
50
+ consumer.temperature_supply}
51
+ for source in self.sources:
52
+ controller_input[source.id] = {PROPERTY_HEAT_DEMAND: self.get_total_demand(time)
53
+ / len(self.sources),
54
+ PROPERTY_TEMPERATURE_RETURN: source.temperature_return,
55
+ PROPERTY_TEMPERATURE_SUPPLY: source.temperature_supply,
56
+ PROPERTY_SET_PRESSURE: False}
57
+ # setting the first source to set the pressure for now.
58
+ controller_input[self.sources[0].id][PROPERTY_SET_PRESSURE] = True
59
+ return controller_input
60
+
61
+ def get_total_demand(self, time: datetime.datetime) -> float:
62
+ """Method to get the total heat demand of the network."""
63
+ return sum([consumer.get_heat_demand(time) for consumer in self.consumers])
64
+
65
+ def get_total_supply(self) -> float:
66
+ """Method to get the total heat supply of the network."""
67
+ return float(sum([source.power for source in self.sources]))
@@ -0,0 +1,31 @@
1
+ # Copyright (c) 2023. Deltares & TNO
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ """Configuration parameters for the simulation that are not included in the ESDL."""
17
+
18
+ from dataclasses import dataclass
19
+ from datetime import datetime
20
+ import uuid
21
+
22
+
23
+ @dataclass
24
+ class SimulationConfiguration:
25
+ """Class to store configuration parameters of the simulations."""
26
+
27
+ simulation_id: uuid.UUID
28
+ name: str
29
+ timestep: int
30
+ start: datetime
31
+ stop: datetime
@@ -0,0 +1,16 @@
1
+ # Copyright (c) 2023. Deltares & TNO
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ """__init__.py file for initialization code."""