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,116 @@
|
|
|
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
|
+
"""Abstract class for asset."""
|
|
17
|
+
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from typing import Dict, List
|
|
20
|
+
|
|
21
|
+
from pandas import DataFrame
|
|
22
|
+
|
|
23
|
+
from simulator_core.entities.assets.esdl_asset_object import EsdlAssetObject
|
|
24
|
+
from simulator_core.entities.assets.junction import Junction
|
|
25
|
+
from simulator_core.solver.network.assets.base_asset import BaseAsset
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AssetAbstract(ABC):
|
|
29
|
+
"""Abstract class for Asset."""
|
|
30
|
+
|
|
31
|
+
from_junction: Junction | None
|
|
32
|
+
"""The junction where the asset starts."""
|
|
33
|
+
|
|
34
|
+
to_junction: Junction | None
|
|
35
|
+
"""The junction where the asset ends."""
|
|
36
|
+
|
|
37
|
+
name: str
|
|
38
|
+
"""The name of the asset."""
|
|
39
|
+
|
|
40
|
+
asset_id: str
|
|
41
|
+
"""The unique identifier of the asset."""
|
|
42
|
+
|
|
43
|
+
output: List[Dict[str, float]]
|
|
44
|
+
"""The output of the asset as a list with a dictionary per timestep."""
|
|
45
|
+
solver_asset: BaseAsset
|
|
46
|
+
|
|
47
|
+
def __init__(self, asset_name: str, asset_id: str):
|
|
48
|
+
"""Basic constructor for asset objects.
|
|
49
|
+
|
|
50
|
+
:param str asset_name: The name of the asset.
|
|
51
|
+
:param str asset_id: The unique identifier of the asset.
|
|
52
|
+
:param PandapipesNet pandapipe_net: Pnadapipes network object to register asset to.
|
|
53
|
+
"""
|
|
54
|
+
self.from_junction = None
|
|
55
|
+
self.to_junction: Junction = None
|
|
56
|
+
self.name: str = asset_name
|
|
57
|
+
self.asset_id: str = asset_id
|
|
58
|
+
self.output: List[Dict[str, float]] = []
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def set_setpoints(self, setpoints: Dict) -> None:
|
|
62
|
+
"""Placeholder to set the setpoints of an asset prior to a simulation.
|
|
63
|
+
|
|
64
|
+
:param Dict setpoints: The setpoints that should be set for the asset.
|
|
65
|
+
The keys of the dictionary are the names of the setpoints and the values are the values
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def get_setpoints(self) -> Dict[str, float]:
|
|
69
|
+
"""Placeholder to get the setpoint attributes of an asset.
|
|
70
|
+
|
|
71
|
+
:return Dict: The setpoints of the asset. The keys of the dictionary are the names of the
|
|
72
|
+
setpoints and the values are the values.
|
|
73
|
+
"""
|
|
74
|
+
return {}
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def add_physical_data(self, esdl_asset: EsdlAssetObject) -> None:
|
|
78
|
+
"""Placeholder method to add physical data to an asset."""
|
|
79
|
+
|
|
80
|
+
def set_from_junction(self, from_junction: Junction) -> None:
|
|
81
|
+
"""Method to set the from junction of an asset.
|
|
82
|
+
|
|
83
|
+
:param Junction from_junction: The junction where the asset starts.
|
|
84
|
+
"""
|
|
85
|
+
self.from_junction = from_junction
|
|
86
|
+
|
|
87
|
+
def set_to_junction(self, to_junction: Junction) -> None:
|
|
88
|
+
"""Method to set the to junction of an asset.
|
|
89
|
+
|
|
90
|
+
:param Junction to_junction: The junction where the asset ends.
|
|
91
|
+
"""
|
|
92
|
+
self.to_junction = to_junction
|
|
93
|
+
|
|
94
|
+
@abstractmethod
|
|
95
|
+
def write_to_output(self) -> None:
|
|
96
|
+
"""Placeholder to get data from pandapipes and store it in the asset."""
|
|
97
|
+
|
|
98
|
+
def get_output(self) -> List[Dict[str, float]]:
|
|
99
|
+
"""Returns all the output of the asset.
|
|
100
|
+
|
|
101
|
+
:return: A dict of property name and list of values.
|
|
102
|
+
"""
|
|
103
|
+
return self.output
|
|
104
|
+
|
|
105
|
+
def get_timeseries(self) -> DataFrame:
|
|
106
|
+
"""Get timeseries as a dataframe from a pandapipes asset.
|
|
107
|
+
|
|
108
|
+
The header is a tuple of the asset id and the property name.
|
|
109
|
+
"""
|
|
110
|
+
# Create dataframe
|
|
111
|
+
temp_dataframe = DataFrame(self.output)
|
|
112
|
+
# Set header
|
|
113
|
+
temp_dataframe.columns = [
|
|
114
|
+
(self.asset_id, column_name) for column_name in temp_dataframe.columns
|
|
115
|
+
]
|
|
116
|
+
return temp_dataframe
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
"""Define default values and names for assets."""
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
|
|
19
|
+
# Default values
|
|
20
|
+
DEFAULT_DIAMETER = 1.2 # [m]
|
|
21
|
+
DEFAULT_PRESSURE = 10.0e5 # [bar]
|
|
22
|
+
DEFAULT_PRESSURE_DIFFERENCE = 5e5 # [bar]
|
|
23
|
+
DEFAULT_TEMPERATURE = 300.0 # [K]
|
|
24
|
+
DEFAULT_TEMPERATURE_DIFFERENCE = 30.0 # [K]
|
|
25
|
+
DEFAULT_NODE_HEIGHT = 0.0 # [m]
|
|
26
|
+
DEFAULT_MASS_FLOW_RATE = 1.0 # [kg/s]
|
|
27
|
+
DEFAULT_POWER = 500000.0 # [W]
|
|
28
|
+
DEFAULT_MISSING_VALUE = -9999.99 # [-]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class PipeDefaults:
|
|
33
|
+
"""Class containing the default values for a pipe.
|
|
34
|
+
|
|
35
|
+
:param float k_value: The k value of the pipe [m].
|
|
36
|
+
:param float alpha_value: The alpha value of the pipe [W/(m2 K)].
|
|
37
|
+
:param float minor_loss_coefficient: The minor loss coefficient of the pipe [-].
|
|
38
|
+
:param float external_temperature: The external temperature of the pipe [K].
|
|
39
|
+
:param float qheat_external: The external heat flow of the pipe [W].
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
k_value: float = 2e-3
|
|
43
|
+
alpha_value: float = 0.0
|
|
44
|
+
minor_loss_coefficient: float = 0.0
|
|
45
|
+
external_temperature: float = 273.15 + 20.0
|
|
46
|
+
qheat_external: float = 0.0
|
|
47
|
+
length: float = 1.0
|
|
48
|
+
diameter: float = DEFAULT_DIAMETER
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Default names
|
|
52
|
+
PROPERTY_HEAT_DEMAND = "heat_demand"
|
|
53
|
+
PROPERTY_TEMPERATURE_SUPPLY = "temperature_supply"
|
|
54
|
+
PROPERTY_TEMPERATURE_RETURN = "temperature_return"
|
|
55
|
+
PROPERTY_PRESSURE_SUPPLY = "pressure_supply"
|
|
56
|
+
PROPERTY_PRESSURE_RETURN = "pressure_return"
|
|
57
|
+
PROPERTY_MASSFLOW = "mass_flow"
|
|
58
|
+
PROPERTY_VOLUMEFLOW = "volume_flow"
|
|
59
|
+
PROPERTY_THERMAL_POWER = "thermal_power"
|
|
60
|
+
PROPERTY_VELOCITY_SUPPLY = "velocity_supply"
|
|
61
|
+
PROPERTY_VELOCITY_RETURN = "velocity_return"
|
|
62
|
+
PROPERTY_SET_PRESSURE = "set_pressure"
|
|
63
|
+
PROPERTY_LENGTH = "length"
|
|
64
|
+
PROPERTY_DIAMETER = "diameter"
|
|
65
|
+
PROPERTY_ROUGHNESS = "roughness"
|
|
66
|
+
|
|
67
|
+
# Static members
|
|
68
|
+
PIPE_DEFAULTS = PipeDefaults()
|
|
@@ -0,0 +1,81 @@
|
|
|
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 classes for the controller."""
|
|
16
|
+
|
|
17
|
+
import datetime
|
|
18
|
+
|
|
19
|
+
import pandas as pd
|
|
20
|
+
from simulator_core.entities.assets.asset_defaults import (DEFAULT_TEMPERATURE,
|
|
21
|
+
DEFAULT_TEMPERATURE_DIFFERENCE)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AssetControllerAbstract:
|
|
25
|
+
"""Abstract class for the asset controller."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, name: str, identifier: str):
|
|
28
|
+
"""Constructor for the asset controller.
|
|
29
|
+
|
|
30
|
+
:param str name: Name of the controller object.
|
|
31
|
+
:param str identifier: Unique identifier of the controller asset.
|
|
32
|
+
"""
|
|
33
|
+
self.name = name
|
|
34
|
+
self.id = identifier
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ControllerConsumer(AssetControllerAbstract):
|
|
38
|
+
"""Class to store the consumer for the controller asset."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, name: str, identifier: str):
|
|
41
|
+
"""Constructor for the consumer.
|
|
42
|
+
|
|
43
|
+
:param str name: Name of the consumer.
|
|
44
|
+
:param str identifier: Unique identifier of the consumer.
|
|
45
|
+
"""
|
|
46
|
+
super().__init__(name, identifier)
|
|
47
|
+
self.temperature_return = DEFAULT_TEMPERATURE
|
|
48
|
+
self.temperature_supply = DEFAULT_TEMPERATURE + DEFAULT_TEMPERATURE_DIFFERENCE
|
|
49
|
+
self.profile: pd.DataFrame = pd.DataFrame()
|
|
50
|
+
self.start_index = 0
|
|
51
|
+
|
|
52
|
+
def get_heat_demand(self, time: datetime.datetime) -> float:
|
|
53
|
+
"""Method to get the heat demand of the consumer.
|
|
54
|
+
|
|
55
|
+
:param datetime.datetime time: Time for which to get the heat demand.
|
|
56
|
+
:return: float with the heat demand.
|
|
57
|
+
"""
|
|
58
|
+
for index in range(self.start_index, len(self.profile)):
|
|
59
|
+
if abs((self.profile["date"][index].to_pydatetime() - time).total_seconds()) < 3600:
|
|
60
|
+
self.start_index = index
|
|
61
|
+
return float(self.profile["values"][index])
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
def add_profile(self, profile: pd.DataFrame) -> None:
|
|
65
|
+
"""Method to add a profile to the consumer."""
|
|
66
|
+
self.profile = profile
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ControllerSource(AssetControllerAbstract):
|
|
70
|
+
"""Class to store the source for the controller."""
|
|
71
|
+
|
|
72
|
+
def __init__(self, name: str, identifier: str):
|
|
73
|
+
"""Constructor for the source.
|
|
74
|
+
|
|
75
|
+
:param str name: Name of the source.
|
|
76
|
+
:param str identifier: Unique identifier of the source.
|
|
77
|
+
"""
|
|
78
|
+
super().__init__(name, identifier)
|
|
79
|
+
self.temperature_return: float = DEFAULT_TEMPERATURE
|
|
80
|
+
self.temperature_supply: float = DEFAULT_TEMPERATURE + DEFAULT_TEMPERATURE_DIFFERENCE
|
|
81
|
+
self.power: float = 5000000000
|
|
@@ -0,0 +1,117 @@
|
|
|
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
|
+
"""demandCluster 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_DIAMETER,
|
|
23
|
+
DEFAULT_POWER,
|
|
24
|
+
DEFAULT_PRESSURE,
|
|
25
|
+
DEFAULT_TEMPERATURE,
|
|
26
|
+
DEFAULT_TEMPERATURE_DIFFERENCE,
|
|
27
|
+
PROPERTY_HEAT_DEMAND,
|
|
28
|
+
PROPERTY_MASSFLOW,
|
|
29
|
+
PROPERTY_PRESSURE_RETURN,
|
|
30
|
+
PROPERTY_PRESSURE_SUPPLY,
|
|
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 DemandCluster(AssetAbstract):
|
|
42
|
+
"""A DemandCluster represents an asset that consumes heat."""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
asset_name: str,
|
|
47
|
+
asset_id: str,
|
|
48
|
+
):
|
|
49
|
+
"""Initialize a DemandCluster object.
|
|
50
|
+
|
|
51
|
+
:param str asset_name: The name of the asset.
|
|
52
|
+
:param str asset_id: The unique identifier of the asset.
|
|
53
|
+
:param PandapipesNet pandapipe_net: Pandapipes network object to register asset to.
|
|
54
|
+
"""
|
|
55
|
+
super().__init__(asset_name=asset_name, asset_id=asset_id)
|
|
56
|
+
|
|
57
|
+
self._internal_diameter = DEFAULT_DIAMETER
|
|
58
|
+
self.temperature_supply = DEFAULT_TEMPERATURE
|
|
59
|
+
self.temperature_return: float = DEFAULT_TEMPERATURE - DEFAULT_TEMPERATURE_DIFFERENCE
|
|
60
|
+
self.temperature_return_target = self.temperature_return
|
|
61
|
+
self.pressure_input = DEFAULT_PRESSURE
|
|
62
|
+
self.thermal_power_allocation = DEFAULT_POWER
|
|
63
|
+
self.mass_flowrate = 0
|
|
64
|
+
self.solver_asset = ProductionAsset(uuid.uuid4())
|
|
65
|
+
# Output list
|
|
66
|
+
self.output: list = []
|
|
67
|
+
|
|
68
|
+
def set_setpoints(self, setpoints: Dict) -> None:
|
|
69
|
+
"""Placeholder to set the setpoints of an asset prior to a simulation.
|
|
70
|
+
|
|
71
|
+
:param Dict setpoints: The setpoints that should be set for the asset.
|
|
72
|
+
The keys of the dictionary are the names of the setpoints and the values are the values
|
|
73
|
+
"""
|
|
74
|
+
# Default keys required
|
|
75
|
+
necessary_setpoints = {
|
|
76
|
+
PROPERTY_TEMPERATURE_SUPPLY,
|
|
77
|
+
PROPERTY_TEMPERATURE_RETURN,
|
|
78
|
+
PROPERTY_HEAT_DEMAND,
|
|
79
|
+
}
|
|
80
|
+
# Dict to set
|
|
81
|
+
setpoints_set = set(setpoints.keys())
|
|
82
|
+
# Check if all setpoints are in the setpoints
|
|
83
|
+
if not necessary_setpoints.issubset(setpoints_set):
|
|
84
|
+
# Print missing setpoints
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f"The setpoints {necessary_setpoints.difference(setpoints_set)} are missing."
|
|
87
|
+
)
|
|
88
|
+
self.thermal_power_allocation = setpoints[PROPERTY_HEAT_DEMAND]
|
|
89
|
+
self.temperature_return_target = setpoints[PROPERTY_TEMPERATURE_RETURN]
|
|
90
|
+
self.temperature_supply = setpoints[PROPERTY_TEMPERATURE_SUPPLY]
|
|
91
|
+
adjusted_mass_flowrate = heat_demand_and_temperature_to_mass_flow(
|
|
92
|
+
self.thermal_power_allocation, self.temperature_supply, self.temperature_return_target
|
|
93
|
+
)
|
|
94
|
+
self.solver_asset.supply_temperature = self.temperature_return_target
|
|
95
|
+
self.solver_asset.mass_flow_rate_set_point = adjusted_mass_flowrate # type: ignore
|
|
96
|
+
|
|
97
|
+
def add_physical_data(self, esdl_asset: EsdlAssetObject) -> None:
|
|
98
|
+
"""Method to add physical data to the asset.
|
|
99
|
+
|
|
100
|
+
:param EsdlAssetObject esdl_asset: The esdl asset object to add the physical data from.
|
|
101
|
+
:return:
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def write_to_output(self) -> None:
|
|
105
|
+
"""Placeholder to write the asset to the output.
|
|
106
|
+
|
|
107
|
+
The output list is a list of dictionaries, where each dictionary
|
|
108
|
+
represents the output of its asset for a specific timestep.
|
|
109
|
+
"""
|
|
110
|
+
output_dict = {
|
|
111
|
+
PROPERTY_MASSFLOW: self.solver_asset.get_mass_flow_rate(1),
|
|
112
|
+
PROPERTY_PRESSURE_SUPPLY: self.solver_asset.get_pressure(0),
|
|
113
|
+
PROPERTY_PRESSURE_RETURN: self.solver_asset.get_pressure(1),
|
|
114
|
+
PROPERTY_TEMPERATURE_SUPPLY: self.solver_asset.get_temperature(0),
|
|
115
|
+
PROPERTY_TEMPERATURE_RETURN: self.solver_asset.get_temperature(1),
|
|
116
|
+
}
|
|
117
|
+
self.output.append(output_dict)
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
"""Module containing classes to be able to interact with esdl objects."""
|
|
17
|
+
import logging
|
|
18
|
+
from typing import Any, Tuple
|
|
19
|
+
|
|
20
|
+
import pandas as pd
|
|
21
|
+
from esdl import esdl
|
|
22
|
+
from simulator_core.entities.utility.influxdb_reader import get_data_from_profile
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class EsdlAssetObject:
|
|
28
|
+
"""
|
|
29
|
+
Class to hold an esdl asset and convert it to local class objects.
|
|
30
|
+
|
|
31
|
+
Conversion is done based on the classes in the conversion_dict.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
esdl_asset: esdl.Asset
|
|
35
|
+
|
|
36
|
+
def __init__(self, asset: esdl.Asset) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Constructor for EsdlAssetObject class.
|
|
39
|
+
|
|
40
|
+
:param asset, esdl.Asset: PyEsdl Asset object
|
|
41
|
+
"""
|
|
42
|
+
self.esdl_asset = asset
|
|
43
|
+
|
|
44
|
+
def get_property(self, esdl_property_name: str, default_value: Any) -> Tuple[Any, bool]:
|
|
45
|
+
"""Get property value from the esdl_asset based on the "ESDL" name.
|
|
46
|
+
|
|
47
|
+
:return: Tuple with the value of the property and a boolean indicating whether the property
|
|
48
|
+
was found in the esdl_asset.
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
return getattr(self.esdl_asset, esdl_property_name), True
|
|
52
|
+
except AttributeError:
|
|
53
|
+
return default_value, False
|
|
54
|
+
|
|
55
|
+
def get_profile(self) -> pd.DataFrame:
|
|
56
|
+
"""Get the profile of the asset."""
|
|
57
|
+
for esdl_port in self.esdl_asset.port:
|
|
58
|
+
if esdl_port.profile:
|
|
59
|
+
return get_data_from_profile(esdl_port.profile[0])
|
|
60
|
+
raise ValueError("No profile found for asset: " + self.esdl_asset.name)
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
"""Junction classes."""
|
|
17
|
+
|
|
18
|
+
from typing import Any, List, Optional
|
|
19
|
+
|
|
20
|
+
from simulator_core.entities.assets.asset_defaults import (DEFAULT_NODE_HEIGHT,
|
|
21
|
+
DEFAULT_PRESSURE,
|
|
22
|
+
DEFAULT_TEMPERATURE)
|
|
23
|
+
from simulator_core.solver.network.assets.node import Node
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Junction:
|
|
27
|
+
"""Wrapper class for pandapipes junctions."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
solver_node: Node,
|
|
32
|
+
pn_bar: float = DEFAULT_PRESSURE,
|
|
33
|
+
tfluid_k: float = DEFAULT_TEMPERATURE,
|
|
34
|
+
height_m: float = DEFAULT_NODE_HEIGHT,
|
|
35
|
+
geodata: Optional[List[Any]] = None,
|
|
36
|
+
name: str = "None",
|
|
37
|
+
in_service: bool = True,
|
|
38
|
+
index: Optional[int] = None,
|
|
39
|
+
):
|
|
40
|
+
"""Initialize a Junction object.
|
|
41
|
+
|
|
42
|
+
:param pn_bar: The pressure at the junction [bar], defaults to DEFAULT_PRESSURE
|
|
43
|
+
:type pn_bar: float, optional
|
|
44
|
+
:param tfluid_k: The temperature at the junction [K], defaults to DEFAULT_TEMPERATURE
|
|
45
|
+
:type tfluid_k: float, optional
|
|
46
|
+
:param height_m: The height of the junction [m], defaults to DEFAULT_NODE_HEIGHT
|
|
47
|
+
:type height_m: float, optional
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
self.pn_bar = pn_bar
|
|
51
|
+
self.tfluid_k = tfluid_k
|
|
52
|
+
self.height_m = height_m
|
|
53
|
+
self.name = name
|
|
54
|
+
self.index = index
|
|
55
|
+
# Initialize the junction
|
|
56
|
+
self.solver_junction = solver_node
|
|
57
|
+
|
|
58
|
+
def write_to_output(self) -> None:
|
|
59
|
+
"""Placeholder to write the asset to the output.
|
|
60
|
+
|
|
61
|
+
The output list is a list of dictionaries, where each dictionary
|
|
62
|
+
represents the output of its asset for a specific timestep.
|
|
63
|
+
"""
|
|
64
|
+
return
|
|
@@ -0,0 +1,174 @@
|
|
|
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 pipe class."""
|
|
16
|
+
import uuid
|
|
17
|
+
from typing import Dict, List
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
from simulator_core.entities.assets.asset_abstract import AssetAbstract
|
|
22
|
+
from simulator_core.entities.assets.asset_defaults import (
|
|
23
|
+
PIPE_DEFAULTS,
|
|
24
|
+
PROPERTY_MASSFLOW,
|
|
25
|
+
PROPERTY_PRESSURE_RETURN,
|
|
26
|
+
PROPERTY_PRESSURE_SUPPLY,
|
|
27
|
+
PROPERTY_TEMPERATURE_RETURN,
|
|
28
|
+
PROPERTY_TEMPERATURE_SUPPLY,
|
|
29
|
+
)
|
|
30
|
+
from simulator_core.entities.assets.esdl_asset_object import EsdlAssetObject
|
|
31
|
+
from simulator_core.entities.assets.utils import (
|
|
32
|
+
calculate_inverse_heat_transfer_coefficient,
|
|
33
|
+
get_thermal_conductivity_table,
|
|
34
|
+
)
|
|
35
|
+
from simulator_core.solver.network.assets.solver_pipe import SolverPipe
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Pipe(AssetAbstract):
|
|
39
|
+
"""A class representing a pipe in a heat network."""
|
|
40
|
+
|
|
41
|
+
_minor_loss_coefficient: float
|
|
42
|
+
"""The minor loss coefficient of the pipe [-]."""
|
|
43
|
+
_external_temperature: float
|
|
44
|
+
"""The external temperature surrounding the pipe [K]."""
|
|
45
|
+
_qheat_external: float
|
|
46
|
+
"""The external heat flow into the pipe [W]."""
|
|
47
|
+
length: float
|
|
48
|
+
"""The length of the pipe [m]."""
|
|
49
|
+
diameter: float
|
|
50
|
+
"""The diameter of the pipe [m]."""
|
|
51
|
+
roughness: float
|
|
52
|
+
"""The wall roughness of the pipe [m]."""
|
|
53
|
+
alpha_value: float
|
|
54
|
+
"""The alpha value of the pipe [W/(m2 K)]."""
|
|
55
|
+
_initialized: bool
|
|
56
|
+
"""Flag to indicate whether the pipe has been initialized in pandapipes."""
|
|
57
|
+
output: List[Dict[str, float]]
|
|
58
|
+
"""The output list of the pipe with a dictionaries for each timestep."""
|
|
59
|
+
|
|
60
|
+
def __init__(self, asset_name: str, asset_id: str):
|
|
61
|
+
"""Initialize a Pipe object.
|
|
62
|
+
|
|
63
|
+
:param str asset_name: The name of the asset.
|
|
64
|
+
:param str asset_id: The unique identifier of the asset.
|
|
65
|
+
:param PandapipesNet pandapipe_net: Pandapipes network object to register asset to.
|
|
66
|
+
"""
|
|
67
|
+
super().__init__(asset_name=asset_name, asset_id=asset_id)
|
|
68
|
+
# Initialize the default values of the pipe
|
|
69
|
+
self._minor_loss_coefficient = PIPE_DEFAULTS.minor_loss_coefficient
|
|
70
|
+
self._external_temperature = PIPE_DEFAULTS.external_temperature
|
|
71
|
+
self._qheat_external = PIPE_DEFAULTS.qheat_external
|
|
72
|
+
# Define properties of the pipe
|
|
73
|
+
self.length = PIPE_DEFAULTS.length
|
|
74
|
+
self.diameter = PIPE_DEFAULTS.diameter
|
|
75
|
+
self.roughness = PIPE_DEFAULTS.k_value
|
|
76
|
+
self.alpha_value = PIPE_DEFAULTS.alpha_value
|
|
77
|
+
# Objects of the pandapipes network
|
|
78
|
+
self.solver_asset = SolverPipe(
|
|
79
|
+
uuid.uuid4(), length=self.length, diameter=self.diameter, roughness=self.roughness
|
|
80
|
+
)
|
|
81
|
+
self.output = []
|
|
82
|
+
|
|
83
|
+
def _get_diameter(self, esdl_asset: EsdlAssetObject) -> float:
|
|
84
|
+
"""Retrieve the diameter of the pipe and convert it if necessary."""
|
|
85
|
+
temp_diameter, property_available = esdl_asset.get_property("innerDiameter", self.diameter)
|
|
86
|
+
if property_available:
|
|
87
|
+
if temp_diameter == 0:
|
|
88
|
+
return self.diameter
|
|
89
|
+
return float(temp_diameter)
|
|
90
|
+
else:
|
|
91
|
+
# Implement DN-conversion
|
|
92
|
+
raise NotImplementedError(
|
|
93
|
+
f"The innderDiamter property is unavailable for {esdl_asset.esdl_asset.name}. \
|
|
94
|
+
Conversion from DN to diameter is not yet implemented."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def _get_heat_transfer_coefficient(self, esdl_asset: EsdlAssetObject) -> float:
|
|
98
|
+
"""Calculate the heat transfer coefficient of the pipe.
|
|
99
|
+
|
|
100
|
+
:param EsdlAssetObject esdl_asset: The ESDL asset object associated with the
|
|
101
|
+
current pipe object.
|
|
102
|
+
|
|
103
|
+
:return: The heat transfer coefficient of the pipe [W/(m2 K)]. If the heat transfer
|
|
104
|
+
coefficient cannot be calculated - for example when the material table is
|
|
105
|
+
not specified - , the default alpha value is returned.
|
|
106
|
+
"""
|
|
107
|
+
diameters, heat_coefficients = get_thermal_conductivity_table(esdl_asset=esdl_asset)
|
|
108
|
+
if diameters:
|
|
109
|
+
# Create a numpy array of the diameters and heat coefficients
|
|
110
|
+
diameters_np = np.array(diameters)
|
|
111
|
+
heat_coefficients_np = np.array(heat_coefficients)
|
|
112
|
+
# Calculate the heat transfer coefficient from the heat transfer table
|
|
113
|
+
inverse_heat_transfer_coefficient = np.sum(
|
|
114
|
+
calculate_inverse_heat_transfer_coefficient(
|
|
115
|
+
inner_diameter=diameters_np[:-1],
|
|
116
|
+
outer_diameter=diameters_np[1:],
|
|
117
|
+
thermal_conductivity=heat_coefficients_np,
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
return 1.0 / float(inverse_heat_transfer_coefficient)
|
|
121
|
+
else:
|
|
122
|
+
return self.alpha_value
|
|
123
|
+
|
|
124
|
+
def add_physical_data(self, esdl_asset: EsdlAssetObject) -> None:
|
|
125
|
+
"""Method to add physical data to the asset.
|
|
126
|
+
|
|
127
|
+
:param EsdlAssetObject esdl_asset: The ESDL asset object associated with the
|
|
128
|
+
current pipe object.
|
|
129
|
+
"""
|
|
130
|
+
# Error handling is performed in EsdlAssetObject.get_asset_parameters
|
|
131
|
+
self.length, _ = esdl_asset.get_property(
|
|
132
|
+
esdl_property_name="length", default_value=self.length
|
|
133
|
+
)
|
|
134
|
+
self.roughness, _ = esdl_asset.get_property(
|
|
135
|
+
esdl_property_name="roughness", default_value=self.roughness
|
|
136
|
+
)
|
|
137
|
+
self.roughness = PIPE_DEFAULTS.k_value if self.roughness == 0 else self.roughness
|
|
138
|
+
self.diameter = self._get_diameter(esdl_asset=esdl_asset)
|
|
139
|
+
|
|
140
|
+
self.alpha_value = self._get_heat_transfer_coefficient(esdl_asset=esdl_asset)
|
|
141
|
+
prop_dict = {"length": self.length, "diameter": self.diameter, "roughness": self.roughness}
|
|
142
|
+
self.solver_asset.set_physical_properties(physical_properties=prop_dict)
|
|
143
|
+
|
|
144
|
+
def set_setpoints(self, setpoints: Dict) -> None:
|
|
145
|
+
"""Set the setpoints of the pipe prior to a simulation.
|
|
146
|
+
|
|
147
|
+
:param Dict setpoints: The setpoints that should be set for the pipe.
|
|
148
|
+
The keys of the dictionary are the names of the setpoints and the values are the values
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def write_to_output(self) -> None:
|
|
152
|
+
"""Write the output of the asset to the output list.
|
|
153
|
+
|
|
154
|
+
The output list is a list of dictionaries, where each dictionary
|
|
155
|
+
represents the output of its asset for a specific timestep.
|
|
156
|
+
|
|
157
|
+
The output of the asset is a dictionary with the following keys:
|
|
158
|
+
- PROPERTY_HEAT_DEMAND: The heat demand of the asset.
|
|
159
|
+
- PROPERTY_TEMPERATURE_SUPPLY: The supply temperature of the asset.
|
|
160
|
+
- PROPERTY_TEMPERATURE_RETURN: The return temperature of the asset.
|
|
161
|
+
- PROPERTY_PRESSURE_SUPPLY: The supply pressure of the asset.
|
|
162
|
+
- PROPERTY_PRESSURE_RETURN: The return pressure of the asset.
|
|
163
|
+
- PROPERTY_MASSFLOW: The mass flow rate of the asset.
|
|
164
|
+
- PROPERTY_VELOCITY_SUPPLY: The supply velocity of the asset.
|
|
165
|
+
- PROPERTY_VELOCITY_RETURN: The return velocity of the asset.
|
|
166
|
+
"""
|
|
167
|
+
output_dict = {
|
|
168
|
+
PROPERTY_MASSFLOW: self.solver_asset.get_mass_flow_rate(1),
|
|
169
|
+
PROPERTY_PRESSURE_SUPPLY: self.solver_asset.get_pressure(0),
|
|
170
|
+
PROPERTY_PRESSURE_RETURN: self.solver_asset.get_pressure(1),
|
|
171
|
+
PROPERTY_TEMPERATURE_SUPPLY: self.solver_asset.get_temperature(0),
|
|
172
|
+
PROPERTY_TEMPERATURE_RETURN: self.solver_asset.get_temperature(1),
|
|
173
|
+
}
|
|
174
|
+
self.output.append(output_dict)
|