exerpy 0.0.2__py3-none-any.whl → 0.0.3__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.
- exerpy/__init__.py +2 -4
- exerpy/analyses.py +597 -297
- exerpy/components/__init__.py +3 -0
- exerpy/components/combustion/base.py +53 -35
- exerpy/components/component.py +8 -8
- exerpy/components/heat_exchanger/base.py +186 -119
- exerpy/components/heat_exchanger/condenser.py +96 -60
- exerpy/components/heat_exchanger/simple.py +237 -137
- exerpy/components/heat_exchanger/steam_generator.py +46 -41
- exerpy/components/helpers/cycle_closer.py +61 -34
- exerpy/components/helpers/power_bus.py +117 -0
- exerpy/components/nodes/deaerator.py +176 -58
- exerpy/components/nodes/drum.py +50 -39
- exerpy/components/nodes/flash_tank.py +218 -43
- exerpy/components/nodes/mixer.py +249 -69
- exerpy/components/nodes/splitter.py +173 -0
- exerpy/components/nodes/storage.py +130 -0
- exerpy/components/piping/valve.py +311 -115
- exerpy/components/power_machines/generator.py +105 -38
- exerpy/components/power_machines/motor.py +111 -39
- exerpy/components/turbomachinery/compressor.py +181 -63
- exerpy/components/turbomachinery/pump.py +182 -63
- exerpy/components/turbomachinery/turbine.py +182 -74
- exerpy/functions.py +388 -263
- exerpy/parser/from_aspen/aspen_config.py +57 -48
- exerpy/parser/from_aspen/aspen_parser.py +373 -280
- exerpy/parser/from_ebsilon/__init__.py +2 -2
- exerpy/parser/from_ebsilon/check_ebs_path.py +15 -19
- exerpy/parser/from_ebsilon/ebsilon_config.py +328 -226
- exerpy/parser/from_ebsilon/ebsilon_functions.py +205 -38
- exerpy/parser/from_ebsilon/ebsilon_parser.py +392 -255
- exerpy/parser/from_ebsilon/utils.py +16 -11
- exerpy/parser/from_tespy/tespy_config.py +32 -1
- exerpy/parser/from_tespy/tespy_parser.py +151 -0
- {exerpy-0.0.2.dist-info → exerpy-0.0.3.dist-info}/METADATA +43 -2
- exerpy-0.0.3.dist-info/RECORD +48 -0
- exerpy-0.0.2.dist-info/RECORD +0 -44
- {exerpy-0.0.2.dist-info → exerpy-0.0.3.dist-info}/WHEEL +0 -0
- {exerpy-0.0.2.dist-info → exerpy-0.0.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,17 +2,16 @@ import json
|
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
4
|
|
|
5
|
-
from exerpy.functions import convert_to_SI
|
|
6
|
-
from exerpy.functions import fluid_property_data
|
|
5
|
+
from exerpy.functions import convert_to_SI, fluid_property_data
|
|
7
6
|
|
|
8
|
-
from .aspen_config import connector_mappings
|
|
9
|
-
from .aspen_config import grouped_components
|
|
7
|
+
from .aspen_config import connector_mappings, grouped_components
|
|
10
8
|
|
|
11
9
|
|
|
12
10
|
class AspenModelParser:
|
|
13
11
|
"""
|
|
14
12
|
A class to parse Aspen Plus models, simulate them, extract data, and write to JSON.
|
|
15
13
|
"""
|
|
14
|
+
|
|
16
15
|
def __init__(self, model_path, split_physical_exergy=True):
|
|
17
16
|
"""
|
|
18
17
|
Initializes the parser with the given model path.
|
|
@@ -29,21 +28,21 @@ class AspenModelParser:
|
|
|
29
28
|
|
|
30
29
|
# Dictionary to map component types to specific connector assignment functions
|
|
31
30
|
self.connector_assignment_functions = {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
"Mixer": self.assign_mixer_connectors,
|
|
32
|
+
"RStoic": self.assign_combustion_chamber_connectors,
|
|
33
|
+
"FSplit": self.assign_splitter_connectors,
|
|
35
34
|
# Add other specific component functions here
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
|
|
39
37
|
def initialize_model(self):
|
|
40
38
|
"""
|
|
41
39
|
Initializes the Aspen Plus application and opens the specified model.
|
|
42
40
|
"""
|
|
43
41
|
from win32com.client import Dispatch
|
|
42
|
+
|
|
44
43
|
try:
|
|
45
44
|
# Start Aspen Plus application via COM Dispatch
|
|
46
|
-
self.aspen = Dispatch(
|
|
45
|
+
self.aspen = Dispatch("Apwn.Document")
|
|
47
46
|
# Load the Aspen model file
|
|
48
47
|
self.aspen.InitFromArchive2(self.model_path)
|
|
49
48
|
logging.info(f"Model opened successfully: {self.model_path}")
|
|
@@ -69,164 +68,218 @@ class AspenModelParser:
|
|
|
69
68
|
logging.error(f"Error while parsing the model: {e}")
|
|
70
69
|
raise
|
|
71
70
|
|
|
72
|
-
|
|
73
71
|
def parse_streams(self):
|
|
74
72
|
"""
|
|
75
73
|
Parses the streams (connections) in the Aspen model.
|
|
76
74
|
"""
|
|
77
75
|
# Get the stream nodes and their names
|
|
78
|
-
stream_nodes = self.aspen.Tree.FindNode(r
|
|
76
|
+
stream_nodes = self.aspen.Tree.FindNode(r"\Data\Streams").Elements
|
|
79
77
|
stream_names = [stream_node.Name for stream_node in stream_nodes]
|
|
80
78
|
|
|
81
79
|
# ALL ASPEN CONNECTIONS
|
|
82
80
|
# Initialize connection data with the common fields
|
|
83
81
|
for stream_name in stream_names:
|
|
84
|
-
|
|
82
|
+
self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}")
|
|
85
83
|
connection_data = {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
84
|
+
"name": stream_name,
|
|
85
|
+
"kind": None,
|
|
86
|
+
"source_component": None,
|
|
87
|
+
"source_connector": None,
|
|
88
|
+
"target_component": None,
|
|
89
|
+
"target_connector": None,
|
|
92
90
|
}
|
|
93
91
|
|
|
94
92
|
# Find the source and target components
|
|
95
|
-
source_port_node = self.aspen.Tree.FindNode(
|
|
93
|
+
source_port_node = self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Ports\SOURCE")
|
|
96
94
|
if source_port_node is not None and source_port_node.Elements.Count > 0:
|
|
97
95
|
connection_data["source_component"] = source_port_node.Elements(0).Name
|
|
98
96
|
|
|
99
|
-
destination_port_node = self.aspen.Tree.FindNode(
|
|
97
|
+
destination_port_node = self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Ports\DEST")
|
|
100
98
|
if destination_port_node is not None and destination_port_node.Elements.Count > 0:
|
|
101
99
|
connection_data["target_component"] = destination_port_node.Elements(0).Name
|
|
102
100
|
|
|
103
101
|
# HEAT AND POWER STREAMS
|
|
104
|
-
if self.aspen.Tree.FindNode(
|
|
105
|
-
connection_data[
|
|
106
|
-
connection_data[
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
102
|
+
if self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Input\WORK") is not None:
|
|
103
|
+
connection_data["kind"] = "power"
|
|
104
|
+
connection_data["energy_flow"] = (
|
|
105
|
+
convert_to_SI(
|
|
106
|
+
"power",
|
|
107
|
+
abs(self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\POWER_OUT").Value),
|
|
108
|
+
self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\POWER_OUT").UnitString,
|
|
109
|
+
)
|
|
110
|
+
if self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\POWER_OUT") is not None
|
|
111
|
+
else None
|
|
112
|
+
)
|
|
113
|
+
elif self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Input\HEAT") is not None:
|
|
114
|
+
connection_data["kind"] = "heat"
|
|
115
|
+
connection_data["energy_flow"] = (
|
|
116
|
+
convert_to_SI(
|
|
117
|
+
"power",
|
|
118
|
+
abs(self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\QCALC").Value),
|
|
119
|
+
self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\QCALC").UnitString,
|
|
120
|
+
)
|
|
121
|
+
if self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\QCALC") is not None
|
|
122
|
+
else None
|
|
123
|
+
)
|
|
118
124
|
|
|
119
125
|
# MATERIAL STREAMS
|
|
120
126
|
else:
|
|
121
127
|
# Assume it's a material stream and retrieve additional properties
|
|
122
|
-
connection_data.update(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
self.aspen.Tree.FindNode(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
128
|
+
connection_data.update(
|
|
129
|
+
{
|
|
130
|
+
"kind": "material",
|
|
131
|
+
"T": (
|
|
132
|
+
convert_to_SI(
|
|
133
|
+
"T",
|
|
134
|
+
self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\TEMP_OUT\MIXED").Value,
|
|
135
|
+
self.aspen.Tree.FindNode(
|
|
136
|
+
rf"\Data\Streams\{stream_name}\Output\TEMP_OUT\MIXED"
|
|
137
|
+
).UnitString,
|
|
138
|
+
)
|
|
139
|
+
if self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\TEMP_OUT\MIXED")
|
|
140
|
+
is not None
|
|
141
|
+
else None
|
|
142
|
+
),
|
|
143
|
+
"T_unit": fluid_property_data["T"]["SI_unit"],
|
|
144
|
+
"p": (
|
|
145
|
+
convert_to_SI(
|
|
146
|
+
"p",
|
|
147
|
+
self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\PRES_OUT\MIXED").Value,
|
|
148
|
+
self.aspen.Tree.FindNode(
|
|
149
|
+
rf"\Data\Streams\{stream_name}\Output\PRES_OUT\MIXED"
|
|
150
|
+
).UnitString,
|
|
151
|
+
)
|
|
152
|
+
if self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\PRES_OUT\MIXED")
|
|
153
|
+
is not None
|
|
154
|
+
else None
|
|
155
|
+
),
|
|
156
|
+
"p_unit": fluid_property_data["p"]["SI_unit"],
|
|
157
|
+
"h": (
|
|
158
|
+
convert_to_SI(
|
|
159
|
+
"h",
|
|
160
|
+
self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\HMX_MASS\MIXED").Value,
|
|
161
|
+
self.aspen.Tree.FindNode(
|
|
162
|
+
rf"\Data\Streams\{stream_name}\Output\HMX_MASS\MIXED"
|
|
163
|
+
).UnitString,
|
|
164
|
+
)
|
|
165
|
+
if self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\HMX_MASS\MIXED")
|
|
166
|
+
is not None
|
|
167
|
+
else None
|
|
168
|
+
),
|
|
169
|
+
"h_unit": fluid_property_data["h"]["SI_unit"],
|
|
170
|
+
"s": (
|
|
171
|
+
convert_to_SI(
|
|
172
|
+
"s",
|
|
173
|
+
self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\SMX_MASS\MIXED").Value,
|
|
174
|
+
self.aspen.Tree.FindNode(
|
|
175
|
+
rf"\Data\Streams\{stream_name}\Output\SMX_MASS\MIXED"
|
|
176
|
+
).UnitString,
|
|
177
|
+
)
|
|
178
|
+
if self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\SMX_MASS\MIXED")
|
|
179
|
+
is not None
|
|
180
|
+
else None
|
|
181
|
+
),
|
|
182
|
+
"s_unit": fluid_property_data["s"]["SI_unit"],
|
|
183
|
+
"m": (
|
|
184
|
+
convert_to_SI(
|
|
185
|
+
"m",
|
|
186
|
+
self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\MASSFLMX\MIXED").Value,
|
|
187
|
+
self.aspen.Tree.FindNode(
|
|
188
|
+
rf"\Data\Streams\{stream_name}\Output\MASSFLMX\MIXED"
|
|
189
|
+
).UnitString,
|
|
190
|
+
)
|
|
191
|
+
if self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\MASSFLMX\MIXED")
|
|
192
|
+
is not None
|
|
193
|
+
else None
|
|
194
|
+
),
|
|
195
|
+
"m_unit": fluid_property_data["m"]["SI_unit"],
|
|
196
|
+
"energy_flow": (
|
|
197
|
+
convert_to_SI(
|
|
198
|
+
"power",
|
|
199
|
+
abs(
|
|
200
|
+
self.aspen.Tree.FindNode(
|
|
201
|
+
rf"\Data\Streams\{stream_name}\Output\HMX_FLOW\MIXED"
|
|
202
|
+
).Value
|
|
203
|
+
),
|
|
204
|
+
self.aspen.Tree.FindNode(
|
|
205
|
+
rf"\Data\Streams\{stream_name}\Output\HMX_FLOW\MIXED"
|
|
206
|
+
).UnitString,
|
|
207
|
+
)
|
|
208
|
+
if self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\HMX_FLOW\MIXED")
|
|
209
|
+
is not None
|
|
210
|
+
else None
|
|
211
|
+
),
|
|
212
|
+
"energy_flow_unit": fluid_property_data["power"]["SI_unit"],
|
|
213
|
+
"e_PH": (
|
|
214
|
+
convert_to_SI(
|
|
215
|
+
"e",
|
|
216
|
+
self.aspen.Tree.FindNode(
|
|
217
|
+
rf"\Data\Streams\{stream_name}\Output\STRM_UPP\EXERGYMS\MIXED\TOTAL"
|
|
218
|
+
).Value,
|
|
219
|
+
self.aspen.Tree.FindNode(
|
|
220
|
+
rf"\Data\Streams\{stream_name}\Output\STRM_UPP\EXERGYMS\MIXED\TOTAL"
|
|
221
|
+
).UnitString,
|
|
222
|
+
)
|
|
223
|
+
if self.aspen.Tree.FindNode(
|
|
224
|
+
rf"\Data\Streams\{stream_name}\Output\STRM_UPP\EXERGYMS\MIXED\TOTAL"
|
|
225
|
+
)
|
|
226
|
+
is not None
|
|
227
|
+
else (logging.warning(f"e_PH node not found for stream {stream_name}"), None)[1]
|
|
228
|
+
),
|
|
229
|
+
"e_PH_unit": fluid_property_data["e"]["SI_unit"],
|
|
230
|
+
"n": (
|
|
231
|
+
convert_to_SI(
|
|
232
|
+
"n",
|
|
233
|
+
self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\TOT_FLOW").Value,
|
|
234
|
+
self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\TOT_FLOW").UnitString,
|
|
235
|
+
)
|
|
236
|
+
if self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\TOT_FLOW") is not None
|
|
237
|
+
else None
|
|
238
|
+
),
|
|
239
|
+
"n_unit": fluid_property_data["n"]["SI_unit"],
|
|
240
|
+
"mass_composition": {},
|
|
241
|
+
"molar_composition": {},
|
|
242
|
+
}
|
|
243
|
+
)
|
|
194
244
|
# Retrieve the fluid names for the stream
|
|
195
|
-
mole_frac_node = self.aspen.Tree.FindNode(
|
|
245
|
+
mole_frac_node = self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\MOLEFRAC\MIXED")
|
|
196
246
|
if mole_frac_node is not None:
|
|
197
247
|
fluid_names = [fluid.Name for fluid in mole_frac_node.Elements]
|
|
198
248
|
|
|
199
249
|
# Retrieve the molar composition for each fluid
|
|
200
250
|
for fluid_name in fluid_names:
|
|
201
|
-
mole_frac = self.aspen.Tree.FindNode(
|
|
251
|
+
mole_frac = self.aspen.Tree.FindNode(
|
|
252
|
+
rf"\Data\Streams\{stream_name}\Output\MOLEFRAC\MIXED\{fluid_name}"
|
|
253
|
+
).Value
|
|
202
254
|
if mole_frac not in [0, None]: # Skip fluids with 0 or None as the fraction
|
|
203
255
|
connection_data["molar_composition"][fluid_name] = mole_frac
|
|
204
256
|
|
|
205
|
-
mass_frac_node = self.aspen.Tree.FindNode(
|
|
257
|
+
mass_frac_node = self.aspen.Tree.FindNode(rf"\Data\Streams\{stream_name}\Output\MASSFRAC\MIXED")
|
|
206
258
|
if mass_frac_node is not None:
|
|
207
259
|
# Retrieve the mass composition for each fluid
|
|
208
260
|
for fluid_name in [fluid.Name for fluid in mass_frac_node.Elements]:
|
|
209
|
-
mass_frac = self.aspen.Tree.FindNode(
|
|
261
|
+
mass_frac = self.aspen.Tree.FindNode(
|
|
262
|
+
rf"\Data\Streams\{stream_name}\Output\MASSFRAC\MIXED\{fluid_name}"
|
|
263
|
+
).Value
|
|
210
264
|
if mass_frac not in [0, None]: # Skip fluids with 0 or None as the fraction
|
|
211
265
|
connection_data["mass_composition"][fluid_name] = mass_frac
|
|
212
266
|
|
|
213
267
|
# Store connection data
|
|
214
268
|
self.connections_data[stream_name] = connection_data
|
|
215
269
|
|
|
216
|
-
|
|
217
270
|
def parse_blocks(self):
|
|
218
271
|
"""
|
|
219
272
|
Parses the blocks (components) in the Aspen model and ensures that all components, including motors created from pumps, are properly grouped.
|
|
220
273
|
"""
|
|
221
|
-
block_nodes = self.aspen.Tree.FindNode(r
|
|
274
|
+
block_nodes = self.aspen.Tree.FindNode(r"\Data\Blocks").Elements
|
|
222
275
|
block_names = [block_node.Name for block_node in block_nodes]
|
|
223
276
|
|
|
224
277
|
# Process each block
|
|
225
278
|
for block_name in block_names:
|
|
226
|
-
model_type_node = self.aspen.Tree.FindNode(
|
|
279
|
+
model_type_node = self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Input\MODEL_TYPE")
|
|
227
280
|
model_type = model_type_node.Value if model_type_node is not None else None
|
|
228
281
|
|
|
229
|
-
component_type_node = self.aspen.Tree.FindNode(
|
|
282
|
+
component_type_node = self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}")
|
|
230
283
|
if component_type_node is None:
|
|
231
284
|
continue
|
|
232
285
|
component_type = component_type_node.AttributeValue(6)
|
|
@@ -237,122 +290,141 @@ class AspenModelParser:
|
|
|
237
290
|
continue
|
|
238
291
|
|
|
239
292
|
component_data = {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
self.aspen.Tree.FindNode(
|
|
244
|
-
if self.aspen.Tree.FindNode(
|
|
293
|
+
"name": block_name,
|
|
294
|
+
"type": component_type,
|
|
295
|
+
"eta_s": (
|
|
296
|
+
self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Output\EFF_ISEN").Value
|
|
297
|
+
if self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Output\EFF_ISEN") is not None
|
|
298
|
+
else None
|
|
245
299
|
),
|
|
246
|
-
|
|
247
|
-
self.aspen.Tree.FindNode(
|
|
248
|
-
if self.aspen.Tree.FindNode(
|
|
300
|
+
"eta_mech": (
|
|
301
|
+
self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Output\EFF_MECH").Value
|
|
302
|
+
if self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Output\EFF_MECH") is not None
|
|
303
|
+
else None
|
|
249
304
|
),
|
|
250
|
-
|
|
305
|
+
"Q": (
|
|
251
306
|
convert_to_SI(
|
|
252
|
-
|
|
253
|
-
self.aspen.Tree.FindNode(
|
|
254
|
-
self.aspen.Tree.FindNode(
|
|
255
|
-
)
|
|
307
|
+
"heat",
|
|
308
|
+
self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Output\QNET").Value,
|
|
309
|
+
self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Output\QNET").UnitString,
|
|
310
|
+
)
|
|
311
|
+
if self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Output\QNET") is not None
|
|
312
|
+
else None
|
|
256
313
|
),
|
|
257
|
-
|
|
258
|
-
|
|
314
|
+
"Q_unit": fluid_property_data["heat"]["SI_unit"],
|
|
315
|
+
"P": (
|
|
259
316
|
convert_to_SI(
|
|
260
|
-
|
|
261
|
-
abs(self.aspen.Tree.FindNode(
|
|
262
|
-
self.aspen.Tree.FindNode(
|
|
263
|
-
)
|
|
317
|
+
"power",
|
|
318
|
+
abs(self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Output\BRAKE_POWER").Value),
|
|
319
|
+
self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Output\BRAKE_POWER").UnitString,
|
|
320
|
+
)
|
|
321
|
+
if self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Output\BRAKE_POWER") is not None
|
|
322
|
+
else None
|
|
264
323
|
),
|
|
265
|
-
|
|
324
|
+
"P_unit": fluid_property_data["power"]["SI_unit"],
|
|
266
325
|
}
|
|
267
326
|
|
|
268
327
|
# Override component type based on model_type
|
|
269
328
|
if model_type is not None:
|
|
270
329
|
if model_type == "COMPRESSOR":
|
|
271
|
-
component_data[
|
|
330
|
+
component_data["type"] = "Compressor"
|
|
272
331
|
elif model_type == "TURBINE":
|
|
273
|
-
component_data[
|
|
274
|
-
|
|
332
|
+
component_data["type"] = "Turbine"
|
|
275
333
|
|
|
276
334
|
# Handle Generators & Motors (if not in a Pump) as multiplier blocks
|
|
277
|
-
if component_type ==
|
|
278
|
-
mult_value_node = self.aspen.Tree.FindNode(
|
|
335
|
+
if component_type == "Mult":
|
|
336
|
+
mult_value_node = self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}")
|
|
279
337
|
mult_value = mult_value_node.Value if mult_value_node is not None else None
|
|
280
|
-
if mult_value ==
|
|
281
|
-
factor_node = self.aspen.Tree.FindNode(
|
|
338
|
+
if mult_value == "WORK":
|
|
339
|
+
factor_node = self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Input\FACTOR")
|
|
282
340
|
factor = factor_node.Value if factor_node is not None else None
|
|
283
341
|
if factor is not None:
|
|
284
342
|
if factor < 1:
|
|
285
|
-
component_data.update({
|
|
286
|
-
'eta_el': factor,
|
|
287
|
-
'type': 'Generator'
|
|
288
|
-
})
|
|
343
|
+
component_data.update({"eta_el": factor, "type": "Generator"})
|
|
289
344
|
elif factor > 1:
|
|
290
|
-
elec_power_node = self.aspen.Tree.FindNode(
|
|
345
|
+
elec_power_node = self.aspen.Tree.FindNode(
|
|
346
|
+
rf"\Data\Blocks\{block_name}\Ports\WS(OUT)"
|
|
347
|
+
).Elements(0)
|
|
291
348
|
elec_power_name = elec_power_node.Name
|
|
292
349
|
if elec_power_name in self.connections_data:
|
|
293
|
-
elec_power = abs(self.connections_data[elec_power_name][
|
|
350
|
+
elec_power = abs(self.connections_data[elec_power_name]["energy_flow"])
|
|
294
351
|
else:
|
|
295
352
|
logging.warning(f"No WS(IN) ports found for block {block_name}")
|
|
296
353
|
elec_power = None
|
|
297
|
-
brake_power_node = self.aspen.Tree.FindNode(
|
|
354
|
+
brake_power_node = self.aspen.Tree.FindNode(
|
|
355
|
+
rf"\Data\Blocks\{block_name}\Ports\WS(IN)"
|
|
356
|
+
).Elements(0)
|
|
298
357
|
brake_power_name = brake_power_node.Name
|
|
299
358
|
if brake_power_name in self.connections_data:
|
|
300
|
-
brake_power = abs(self.connections_data[brake_power_name][
|
|
359
|
+
brake_power = abs(self.connections_data[brake_power_name]["energy_flow"])
|
|
301
360
|
else:
|
|
302
361
|
logging.warning(f"No WS(IN) ports found for block {block_name}")
|
|
303
362
|
brake_power = None
|
|
304
|
-
component_data.update(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
363
|
+
component_data.update(
|
|
364
|
+
{
|
|
365
|
+
"eta_el": 1 / factor,
|
|
366
|
+
"multiplier factor": factor,
|
|
367
|
+
"type": "Motor",
|
|
368
|
+
"P_el": elec_power,
|
|
369
|
+
"P_el_unit": fluid_property_data["power"]["SI_unit"],
|
|
370
|
+
"P_mech": brake_power,
|
|
371
|
+
"P_mech_unit": fluid_property_data["power"]["SI_unit"],
|
|
372
|
+
}
|
|
373
|
+
)
|
|
313
374
|
else: # factor == 1
|
|
314
|
-
choice =
|
|
315
|
-
|
|
316
|
-
|
|
375
|
+
choice = (
|
|
376
|
+
input(
|
|
377
|
+
f"Multiplier Block '{block_name}' has factor = 1. Enter 'G' if it is a Generator or 'M' for Motor: "
|
|
378
|
+
)
|
|
379
|
+
.strip()
|
|
380
|
+
.upper()
|
|
381
|
+
)
|
|
382
|
+
if choice == "M":
|
|
383
|
+
elec_power_node = self.aspen.Tree.FindNode(
|
|
384
|
+
rf"\Data\Blocks\{block_name}\Ports\WS(OUT)"
|
|
385
|
+
).Elements(0)
|
|
317
386
|
elec_power_name = elec_power_node.Name
|
|
318
387
|
if elec_power_name in self.connections_data:
|
|
319
|
-
elec_power = abs(self.connections_data[elec_power_name][
|
|
388
|
+
elec_power = abs(self.connections_data[elec_power_name]["energy_flow"])
|
|
320
389
|
else:
|
|
321
390
|
logging.warning(f"No WS(IN) ports found for block {block_name}")
|
|
322
391
|
elec_power = None
|
|
323
|
-
brake_power_node = self.aspen.Tree.FindNode(
|
|
392
|
+
brake_power_node = self.aspen.Tree.FindNode(
|
|
393
|
+
rf"\Data\Blocks\{block_name}\Ports\WS(IN)"
|
|
394
|
+
).Elements(0)
|
|
324
395
|
brake_power_name = brake_power_node.Name
|
|
325
396
|
if brake_power_name in self.connections_data:
|
|
326
|
-
brake_power = abs(self.connections_data[brake_power_name][
|
|
397
|
+
brake_power = abs(self.connections_data[brake_power_name]["energy_flow"])
|
|
327
398
|
else:
|
|
328
399
|
logging.warning(f"No WS(IN) ports found for block {block_name}")
|
|
329
400
|
brake_power = None
|
|
330
|
-
component_data.update(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
401
|
+
component_data.update(
|
|
402
|
+
{
|
|
403
|
+
"eta_el": factor,
|
|
404
|
+
"type": "Motor",
|
|
405
|
+
"P_el": elec_power,
|
|
406
|
+
"P_el_unit": fluid_property_data["power"]["SI_unit"],
|
|
407
|
+
"P_mech": brake_power,
|
|
408
|
+
"P_mech_unit": fluid_property_data["power"]["SI_unit"],
|
|
409
|
+
}
|
|
410
|
+
)
|
|
338
411
|
else:
|
|
339
|
-
component_data.update({
|
|
340
|
-
'eta_el': factor,
|
|
341
|
-
'type': 'Generator'
|
|
342
|
-
})
|
|
412
|
+
component_data.update({"eta_el": factor, "type": "Generator"})
|
|
343
413
|
|
|
344
414
|
# Create a connection for the heat flows of the SimpleHeatExchanger blocks
|
|
345
|
-
if component_type ==
|
|
415
|
+
if component_type == "Heater":
|
|
346
416
|
heat_connection_name = f"{block_name}_HEAT"
|
|
347
417
|
heat_connection_data = {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
418
|
+
"name": heat_connection_name,
|
|
419
|
+
"kind": "heat",
|
|
420
|
+
"source_component": block_name,
|
|
421
|
+
"source_connector": 1, # 00 is reserved for the fluid streams
|
|
422
|
+
"target_component": None, # Heat assumed to leave the system (not relevant for exergy analysis)
|
|
423
|
+
"target_connector": None, # Heat assumed to leave the system (not relevant for exergy analysis)
|
|
424
|
+
"energy_flow": abs(
|
|
425
|
+
component_data["Q"]
|
|
426
|
+
), # the user defines in the balances if the heat flow is positive or negative
|
|
427
|
+
"energy_flow_unit": fluid_property_data["heat"]["SI_unit"],
|
|
356
428
|
}
|
|
357
429
|
|
|
358
430
|
# Store the heat connection
|
|
@@ -362,26 +434,43 @@ class AspenModelParser:
|
|
|
362
434
|
self.group_component(component_data, block_name)
|
|
363
435
|
|
|
364
436
|
# Handle Pumps and their associated Motors
|
|
365
|
-
if component_type ==
|
|
437
|
+
if component_type == "Pump":
|
|
366
438
|
motor_name = f"{block_name}-MOTOR"
|
|
367
|
-
elec_power_node = self.aspen.Tree.FindNode(
|
|
368
|
-
elec_power =
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
439
|
+
elec_power_node = self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Output\ELEC_POWER")
|
|
440
|
+
elec_power = (
|
|
441
|
+
abs(
|
|
442
|
+
convert_to_SI(
|
|
443
|
+
"power",
|
|
444
|
+
elec_power_node.Value,
|
|
445
|
+
elec_power_node.UnitString,
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
if elec_power_node is not None
|
|
449
|
+
else None
|
|
450
|
+
)
|
|
451
|
+
brake_power_node = self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Output\BRAKE_POWER")
|
|
452
|
+
brake_power = (
|
|
453
|
+
abs(
|
|
454
|
+
convert_to_SI(
|
|
455
|
+
"power",
|
|
456
|
+
brake_power_node.Value,
|
|
457
|
+
brake_power_node.UnitString,
|
|
458
|
+
)
|
|
459
|
+
)
|
|
460
|
+
if brake_power_node is not None
|
|
461
|
+
else None
|
|
462
|
+
)
|
|
463
|
+
eff_driv_node = self.aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Output\EFF_DRIV")
|
|
372
464
|
eff_driv = eff_driv_node.Value if eff_driv_node is not None else None
|
|
373
465
|
|
|
374
466
|
motor_data = {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
eff_driv
|
|
383
|
-
if eff_driv is not None else None
|
|
384
|
-
),
|
|
467
|
+
"name": motor_name,
|
|
468
|
+
"type": "Motor",
|
|
469
|
+
"P_el": elec_power,
|
|
470
|
+
"P_el_unit": fluid_property_data["power"]["SI_unit"],
|
|
471
|
+
"P_mech": brake_power,
|
|
472
|
+
"P_mech_unit": fluid_property_data["power"]["SI_unit"],
|
|
473
|
+
"eta_el": (eff_driv if eff_driv is not None else None),
|
|
385
474
|
}
|
|
386
475
|
|
|
387
476
|
# Group the motor
|
|
@@ -391,26 +480,26 @@ class AspenModelParser:
|
|
|
391
480
|
if elec_power is not None:
|
|
392
481
|
electr_connection_name = f"{block_name}_ELEC"
|
|
393
482
|
electr_connection_data = {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
483
|
+
"name": electr_connection_name,
|
|
484
|
+
"kind": "power",
|
|
485
|
+
"source_component": None, # Electrical power usually leaves the system
|
|
486
|
+
"source_connector": None, # Electrical power usually leaves the system
|
|
487
|
+
"target_component": motor_name,
|
|
488
|
+
"target_connector": 0,
|
|
489
|
+
"energy_flow": motor_data["P_el"],
|
|
490
|
+
"energy_flow_unit": fluid_property_data["power"]["SI_unit"],
|
|
402
491
|
}
|
|
403
492
|
|
|
404
493
|
mech_connection_name = f"{block_name}_MECH"
|
|
405
494
|
mech_connection_data = {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
495
|
+
"name": mech_connection_name,
|
|
496
|
+
"kind": "power",
|
|
497
|
+
"source_component": motor_name,
|
|
498
|
+
"source_connector": 0,
|
|
499
|
+
"target_component": block_name,
|
|
500
|
+
"target_connector": 1,
|
|
501
|
+
"energy_flow": motor_data["P_mech"],
|
|
502
|
+
"energy_flow_unit": fluid_property_data["power"]["SI_unit"],
|
|
414
503
|
}
|
|
415
504
|
|
|
416
505
|
# Store the motor connection
|
|
@@ -420,12 +509,11 @@ class AspenModelParser:
|
|
|
420
509
|
# Assign connectors
|
|
421
510
|
self.assign_connectors(component_data, block_name)
|
|
422
511
|
|
|
423
|
-
|
|
424
512
|
def assign_connectors(self, component_data, block_name):
|
|
425
513
|
"""
|
|
426
514
|
Assigns connectors to streams for each component based on its type.
|
|
427
515
|
"""
|
|
428
|
-
component_type = component_data[
|
|
516
|
+
component_type = component_data["type"]
|
|
429
517
|
|
|
430
518
|
# Check if there is a specific assignment function for this component type
|
|
431
519
|
if component_type in self.connector_assignment_functions:
|
|
@@ -433,14 +521,15 @@ class AspenModelParser:
|
|
|
433
521
|
self.connector_assignment_functions[component_type](block_name, self.aspen, self.connections_data)
|
|
434
522
|
else:
|
|
435
523
|
# Fall back to the generic connector assignment logic
|
|
436
|
-
self.assign_generic_connectors(
|
|
437
|
-
|
|
524
|
+
self.assign_generic_connectors(
|
|
525
|
+
block_name, component_type, self.aspen, self.connections_data, connector_mappings
|
|
526
|
+
)
|
|
438
527
|
|
|
439
528
|
def assign_mixer_connectors(self, block_name, aspen, connections_data):
|
|
440
529
|
"""
|
|
441
530
|
Assign connectors for a Mixer by examining connected streams and their source/target components.
|
|
442
531
|
"""
|
|
443
|
-
ports_node = aspen.Tree.FindNode(
|
|
532
|
+
ports_node = aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Ports")
|
|
444
533
|
if ports_node is None:
|
|
445
534
|
logging.warning(f"No Ports node found for Mixer block: {block_name}")
|
|
446
535
|
return
|
|
@@ -450,38 +539,39 @@ class AspenModelParser:
|
|
|
450
539
|
|
|
451
540
|
for port in ports_node.Elements:
|
|
452
541
|
port_label = port.Name
|
|
453
|
-
port_node = aspen.Tree.FindNode(
|
|
542
|
+
port_node = aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Ports\{port_label}")
|
|
454
543
|
if port_node is not None and port_node.Elements.Count > 0:
|
|
455
544
|
for element in port_node.Elements:
|
|
456
545
|
stream_name = element.Name
|
|
457
546
|
if stream_name in connections_data:
|
|
458
547
|
stream_data = connections_data[stream_name]
|
|
459
|
-
if stream_data.get(
|
|
548
|
+
if stream_data.get("target_component") == block_name:
|
|
460
549
|
inlet_streams.append((port_label, stream_name))
|
|
461
|
-
elif stream_data.get(
|
|
550
|
+
elif stream_data.get("source_component") == block_name:
|
|
462
551
|
outlet_streams.append((port_label, stream_name))
|
|
463
552
|
else:
|
|
464
|
-
logging.warning(
|
|
553
|
+
logging.warning(
|
|
554
|
+
f"Stream {stream_name} connected to {block_name} but source/target components do not match."
|
|
555
|
+
)
|
|
465
556
|
|
|
466
557
|
# Assign connectors to inlet streams
|
|
467
558
|
for idx, (port_label, stream_name) in enumerate(inlet_streams):
|
|
468
|
-
connections_data[stream_name][
|
|
559
|
+
connections_data[stream_name]["target_connector"] = idx
|
|
469
560
|
logging.debug(f"Assigned connector {idx} to inlet stream: {stream_name}")
|
|
470
561
|
|
|
471
562
|
# Assign connector to outlet stream
|
|
472
563
|
if outlet_streams:
|
|
473
564
|
for idx, (port_label, stream_name) in enumerate(outlet_streams):
|
|
474
|
-
connections_data[stream_name][
|
|
565
|
+
connections_data[stream_name]["source_connector"] = 0 # Assuming single outlet for mixer
|
|
475
566
|
logging.debug(f"Assigned connector 0 to outlet stream: {stream_name}")
|
|
476
567
|
|
|
477
|
-
|
|
478
568
|
def assign_splitter_connectors(self, block_name, aspen, connections_data):
|
|
479
569
|
"""
|
|
480
570
|
Assign connectors for a Splitter (FSplit) by examining connected streams and their source/target components.
|
|
481
571
|
The inlet stream is assigned 'target_connector' = 0.
|
|
482
572
|
The outlet streams are assigned 'source_connector' numbers starting from 0.
|
|
483
573
|
"""
|
|
484
|
-
ports_node = aspen.Tree.FindNode(
|
|
574
|
+
ports_node = aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Ports")
|
|
485
575
|
if ports_node is None:
|
|
486
576
|
logging.warning(f"No Ports node found for Splitter block: {block_name}")
|
|
487
577
|
return
|
|
@@ -492,36 +582,37 @@ class AspenModelParser:
|
|
|
492
582
|
# Iterate over all ports connected to the splitter
|
|
493
583
|
for port in ports_node.Elements:
|
|
494
584
|
port_label = port.Name
|
|
495
|
-
port_node = aspen.Tree.FindNode(
|
|
585
|
+
port_node = aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Ports\{port_label}")
|
|
496
586
|
if port_node is not None and port_node.Elements.Count > 0:
|
|
497
587
|
for element in port_node.Elements:
|
|
498
588
|
stream_name = element.Name
|
|
499
589
|
if stream_name in connections_data:
|
|
500
590
|
stream_data = connections_data[stream_name]
|
|
501
591
|
# Determine if the stream is an inlet or outlet based on source and target components
|
|
502
|
-
if stream_data.get(
|
|
592
|
+
if stream_data.get("target_component") == block_name:
|
|
503
593
|
inlet_streams.append((port_label, stream_name))
|
|
504
|
-
elif stream_data.get(
|
|
594
|
+
elif stream_data.get("source_component") == block_name:
|
|
505
595
|
outlet_streams.append((port_label, stream_name))
|
|
506
596
|
else:
|
|
507
|
-
logging.warning(
|
|
597
|
+
logging.warning(
|
|
598
|
+
f"Stream {stream_name} connected to {block_name} but source/target components do not match."
|
|
599
|
+
)
|
|
508
600
|
|
|
509
601
|
# Assign connector to inlet stream(s)
|
|
510
602
|
for idx, (port_label, stream_name) in enumerate(inlet_streams):
|
|
511
|
-
connections_data[stream_name][
|
|
603
|
+
connections_data[stream_name]["target_connector"] = 0 # Assuming single inlet for splitter
|
|
512
604
|
logging.debug(f"Assigned connector 0 to inlet stream: {stream_name}")
|
|
513
605
|
|
|
514
606
|
# Assign connectors to outlet streams
|
|
515
607
|
for idx, (port_label, stream_name) in enumerate(outlet_streams):
|
|
516
|
-
connections_data[stream_name][
|
|
608
|
+
connections_data[stream_name]["source_connector"] = idx
|
|
517
609
|
logging.debug(f"Assigned connector {idx} to outlet stream: {stream_name}")
|
|
518
610
|
|
|
519
|
-
|
|
520
611
|
def assign_combustion_chamber_connectors(self, block_name, aspen, connections_data):
|
|
521
612
|
"""
|
|
522
613
|
Assign connectors for a combustion chamber (RStoic), based on stream types (air, fuel, etc.).
|
|
523
614
|
"""
|
|
524
|
-
ports_node = aspen.Tree.FindNode(
|
|
615
|
+
ports_node = aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Ports")
|
|
525
616
|
if ports_node is None:
|
|
526
617
|
logging.warning(f"No Ports node found for combustion chamber block: {block_name}")
|
|
527
618
|
return
|
|
@@ -531,33 +622,32 @@ class AspenModelParser:
|
|
|
531
622
|
port_label = port.Name
|
|
532
623
|
|
|
533
624
|
# Handle inlet ports
|
|
534
|
-
if
|
|
535
|
-
port_node = aspen.Tree.FindNode(
|
|
625
|
+
if "(IN)" in port_label:
|
|
626
|
+
port_node = aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Ports\{port_label}")
|
|
536
627
|
if port_node is not None and port_node.Elements.Count > 0:
|
|
537
628
|
for element in port_node.Elements:
|
|
538
629
|
stream_name = element.Name
|
|
539
630
|
if stream_name in connections_data:
|
|
540
|
-
molar_composition = connections_data[stream_name].get(
|
|
541
|
-
if molar_composition.get(
|
|
542
|
-
connections_data[stream_name][
|
|
631
|
+
molar_composition = connections_data[stream_name].get("molar_composition", {})
|
|
632
|
+
if molar_composition.get("O2", 0) > 0.15:
|
|
633
|
+
connections_data[stream_name]["target_connector"] = 0 # Air inlet
|
|
543
634
|
logging.debug(f"Assigned connector 0 to air inlet stream: {stream_name}")
|
|
544
|
-
elif molar_composition.get(
|
|
545
|
-
connections_data[stream_name][
|
|
635
|
+
elif molar_composition.get("CH4", 0) > 0.15:
|
|
636
|
+
connections_data[stream_name]["target_connector"] = 1 # Fuel inlet
|
|
546
637
|
logging.debug(f"Assigned connector 1 to fuel inlet stream: {stream_name}")
|
|
547
638
|
else:
|
|
548
639
|
logging.warning(f"Stream {stream_name} in {block_name} has ambiguous composition.")
|
|
549
640
|
|
|
550
641
|
# Handle outlet ports
|
|
551
|
-
elif
|
|
552
|
-
port_node = aspen.Tree.FindNode(
|
|
642
|
+
elif "(OUT)" in port_label:
|
|
643
|
+
port_node = aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Ports\{port_label}")
|
|
553
644
|
if port_node is not None and port_node.Elements.Count > 0:
|
|
554
645
|
for element in port_node.Elements:
|
|
555
646
|
stream_name = element.Name
|
|
556
647
|
if stream_name in connections_data:
|
|
557
|
-
connections_data[stream_name][
|
|
648
|
+
connections_data[stream_name]["source_connector"] = 0 # Outlet stream
|
|
558
649
|
logging.info(f"Assigned connector 0 to outlet stream: {stream_name}")
|
|
559
650
|
|
|
560
|
-
|
|
561
651
|
def assign_generic_connectors(self, block_name, component_type, aspen, connections_data, connector_mappings):
|
|
562
652
|
"""
|
|
563
653
|
Generic function for components with predefined connector mappings.
|
|
@@ -567,24 +657,27 @@ class AspenModelParser:
|
|
|
567
657
|
|
|
568
658
|
# Access the ports of the component to find the connected streams
|
|
569
659
|
for port_label, connector_num in mapping.items():
|
|
570
|
-
port_node = aspen.Tree.FindNode(
|
|
660
|
+
port_node = aspen.Tree.FindNode(rf"\Data\Blocks\{block_name}\Ports\{port_label}")
|
|
571
661
|
if port_node is not None and port_node.Elements.Count > 0:
|
|
572
662
|
for element in port_node.Elements:
|
|
573
663
|
stream_name = element.Name
|
|
574
664
|
# Assign the connector number to the appropriate stream in the connection data
|
|
575
665
|
if stream_name in connections_data:
|
|
576
|
-
if
|
|
577
|
-
|
|
578
|
-
connections_data[stream_name][
|
|
666
|
+
if (
|
|
667
|
+
"source_component" in connections_data[stream_name]
|
|
668
|
+
and connections_data[stream_name]["source_component"] == block_name
|
|
669
|
+
):
|
|
670
|
+
connections_data[stream_name]["source_connector"] = connector_num
|
|
579
671
|
logging.debug(f"Assigned connector {connector_num} to source stream: {stream_name}")
|
|
580
|
-
elif
|
|
581
|
-
|
|
582
|
-
connections_data[stream_name][
|
|
672
|
+
elif (
|
|
673
|
+
"target_component" in connections_data[stream_name]
|
|
674
|
+
and connections_data[stream_name]["target_component"] == block_name
|
|
675
|
+
):
|
|
676
|
+
connections_data[stream_name]["target_connector"] = connector_num
|
|
583
677
|
logging.debug(f"Assigned connector {connector_num} to target stream: {stream_name}")
|
|
584
678
|
else:
|
|
585
679
|
logging.warning(f"No connector mapping defined for component type {component_type}.")
|
|
586
680
|
|
|
587
|
-
|
|
588
681
|
def group_component(self, component_data, component_name):
|
|
589
682
|
"""
|
|
590
683
|
Group the component based on its type into the correct group within components_data.
|
|
@@ -596,13 +689,13 @@ class AspenModelParser:
|
|
|
596
689
|
# Determine the group for the component based on its type
|
|
597
690
|
group = None
|
|
598
691
|
for group_name, type_list in grouped_components.items():
|
|
599
|
-
if component_data[
|
|
692
|
+
if component_data["type"] in type_list:
|
|
600
693
|
group = group_name
|
|
601
694
|
break
|
|
602
695
|
|
|
603
696
|
# If the component doesn't belong to any predefined group, use its type name
|
|
604
697
|
if not group:
|
|
605
|
-
group = component_data[
|
|
698
|
+
group = component_data["type"]
|
|
606
699
|
|
|
607
700
|
# Initialize the group in the components_data dictionary if not already present
|
|
608
701
|
if group not in self.components_data:
|
|
@@ -611,7 +704,6 @@ class AspenModelParser:
|
|
|
611
704
|
# Store the component data in the appropriate group
|
|
612
705
|
self.components_data[group][component_name] = component_data
|
|
613
706
|
|
|
614
|
-
|
|
615
707
|
def parse_ambient_conditions(self):
|
|
616
708
|
"""
|
|
617
709
|
Parses the ambient conditions from the Aspen model and stores them as class attributes.
|
|
@@ -620,25 +712,21 @@ class AspenModelParser:
|
|
|
620
712
|
try:
|
|
621
713
|
# Parse ambient temperature (Tamb)
|
|
622
714
|
temp_node = self.aspen.Tree.FindNode(r"\Data\Setup\Sim-Options\Input\REF_TEMP")
|
|
623
|
-
self.Tamb = convert_to_SI(
|
|
624
|
-
'T',
|
|
625
|
-
temp_node.Value,
|
|
626
|
-
temp_node.UnitString
|
|
627
|
-
) if temp_node is not None else None
|
|
715
|
+
self.Tamb = convert_to_SI("T", temp_node.Value, temp_node.UnitString) if temp_node is not None else None
|
|
628
716
|
|
|
629
717
|
if self.Tamb is None:
|
|
630
|
-
raise ValueError(
|
|
718
|
+
raise ValueError(
|
|
719
|
+
"Ambient temperature (Tamb) not found in the Aspen model. Please set it in Setup > Calculation Options."
|
|
720
|
+
)
|
|
631
721
|
|
|
632
722
|
# Parse ambient pressure (pamb)
|
|
633
723
|
pres_node = self.aspen.Tree.FindNode(r"\Data\Setup\Sim-Options\Input\REF_PRES")
|
|
634
|
-
self.pamb = convert_to_SI(
|
|
635
|
-
'p',
|
|
636
|
-
pres_node.Value,
|
|
637
|
-
pres_node.UnitString
|
|
638
|
-
) if pres_node is not None else None
|
|
724
|
+
self.pamb = convert_to_SI("p", pres_node.Value, pres_node.UnitString) if pres_node is not None else None
|
|
639
725
|
|
|
640
726
|
if self.pamb is None:
|
|
641
|
-
raise ValueError(
|
|
727
|
+
raise ValueError(
|
|
728
|
+
"Ambient pressure (pamb) not found in the Aspen model. Please set it in Setup > Calculation Options."
|
|
729
|
+
)
|
|
642
730
|
|
|
643
731
|
logging.info(f"Parsed ambient conditions: Tamb = {self.Tamb} K, pamb = {self.pamb} Pa")
|
|
644
732
|
|
|
@@ -654,15 +742,20 @@ class AspenModelParser:
|
|
|
654
742
|
dict: A dictionary containing sorted 'components', 'connections', and ambient conditions data.
|
|
655
743
|
"""
|
|
656
744
|
sorted_components = {comp_name: self.components_data[comp_name] for comp_name in sorted(self.components_data)}
|
|
657
|
-
sorted_connections = {
|
|
745
|
+
sorted_connections = {
|
|
746
|
+
conn_name: self.connections_data[conn_name] for conn_name in sorted(self.connections_data)
|
|
747
|
+
}
|
|
658
748
|
ambient_conditions = {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
749
|
+
"Tamb": self.Tamb,
|
|
750
|
+
"Tamb_unit": fluid_property_data["T"]["SI_unit"],
|
|
751
|
+
"pamb": self.pamb,
|
|
752
|
+
"pamb_unit": fluid_property_data["p"]["SI_unit"],
|
|
753
|
+
}
|
|
754
|
+
return {
|
|
755
|
+
"components": sorted_components,
|
|
756
|
+
"connections": sorted_connections,
|
|
757
|
+
"ambient_conditions": ambient_conditions,
|
|
663
758
|
}
|
|
664
|
-
return {'components': sorted_components, 'connections': sorted_connections, 'ambient_conditions': ambient_conditions}
|
|
665
|
-
|
|
666
759
|
|
|
667
760
|
def write_to_json(self, output_path):
|
|
668
761
|
"""
|
|
@@ -674,7 +767,7 @@ class AspenModelParser:
|
|
|
674
767
|
data = self.get_sorted_data()
|
|
675
768
|
|
|
676
769
|
try:
|
|
677
|
-
with open(output_path,
|
|
770
|
+
with open(output_path, "w") as json_file:
|
|
678
771
|
json.dump(data, json_file, indent=4)
|
|
679
772
|
logging.info(f"Data successfully written to {output_path}")
|
|
680
773
|
except Exception as e:
|