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.
- omotes_simulator_core-0.0.3.dev0.dist-info/LICENSE +621 -0
- omotes_simulator_core-0.0.3.dev0.dist-info/METADATA +55 -0
- omotes_simulator_core-0.0.3.dev0.dist-info/RECORD +59 -0
- omotes_simulator_core-0.0.3.dev0.dist-info/WHEEL +5 -0
- omotes_simulator_core-0.0.3.dev0.dist-info/top_level.txt +1 -0
- simulator_core/__init__.py +15 -0
- simulator_core/__main__.py +21 -0
- simulator_core/adapter/__init__.py +16 -0
- simulator_core/adapter/presentation/__init__.py +16 -0
- simulator_core/adapter/transforms/__init__.py +16 -0
- simulator_core/adapter/transforms/esdl_asset_mapper.py +53 -0
- simulator_core/adapter/transforms/mappers.py +226 -0
- simulator_core/adapter/transforms/string_to_esdl.py +66 -0
- simulator_core/adapter/transforms/transform_utils.py +34 -0
- simulator_core/entities/__init__.py +16 -0
- simulator_core/entities/assets/__init__.py +16 -0
- simulator_core/entities/assets/asset_abstract.py +116 -0
- simulator_core/entities/assets/asset_defaults.py +68 -0
- simulator_core/entities/assets/controller_classes.py +81 -0
- simulator_core/entities/assets/demand_cluster.py +117 -0
- simulator_core/entities/assets/esdl_asset_object.py +60 -0
- simulator_core/entities/assets/junction.py +64 -0
- simulator_core/entities/assets/pipe.py +174 -0
- simulator_core/entities/assets/production_cluster.py +222 -0
- simulator_core/entities/assets/utils.py +154 -0
- simulator_core/entities/esdl_object.py +90 -0
- simulator_core/entities/heat_network.py +82 -0
- simulator_core/entities/network_controller.py +67 -0
- simulator_core/entities/simulation_configuration.py +31 -0
- simulator_core/entities/utility/__init__.py +16 -0
- simulator_core/entities/utility/influxdb_reader.py +129 -0
- simulator_core/infrastructure/__init__.py +16 -0
- simulator_core/infrastructure/app.py +55 -0
- simulator_core/infrastructure/simulation_manager.py +55 -0
- simulator_core/infrastructure/utils.py +45 -0
- simulator_core/py.typed +0 -0
- simulator_core/simulation/__init__.py +18 -0
- simulator_core/simulation/mappers/__init__.py +16 -0
- simulator_core/simulation/mappers/mappers.py +35 -0
- simulator_core/simulation/networksimulation.py +63 -0
- simulator_core/solver/__init__.py +15 -0
- simulator_core/solver/matrix/__init__.py +15 -0
- simulator_core/solver/matrix/core_enum.py +26 -0
- simulator_core/solver/matrix/equation_object.py +56 -0
- simulator_core/solver/matrix/matrix.py +125 -0
- simulator_core/solver/matrix/utility.py +38 -0
- simulator_core/solver/network/__init__.py +15 -0
- simulator_core/solver/network/assets/__init__.py +15 -0
- simulator_core/solver/network/assets/base_asset.py +303 -0
- simulator_core/solver/network/assets/base_item.py +63 -0
- simulator_core/solver/network/assets/base_node_item.py +67 -0
- simulator_core/solver/network/assets/boundary.py +83 -0
- simulator_core/solver/network/assets/fall_type.py +225 -0
- simulator_core/solver/network/assets/node.py +274 -0
- simulator_core/solver/network/assets/production_asset.py +171 -0
- simulator_core/solver/network/assets/solver_pipe.py +541 -0
- simulator_core/solver/network/network.py +398 -0
- simulator_core/solver/solver.py +80 -0
- simulator_core/solver/utils/fluid_properties.py +173 -0
|
@@ -0,0 +1,398 @@
|
|
|
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
|
+
"""Module containing the network class."""
|
|
16
|
+
import uuid
|
|
17
|
+
|
|
18
|
+
from simulator_core.solver.network.assets.base_asset import BaseAsset
|
|
19
|
+
from simulator_core.solver.network.assets.boundary import BaseBoundary
|
|
20
|
+
from simulator_core.solver.network.assets.fall_type import FallType
|
|
21
|
+
from simulator_core.solver.network.assets.node import Node
|
|
22
|
+
from simulator_core.solver.network.assets.production_asset import ProductionAsset
|
|
23
|
+
from simulator_core.solver.network.assets.solver_pipe import SolverPipe
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Network:
|
|
27
|
+
"""Class to store a network consisting of nodes and assets."""
|
|
28
|
+
|
|
29
|
+
str_to_class_dict = {
|
|
30
|
+
"Boundary": BaseBoundary,
|
|
31
|
+
"Asset": BaseAsset,
|
|
32
|
+
"Fall": FallType,
|
|
33
|
+
"Production": ProductionAsset,
|
|
34
|
+
"Pipe": SolverPipe,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def __init__(self) -> None:
|
|
38
|
+
"""Constructor of the network class.
|
|
39
|
+
|
|
40
|
+
Initializes the class properties and loads the fluid properties.
|
|
41
|
+
"""
|
|
42
|
+
self.assets: dict[uuid.UUID, BaseAsset] = {}
|
|
43
|
+
self.nodes: dict[uuid.UUID, Node] = {}
|
|
44
|
+
|
|
45
|
+
def add_asset(self, asset_type: str, name: uuid.UUID | None = None) -> uuid.UUID:
|
|
46
|
+
"""Method to add an asset to the network.
|
|
47
|
+
|
|
48
|
+
This method creates and asset of the given type.
|
|
49
|
+
If the type does not exist a ValueError is raised.
|
|
50
|
+
The unique id which is created for this asset is returned.
|
|
51
|
+
:param name: Unique name of the asset, if not given a random uuid is created.
|
|
52
|
+
:param str asset_type: The type of asset to be added
|
|
53
|
+
:return: Unique id of the asset.
|
|
54
|
+
"""
|
|
55
|
+
if asset_type not in self.str_to_class_dict:
|
|
56
|
+
raise ValueError(asset_type + " not recognized.")
|
|
57
|
+
if name is None:
|
|
58
|
+
name = uuid.uuid4()
|
|
59
|
+
self.assets[name] = self.str_to_class_dict[asset_type](name)
|
|
60
|
+
return name
|
|
61
|
+
|
|
62
|
+
def add_existing_asset(self, asset: BaseAsset) -> uuid.UUID:
|
|
63
|
+
"""Method to add an existing asset to the network.
|
|
64
|
+
|
|
65
|
+
This method adds an existing asset to the network. It checks if the asset already exists.
|
|
66
|
+
If it does a ValueError is raised.
|
|
67
|
+
:param BaseAsset asset: The asset to be added to the network.
|
|
68
|
+
:return: Unique id of the asset.
|
|
69
|
+
"""
|
|
70
|
+
if asset.name in self.assets:
|
|
71
|
+
raise ValueError(f"{asset.name} already exists in network.")
|
|
72
|
+
self.assets[asset.name] = asset
|
|
73
|
+
return asset.name
|
|
74
|
+
|
|
75
|
+
def _connect_single_asset_at_node(
|
|
76
|
+
self,
|
|
77
|
+
asset_id_connected: uuid.UUID,
|
|
78
|
+
connection_point_connected: int,
|
|
79
|
+
asset_id_unconnected: uuid.UUID,
|
|
80
|
+
connection_point_unconnected: int,
|
|
81
|
+
) -> uuid.UUID:
|
|
82
|
+
"""Method to connect a single asset to a node.
|
|
83
|
+
|
|
84
|
+
This method connects the unconnected asset to the node of the connected asset.
|
|
85
|
+
The id of the node is returned.
|
|
86
|
+
|
|
87
|
+
:param asset_id_connected: id of the connected asset
|
|
88
|
+
:type asset_id_connected: uuid.UUID
|
|
89
|
+
:param connection_point_connected: Connection point of the connected asset
|
|
90
|
+
:type connection_point_connected: int
|
|
91
|
+
:param asset_id_unconnected: id of the unconnected asset
|
|
92
|
+
:type asset_id_unconnected: uuid.UUID
|
|
93
|
+
:param connection_point_unconnected: Connection point of the unconnected asset
|
|
94
|
+
:type connection_point_unconnected: int
|
|
95
|
+
:return: id of node connecting the two assets
|
|
96
|
+
"""
|
|
97
|
+
# Get the node of the connected asset
|
|
98
|
+
node = self.assets[asset_id_connected].get_connected_node(
|
|
99
|
+
connection_point=connection_point_connected
|
|
100
|
+
)
|
|
101
|
+
# Connect the asset to the node
|
|
102
|
+
self.assets[asset_id_unconnected].connect_node(
|
|
103
|
+
connection_point=connection_point_unconnected, node=node
|
|
104
|
+
)
|
|
105
|
+
# Connect the node to the asset
|
|
106
|
+
node.connect_asset(
|
|
107
|
+
self.assets[asset_id_unconnected], connection_point=connection_point_unconnected
|
|
108
|
+
)
|
|
109
|
+
return node.name
|
|
110
|
+
|
|
111
|
+
def _connect_both_assets_at_node(
|
|
112
|
+
self,
|
|
113
|
+
asset1_id: uuid.UUID,
|
|
114
|
+
connection_point_1: int,
|
|
115
|
+
asset2_id: uuid.UUID,
|
|
116
|
+
connection_point_2: int,
|
|
117
|
+
) -> uuid.UUID:
|
|
118
|
+
"""Method to connect to assets at the given connection points.
|
|
119
|
+
|
|
120
|
+
Connects the two assets to a new node and returns the id of the node.
|
|
121
|
+
|
|
122
|
+
:param asset1_id: id of first asset to be connected
|
|
123
|
+
:type asset1_id: uuid.UUID
|
|
124
|
+
:param connection_point_1: Connection point of first asset to be connected
|
|
125
|
+
:type connection_point_1: int
|
|
126
|
+
:param asset2_id: id of second asset to be connected
|
|
127
|
+
:type asset2_id: uuid.UUID
|
|
128
|
+
:param connection_point_2: Connection point of second asset to be connected
|
|
129
|
+
:type connection_point_2: int
|
|
130
|
+
:return: id of node connecting the two assets
|
|
131
|
+
"""
|
|
132
|
+
# Create a new node
|
|
133
|
+
node_id = uuid.uuid4()
|
|
134
|
+
self.nodes[node_id] = Node(name=node_id)
|
|
135
|
+
# Connect the assets to the node
|
|
136
|
+
for asset_id, connection_point in [
|
|
137
|
+
(asset1_id, connection_point_1),
|
|
138
|
+
(asset2_id, connection_point_2),
|
|
139
|
+
]:
|
|
140
|
+
self.assets[asset_id].connect_node(
|
|
141
|
+
connection_point=connection_point, node=self.nodes[node_id]
|
|
142
|
+
)
|
|
143
|
+
self.nodes[node_id].connect_asset(
|
|
144
|
+
asset=self.assets[asset_id], connection_point=connection_point
|
|
145
|
+
)
|
|
146
|
+
return node_id
|
|
147
|
+
|
|
148
|
+
def _connect_both_assets_and_replace_node(
|
|
149
|
+
self,
|
|
150
|
+
asset1_id: uuid.UUID,
|
|
151
|
+
connection_point_1: int,
|
|
152
|
+
asset2_id: uuid.UUID,
|
|
153
|
+
connection_point_2: int,
|
|
154
|
+
) -> uuid.UUID:
|
|
155
|
+
"""Method to connect to assets at the given connection points.
|
|
156
|
+
|
|
157
|
+
Connects the two assets to a new node and returns the id of the node.
|
|
158
|
+
|
|
159
|
+
:param asset1_id: id of first asset to be connected
|
|
160
|
+
:type asset1_id: uuid.UUID
|
|
161
|
+
:param connection_point_1: Connection point of first asset to be connected
|
|
162
|
+
:type connection_point_1: int
|
|
163
|
+
:param asset2_id: id of second asset to be connected
|
|
164
|
+
:type asset2_id: uuid.UUID
|
|
165
|
+
:param connection_point_2: Connection point of second asset to be connected
|
|
166
|
+
:type connection_point_2: int
|
|
167
|
+
:return: id of node connecting the two assets
|
|
168
|
+
"""
|
|
169
|
+
# Retrieve the nodes of the assets
|
|
170
|
+
node1 = self.assets[asset1_id].get_connected_node(connection_point=connection_point_1)
|
|
171
|
+
node2 = self.assets[asset2_id].get_connected_node(connection_point=connection_point_2)
|
|
172
|
+
# Check if the nodes are the same, if so return the name of the node.
|
|
173
|
+
if node1 == node2:
|
|
174
|
+
return node1.name
|
|
175
|
+
else:
|
|
176
|
+
# Both nodes are different, connect the two nodes and remove the second node.
|
|
177
|
+
# First connect all assets connected to the second node to the first node.
|
|
178
|
+
for connected_comp, connection_point in node2.get_connected_assets():
|
|
179
|
+
# Disconnect the connected component from the second node
|
|
180
|
+
connected_comp.disconnect_node(connection_point=connection_point)
|
|
181
|
+
# Connect the connected component to the first node
|
|
182
|
+
self.connect_assets(
|
|
183
|
+
asset1_id=connected_comp.name,
|
|
184
|
+
connection_point_1=connection_point,
|
|
185
|
+
asset2_id=asset1_id,
|
|
186
|
+
connection_point_2=connection_point_1,
|
|
187
|
+
)
|
|
188
|
+
# Remove the second node from the network
|
|
189
|
+
del self.nodes[node2.name]
|
|
190
|
+
# Finally connect the first asset to the second asset
|
|
191
|
+
self.assets[asset2_id].disconnect_node(connection_point=connection_point_2)
|
|
192
|
+
self.assets[asset2_id].connect_node(connection_point=connection_point_2, node=node1)
|
|
193
|
+
return node1.name
|
|
194
|
+
|
|
195
|
+
def connect_assets(
|
|
196
|
+
self,
|
|
197
|
+
asset1_id: uuid.UUID,
|
|
198
|
+
connection_point_1: int,
|
|
199
|
+
asset2_id: uuid.UUID,
|
|
200
|
+
connection_point_2: int,
|
|
201
|
+
) -> uuid.UUID:
|
|
202
|
+
"""Method to connect to assets at the given connection points.
|
|
203
|
+
|
|
204
|
+
This method connects the two assets if the exists. It checks if they already are connected.
|
|
205
|
+
If not a new node is created. Otherwise, both are connected to the existing node.
|
|
206
|
+
The id of the node connecting the two is returned
|
|
207
|
+
|
|
208
|
+
:param asset1_id: id of first asset to be connected
|
|
209
|
+
:param connection_point_1: Connection point of first asset to be connected
|
|
210
|
+
:param asset2_id: id of second asset to be connected
|
|
211
|
+
:param connection_point_2: Connection point of second asset to be connected
|
|
212
|
+
:return: id of node connecting the two assets
|
|
213
|
+
"""
|
|
214
|
+
# Check if both assets exist; if not raise a ValueError
|
|
215
|
+
for asset_id in [asset1_id, asset2_id]:
|
|
216
|
+
self.exists_asset(asset_id=asset_id)
|
|
217
|
+
# Create boolean list with for each asset wether the connection point is connected
|
|
218
|
+
connected_1 = self.assets[asset1_id].is_connected(connection_point=connection_point_1)
|
|
219
|
+
connected_2 = self.assets[asset2_id].is_connected(connection_point=connection_point_2)
|
|
220
|
+
# Use the connect_assets_decision_tree to connect the assets
|
|
221
|
+
return self.connect_assets_decision_tree(
|
|
222
|
+
connected_1=connected_1,
|
|
223
|
+
connected_2=connected_2,
|
|
224
|
+
asset1_id=asset1_id,
|
|
225
|
+
connection_point_1=connection_point_1,
|
|
226
|
+
asset2_id=asset2_id,
|
|
227
|
+
connection_point_2=connection_point_2,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
def connect_assets_decision_tree(
|
|
231
|
+
self,
|
|
232
|
+
connected_1: bool,
|
|
233
|
+
connected_2: bool,
|
|
234
|
+
asset1_id: uuid.UUID,
|
|
235
|
+
connection_point_1: int,
|
|
236
|
+
asset2_id: uuid.UUID,
|
|
237
|
+
connection_point_2: int,
|
|
238
|
+
) -> uuid.UUID:
|
|
239
|
+
"""Method to connect to assets at the given connection points.
|
|
240
|
+
|
|
241
|
+
The method uses a decision tree to connect the assets. The decision tree is as follows:
|
|
242
|
+
1. If both assets are not connected, create a new node and connect everything.
|
|
243
|
+
2. If asset 1 is connected and asset 2 is not, connect the node of asset 1 to asset 2.
|
|
244
|
+
3. If asset 2 is connected and asset 1 is not, connect the node of asset 2 to asset 1.
|
|
245
|
+
4. If both assets are connected, check if they are connected to the same node, otherwise
|
|
246
|
+
connect the two nodes and remove the second node.
|
|
247
|
+
5. If none of the above is true raise a NotImplementedError.
|
|
248
|
+
|
|
249
|
+
:param connected_1: Boolean indicating if asset 1 is connected
|
|
250
|
+
:param connected_2: Boolean indicating if asset 2 is connected
|
|
251
|
+
:param asset1_id: id of first asset to be connected
|
|
252
|
+
:param connection_point_1: Connection point of first asset to be connected
|
|
253
|
+
:param asset2_id: id of second asset to be connected
|
|
254
|
+
:param connection_point_2: Connection point of second asset to be connected
|
|
255
|
+
:return: id of node connecting the two assets
|
|
256
|
+
"""
|
|
257
|
+
if not any([connected_1, connected_2]):
|
|
258
|
+
# both assets are not connected. Create a new node and connect everything.
|
|
259
|
+
return self._connect_both_assets_at_node(
|
|
260
|
+
asset1_id=asset1_id,
|
|
261
|
+
connection_point_1=connection_point_1,
|
|
262
|
+
asset2_id=asset2_id,
|
|
263
|
+
connection_point_2=connection_point_2,
|
|
264
|
+
)
|
|
265
|
+
elif connected_1 and (not connected_2):
|
|
266
|
+
# asset 1 connected asset 2 not, connect the node of asset 1 to asset 2
|
|
267
|
+
return self._connect_single_asset_at_node(
|
|
268
|
+
asset_id_connected=asset1_id,
|
|
269
|
+
connection_point_connected=connection_point_1,
|
|
270
|
+
asset_id_unconnected=asset2_id,
|
|
271
|
+
connection_point_unconnected=connection_point_2,
|
|
272
|
+
)
|
|
273
|
+
elif (not connected_1) and connected_2:
|
|
274
|
+
# asset 2 connected asset 1 not connect the node of asset 2 to asset 1
|
|
275
|
+
return self._connect_single_asset_at_node(
|
|
276
|
+
asset_id_connected=asset2_id,
|
|
277
|
+
connection_point_connected=connection_point_2,
|
|
278
|
+
asset_id_unconnected=asset1_id,
|
|
279
|
+
connection_point_unconnected=connection_point_1,
|
|
280
|
+
)
|
|
281
|
+
else:
|
|
282
|
+
# Effectively we call: all([connected_1, connected_2])
|
|
283
|
+
# both assets are connected, check if they are connected to the same node
|
|
284
|
+
# otherwise connect the two nodes and remove the second node.
|
|
285
|
+
return self._connect_both_assets_and_replace_node(
|
|
286
|
+
asset1_id=asset1_id,
|
|
287
|
+
connection_point_1=connection_point_1,
|
|
288
|
+
asset2_id=asset2_id,
|
|
289
|
+
connection_point_2=connection_point_2,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
def exists_asset(self, asset_id: uuid.UUID) -> bool:
|
|
293
|
+
"""Method returns true when an asset with the given id exists in the network.
|
|
294
|
+
|
|
295
|
+
:param uuid.UUID asset_id: unique id of the asset to check.
|
|
296
|
+
:return:True when asset exists and False when not
|
|
297
|
+
"""
|
|
298
|
+
if not (asset_id in self.assets):
|
|
299
|
+
raise ValueError(f"Asset with id:{asset_id} does not exist in network.")
|
|
300
|
+
else:
|
|
301
|
+
return True
|
|
302
|
+
|
|
303
|
+
def exists_node(self, node_id: uuid.UUID) -> bool:
|
|
304
|
+
"""Method returns true when a node with the given id exists in the network.
|
|
305
|
+
|
|
306
|
+
:param uuid.UUID node_id: unique id of the node to check.
|
|
307
|
+
:return:True when node exists and False when not
|
|
308
|
+
"""
|
|
309
|
+
if not (node_id in self.nodes):
|
|
310
|
+
raise ValueError(f"Node with id:{node_id} does not exist in network.")
|
|
311
|
+
else:
|
|
312
|
+
return True
|
|
313
|
+
|
|
314
|
+
def remove_asset(self) -> None:
|
|
315
|
+
"""Method to remove an asset from the network."""
|
|
316
|
+
|
|
317
|
+
def disconnect_asset(self) -> None:
|
|
318
|
+
"""Method to disconnect an asset from the network."""
|
|
319
|
+
|
|
320
|
+
def get_asset(self, asset_id: uuid.UUID) -> BaseAsset: # type: ignore
|
|
321
|
+
"""Method to get an asset in the network.
|
|
322
|
+
|
|
323
|
+
Method returns the asset with the given id, when it exists in the network.
|
|
324
|
+
when it does not exist a ValueError is raised.
|
|
325
|
+
|
|
326
|
+
:param id: asset_id of the asset which needs to be retrieved.
|
|
327
|
+
:type asset_id: uuid.UUID
|
|
328
|
+
:return: Asset
|
|
329
|
+
"""
|
|
330
|
+
if self.exists_asset(asset_id):
|
|
331
|
+
return self.assets[asset_id]
|
|
332
|
+
|
|
333
|
+
def get_node(self, node_id: uuid.UUID) -> Node: # type: ignore
|
|
334
|
+
"""Method to get a node in the network.
|
|
335
|
+
|
|
336
|
+
Method returns the node with the given id, when it exists in the network.
|
|
337
|
+
when it does not exist a ValueError is raised.
|
|
338
|
+
|
|
339
|
+
:param node_id: node_id of the node which needs to be retrieved.
|
|
340
|
+
:type node_id: uuid.UUID
|
|
341
|
+
:return: Node
|
|
342
|
+
"""
|
|
343
|
+
if self.exists_node(node_id=node_id):
|
|
344
|
+
return self.nodes[node_id]
|
|
345
|
+
|
|
346
|
+
def check_connectivity_assets(self) -> bool:
|
|
347
|
+
"""Method to check if all assets are connected at all of their connection points.
|
|
348
|
+
|
|
349
|
+
Method returns True when all assets are connected and False when an asset is not connected.
|
|
350
|
+
:return: True or False depending on if all assets are connected
|
|
351
|
+
"""
|
|
352
|
+
# TODO pass back which assets are not connected.
|
|
353
|
+
result = [asset.is_all_connected() for _, asset in self.assets.items()]
|
|
354
|
+
return all(result)
|
|
355
|
+
|
|
356
|
+
def check_connectivity_nodes(self) -> bool:
|
|
357
|
+
"""Method to check if all nodes are connected.
|
|
358
|
+
|
|
359
|
+
Method returns True when all nodes are connected and False when an node is not connected.
|
|
360
|
+
:return: True or False depending on if all nodes are connected
|
|
361
|
+
"""
|
|
362
|
+
result = [node.is_connected() for _, node in self.nodes.items()]
|
|
363
|
+
return all(result)
|
|
364
|
+
|
|
365
|
+
def check_connectivity(self) -> bool:
|
|
366
|
+
"""Method to check if all nodes and assets are connected.
|
|
367
|
+
|
|
368
|
+
:return:True when everything is connected and False when not.
|
|
369
|
+
"""
|
|
370
|
+
return self.check_connectivity_assets() and self.check_connectivity_nodes()
|
|
371
|
+
|
|
372
|
+
def set_result_asset(self, solution: list[float]) -> None:
|
|
373
|
+
"""Method to transfer the solution to the asset in the network.
|
|
374
|
+
|
|
375
|
+
:param list[float] solution:Solution to be transferred to the assets.
|
|
376
|
+
:return: None
|
|
377
|
+
"""
|
|
378
|
+
for asset in self.assets:
|
|
379
|
+
index = self.get_asset(asset_id=asset).matrix_index
|
|
380
|
+
nou = self.get_asset(asset_id=asset).number_of_unknowns
|
|
381
|
+
self.get_asset(asset_id=asset).prev_sol = solution[index: index + nou]
|
|
382
|
+
|
|
383
|
+
def set_result_node(self, solution: list[float]) -> None:
|
|
384
|
+
"""Method to transfer the solution to the nodes in the network.
|
|
385
|
+
|
|
386
|
+
:param list[float] solution:Solution to be transferred to the nodes.
|
|
387
|
+
:return: None
|
|
388
|
+
"""
|
|
389
|
+
for node in self.nodes:
|
|
390
|
+
index = self.get_node(node_id=node).matrix_index
|
|
391
|
+
nou = self.get_node(node_id=node).number_of_unknowns
|
|
392
|
+
self.get_node(node_id=node).prev_sol = solution[index: index + nou]
|
|
393
|
+
|
|
394
|
+
def print_result(self) -> None:
|
|
395
|
+
"""Method to print the result of the network."""
|
|
396
|
+
for asset in self.assets:
|
|
397
|
+
print(type(self.get_asset(asset_id=asset)))
|
|
398
|
+
print(self.get_asset(asset_id=asset).get_result())
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
"""Module for solving the network class."""
|
|
16
|
+
from simulator_core.solver.matrix.equation_object import EquationObject
|
|
17
|
+
from simulator_core.solver.matrix.matrix import Matrix
|
|
18
|
+
from simulator_core.solver.network.network import Network
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Solver:
|
|
22
|
+
"""Class to solve the network."""
|
|
23
|
+
|
|
24
|
+
_iteration_limit: int = 100
|
|
25
|
+
"""The maximum number of iterations for the solver."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, network: Network):
|
|
28
|
+
"""Constructor of the solver class.
|
|
29
|
+
|
|
30
|
+
Initializes the class properties and sets the unknowns of the matrix.
|
|
31
|
+
|
|
32
|
+
:param Network network: The network to be solved.
|
|
33
|
+
"""
|
|
34
|
+
self.matrix = Matrix()
|
|
35
|
+
self.network = network
|
|
36
|
+
self.set_unknowns_matrix()
|
|
37
|
+
|
|
38
|
+
def set_unknowns_matrix(self) -> None:
|
|
39
|
+
"""Sets the unknowns of the matrix."""
|
|
40
|
+
for asset in self.network.assets:
|
|
41
|
+
self.network.get_asset(asset).set_matrix_index(
|
|
42
|
+
self.matrix.add_unknowns(self.network.get_asset(asset).number_of_unknowns)
|
|
43
|
+
)
|
|
44
|
+
for node in self.network.nodes:
|
|
45
|
+
self.network.get_node(node).set_matrix_index(
|
|
46
|
+
self.matrix.add_unknowns(self.network.get_node(node).number_of_unknowns)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def get_equations(self) -> list[EquationObject]:
|
|
50
|
+
"""Method to get the equations of the network.
|
|
51
|
+
|
|
52
|
+
:return: list[EquationObject] equations: List of equations of the network.
|
|
53
|
+
"""
|
|
54
|
+
equations: list[EquationObject] = []
|
|
55
|
+
for asset in self.network.assets:
|
|
56
|
+
equations = equations + self.network.assets[asset].get_equations()
|
|
57
|
+
for node in self.network.nodes:
|
|
58
|
+
equations = equations + self.network.nodes[node].get_equations()
|
|
59
|
+
return equations
|
|
60
|
+
|
|
61
|
+
def solve(self) -> None:
|
|
62
|
+
"""Method to solve the network."""
|
|
63
|
+
iteration = 0
|
|
64
|
+
self.matrix.reset_solution()
|
|
65
|
+
while not self.matrix.is_converged():
|
|
66
|
+
iteration += 1
|
|
67
|
+
equations = self.get_equations()
|
|
68
|
+
self.matrix.solve(equations, dumb=False)
|
|
69
|
+
self.results_to_assets()
|
|
70
|
+
if iteration > self._iteration_limit:
|
|
71
|
+
print("No converged solution reached")
|
|
72
|
+
break
|
|
73
|
+
|
|
74
|
+
def get_results(self) -> None:
|
|
75
|
+
"""Method to get the results of the network."""
|
|
76
|
+
|
|
77
|
+
def results_to_assets(self) -> None:
|
|
78
|
+
"""Method to transfer the results to the assets from the matrix."""
|
|
79
|
+
self.network.set_result_asset(self.matrix.sol_new)
|
|
80
|
+
self.network.set_result_node(self.matrix.sol_new)
|
|
@@ -0,0 +1,173 @@
|
|
|
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
|
+
"""This module contains the fluid properties class."""
|
|
16
|
+
import csv
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from scipy.interpolate import make_interp_spline
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FluidProperties:
|
|
23
|
+
"""Class to represent the fluid properties.
|
|
24
|
+
|
|
25
|
+
This class contains methods to get the internal energy, temperature, density,
|
|
26
|
+
and viscosity of the fluid. THe data is loaded from a csv file in the same folder as this file.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
T: list[float]
|
|
30
|
+
"""A list of floats that store the temperature of the fluid [K] per temperature."""
|
|
31
|
+
|
|
32
|
+
cp: list[float]
|
|
33
|
+
"""A list of floats that store the heat capacity of the fluid [J/kg/K] per temperature."""
|
|
34
|
+
|
|
35
|
+
rho: list[float]
|
|
36
|
+
"""A list of floats that store the density of the fluid [kg/m^3] per temperature."""
|
|
37
|
+
|
|
38
|
+
visc: list[float]
|
|
39
|
+
"""A list of floats that store the viscosity of the fluid [Pa.s] per temperature."""
|
|
40
|
+
|
|
41
|
+
get_internal_energy_object: make_interp_spline
|
|
42
|
+
"""An interpolation object to get the internal energy of the fluid at a given temperature."""
|
|
43
|
+
|
|
44
|
+
get_temperature_object: make_interp_spline
|
|
45
|
+
"""An interpolation object to get the temperature of the fluid at a given internal energy."""
|
|
46
|
+
|
|
47
|
+
get_density_object: make_interp_spline
|
|
48
|
+
"""An interpolation object to get the density of the fluid at a given temperature."""
|
|
49
|
+
|
|
50
|
+
get_viscosity_object: make_interp_spline
|
|
51
|
+
"""An interpolation object to get the viscosity of the fluid at a given temperature."""
|
|
52
|
+
|
|
53
|
+
get_heat_capacity_object: make_interp_spline
|
|
54
|
+
"""An interpolation object to get the heat capacity of the fluid at a given temperature."""
|
|
55
|
+
|
|
56
|
+
get_thermal_conductivity_object: make_interp_spline
|
|
57
|
+
"""An interpolation object to get the thermal conductivity of the fluid at a given
|
|
58
|
+
temperature."""
|
|
59
|
+
|
|
60
|
+
def __init__(self) -> None:
|
|
61
|
+
"""Constructor of the fluid properties class.
|
|
62
|
+
|
|
63
|
+
Initializes the class properties and loads the fluid properties from a csv file.
|
|
64
|
+
"""
|
|
65
|
+
file = Path(__file__).parent / "temp_props.csv"
|
|
66
|
+
self.T = []
|
|
67
|
+
self.cp = []
|
|
68
|
+
self.rho = []
|
|
69
|
+
self.visc = []
|
|
70
|
+
self.therm_cond = []
|
|
71
|
+
with open(file) as csv_file:
|
|
72
|
+
csv_reader = csv.reader(csv_file, delimiter=";")
|
|
73
|
+
for row in csv_reader:
|
|
74
|
+
self.T.append(float(row[0]))
|
|
75
|
+
self.cp.append(float(row[1]))
|
|
76
|
+
self.rho.append(float(row[2]))
|
|
77
|
+
self.visc.append(float(row[3]))
|
|
78
|
+
self.therm_cond.append(float(row[4]))
|
|
79
|
+
|
|
80
|
+
self.IE = [0.0]
|
|
81
|
+
for i in range(1, len(self.T)):
|
|
82
|
+
self.IE.append(
|
|
83
|
+
self.IE[-1] + (self.cp[i - 1] + self.cp[i]) / 2 * (self.T[i] - self.T[i - 1])
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Create interpolation objects for the fluid properties
|
|
87
|
+
self.get_internal_energy_object = make_interp_spline(x=self.T, y=self.IE, k=1)
|
|
88
|
+
self.get_temperature_object = make_interp_spline(x=self.IE, y=self.T, k=1)
|
|
89
|
+
self.get_density_object = make_interp_spline(x=self.T, y=self.rho, k=1)
|
|
90
|
+
self.get_viscosity_object = make_interp_spline(x=self.T, y=self.visc, k=1)
|
|
91
|
+
self.get_heat_capacity_object = make_interp_spline(x=self.T, y=self.cp, k=1)
|
|
92
|
+
self.get_thermal_conductivity_object = make_interp_spline(x=self.T, y=self.therm_cond, k=1)
|
|
93
|
+
|
|
94
|
+
def check_temperature(self, t: float) -> None:
|
|
95
|
+
"""Check if the temperature is within the range of the fluid properties.
|
|
96
|
+
|
|
97
|
+
:param float t: The temperature to check.
|
|
98
|
+
"""
|
|
99
|
+
if t < self.T[0] or t > self.T[-1]:
|
|
100
|
+
raise ValueError(f"The temperature {t} is outside the range of the fluid properties.")
|
|
101
|
+
|
|
102
|
+
def check_internal_energy(self, ie: float) -> None:
|
|
103
|
+
"""Check if the internal energy is within the range of the fluid properties.
|
|
104
|
+
|
|
105
|
+
:param float ie: The internal energy to check.
|
|
106
|
+
"""
|
|
107
|
+
if ie < self.IE[0] or ie > self.IE[-1]:
|
|
108
|
+
raise ValueError(
|
|
109
|
+
f"The internal energy {ie} is outside the range of the fluid properties."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def get_ie(self, t: float) -> float:
|
|
113
|
+
"""Returns the internal energy of the fluid at a given temperature.
|
|
114
|
+
|
|
115
|
+
:param t: The temperature of the fluid.
|
|
116
|
+
:return: The internal energy of the fluid at the given temperature.
|
|
117
|
+
"""
|
|
118
|
+
self.check_temperature(t=t)
|
|
119
|
+
# return float(np.interp(np.array([t]), self.T, self.IE).item())
|
|
120
|
+
return float(self.get_internal_energy_object(t).item())
|
|
121
|
+
|
|
122
|
+
def get_t(self, ie: float) -> float:
|
|
123
|
+
"""Returns the temperature of the fluid at a given internal energy.
|
|
124
|
+
|
|
125
|
+
:param ie: The internal energy of the fluid.
|
|
126
|
+
:return: The temperature of the fluid at the given internal energy.
|
|
127
|
+
"""
|
|
128
|
+
self.check_internal_energy(ie=ie)
|
|
129
|
+
# return float(np.interp(np.array([ie]), self.IE, self.T).item())
|
|
130
|
+
return float(self.get_temperature_object(ie).item())
|
|
131
|
+
|
|
132
|
+
def get_density(self, t: float) -> float:
|
|
133
|
+
"""Returns the density of the fluid at a given temperature.
|
|
134
|
+
|
|
135
|
+
:param t: The temperature of the fluid.
|
|
136
|
+
:return: The density of the fluid at the given temperature.
|
|
137
|
+
"""
|
|
138
|
+
self.check_temperature(t=t)
|
|
139
|
+
# return float(np.interp(np.array([t]), self.T, self.rho).item())
|
|
140
|
+
return float(self.get_density_object(t).item())
|
|
141
|
+
|
|
142
|
+
def get_viscosity(self, t: float) -> float:
|
|
143
|
+
"""Returns the viscosity of the fluid at a given temperature.
|
|
144
|
+
|
|
145
|
+
:param t: The temperature of the fluid.
|
|
146
|
+
:return: The viscosity of the fluid at the given temperature.
|
|
147
|
+
"""
|
|
148
|
+
self.check_temperature(t=t)
|
|
149
|
+
# return float(np.interp(np.array([t]), self.T, self.visc).item())
|
|
150
|
+
return float(self.get_viscosity_object(t).item())
|
|
151
|
+
|
|
152
|
+
def get_heat_capacity(self, t: float) -> float:
|
|
153
|
+
"""Returns the heat capacity of the fluid at a given temperature.
|
|
154
|
+
|
|
155
|
+
:param t: The temperature of the fluid.
|
|
156
|
+
:return: The capacity of the fluid at the given temperature.
|
|
157
|
+
"""
|
|
158
|
+
self.check_temperature(t=t)
|
|
159
|
+
# return float(np.interp(np.array([t]), self.T, self.cp).item())
|
|
160
|
+
return float(self.get_heat_capacity_object(t).item())
|
|
161
|
+
|
|
162
|
+
def get_thermal_conductivity(self, t: float) -> float:
|
|
163
|
+
"""Returns the thermal conductivity of the fluid at a given temperature.
|
|
164
|
+
|
|
165
|
+
:param t: The temperature of the fluid.
|
|
166
|
+
:return: The thermal conductivity of the fluid at the given temperature.
|
|
167
|
+
"""
|
|
168
|
+
self.check_temperature(t=t)
|
|
169
|
+
# return float(np.interp(np.array([t]), self.T, self.therm_cond).item())
|
|
170
|
+
return float(self.get_thermal_conductivity_object(t).item())
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
fluid_props = FluidProperties()
|