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,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()