tespy 0.3.0__py2.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.
- TESPy-0.3.0.data/data/src/tespy/data/char_lines.json +213 -0
- TESPy-0.3.0.data/data/src/tespy/data/char_maps.json +52 -0
- TESPy-0.3.0.dist-info/DESCRIPTION.rst +117 -0
- TESPy-0.3.0.dist-info/METADATA +156 -0
- TESPy-0.3.0.dist-info/RECORD +34 -0
- TESPy-0.3.0.dist-info/WHEEL +6 -0
- TESPy-0.3.0.dist-info/metadata.json +1 -0
- TESPy-0.3.0.dist-info/top_level.txt +1 -0
- tespy/__init__.py +27 -0
- tespy/components/__init__.py +28 -0
- tespy/components/basics.py +507 -0
- tespy/components/combustion.py +3291 -0
- tespy/components/components.py +813 -0
- tespy/components/customs.py +729 -0
- tespy/components/heat_exchangers.py +3116 -0
- tespy/components/nodes.py +1890 -0
- tespy/components/piping.py +536 -0
- tespy/components/reactors.py +965 -0
- tespy/components/subsystems.py +89 -0
- tespy/components/turbomachinery.py +1726 -0
- tespy/connections.py +1062 -0
- tespy/data/__init__.py +1 -0
- tespy/data/char_lines.json +213 -0
- tespy/data/char_maps.json +52 -0
- tespy/networks/__init__.py +3 -0
- tespy/networks/network_reader.py +660 -0
- tespy/networks/networks.py +3001 -0
- tespy/tools/__init__.py +13 -0
- tespy/tools/characteristics.py +663 -0
- tespy/tools/data_containers.py +429 -0
- tespy/tools/fluid_properties.py +1763 -0
- tespy/tools/global_vars.py +15 -0
- tespy/tools/helpers.py +592 -0
- tespy/tools/logger.py +216 -0
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
# -*- coding: utf-8
|
|
2
|
+
|
|
3
|
+
"""Module class component.
|
|
4
|
+
|
|
5
|
+
All tespy components inherit from this class.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
This file is part of project TESPy (github.com/oemof/tespy). It's copyrighted
|
|
9
|
+
by the contributors recorded in the version control history of the file,
|
|
10
|
+
available from its original location tespy/components/components.py
|
|
11
|
+
|
|
12
|
+
SPDX-License-Identifier: MIT
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
from tespy.tools.characteristics import char_line
|
|
20
|
+
from tespy.tools.characteristics import char_map
|
|
21
|
+
from tespy.tools.characteristics import compressor_map
|
|
22
|
+
from tespy.tools.characteristics import load_default_char as ldc
|
|
23
|
+
from tespy.tools.data_containers import data_container
|
|
24
|
+
from tespy.tools.data_containers import dc_cc
|
|
25
|
+
from tespy.tools.data_containers import dc_cm
|
|
26
|
+
from tespy.tools.data_containers import dc_cp
|
|
27
|
+
from tespy.tools.data_containers import dc_gcp
|
|
28
|
+
from tespy.tools.data_containers import dc_simple
|
|
29
|
+
from tespy.tools.fluid_properties import v_mix_ph
|
|
30
|
+
from tespy.tools.helpers import bus_char_derivative
|
|
31
|
+
from tespy.tools.helpers import bus_char_evaluation
|
|
32
|
+
from tespy.tools.helpers import newton
|
|
33
|
+
|
|
34
|
+
# %%
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class component:
|
|
38
|
+
r"""
|
|
39
|
+
Class component is the base class of all TESPy components.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
label : str
|
|
44
|
+
The label of the component.
|
|
45
|
+
|
|
46
|
+
design : list
|
|
47
|
+
List containing design parameters (stated as String).
|
|
48
|
+
|
|
49
|
+
offdesign : list
|
|
50
|
+
List containing offdesign parameters (stated as String).
|
|
51
|
+
|
|
52
|
+
design_path: str
|
|
53
|
+
Path to the components design case.
|
|
54
|
+
|
|
55
|
+
local_offdesign : boolean
|
|
56
|
+
Treat this component in offdesign mode in a design calculation.
|
|
57
|
+
|
|
58
|
+
local_design : boolean
|
|
59
|
+
Treat this component in design mode in an offdesign calculation.
|
|
60
|
+
|
|
61
|
+
char_warnings: boolean
|
|
62
|
+
Ignore warnings on default characteristics usage for this component.
|
|
63
|
+
|
|
64
|
+
printout: boolean
|
|
65
|
+
Include this component in the network's results printout.
|
|
66
|
+
|
|
67
|
+
**kwargs :
|
|
68
|
+
See the class documentation of desired component for available
|
|
69
|
+
keywords.
|
|
70
|
+
|
|
71
|
+
Note
|
|
72
|
+
----
|
|
73
|
+
The initialisation method (__init__), setter method (set_attr) and getter
|
|
74
|
+
method (get_attr) are used for instances of class component and its
|
|
75
|
+
children.
|
|
76
|
+
|
|
77
|
+
Allowed keywords in kwargs are 'design_path', 'design' and 'offdesign'.
|
|
78
|
+
Additional keywords depend on the type of component you want to create.
|
|
79
|
+
|
|
80
|
+
Example
|
|
81
|
+
-------
|
|
82
|
+
Basic example for a setting up a tespy.components.components.component
|
|
83
|
+
object. This example does not run a tespy calculation.
|
|
84
|
+
|
|
85
|
+
>>> from tespy.components.components import component
|
|
86
|
+
>>> comp = component('myComponent')
|
|
87
|
+
>>> type(comp)
|
|
88
|
+
<class 'tespy.components.components.component'>
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(self, label, **kwargs):
|
|
92
|
+
|
|
93
|
+
# check if components label is of type str and for prohibited chars
|
|
94
|
+
if not isinstance(label, str):
|
|
95
|
+
msg = 'Component label must be of type str!'
|
|
96
|
+
logging.error(msg)
|
|
97
|
+
raise ValueError(msg)
|
|
98
|
+
|
|
99
|
+
elif len([x for x in [';', ',', '.'] if x in label]) > 0:
|
|
100
|
+
msg = (
|
|
101
|
+
'You must not use ' + str([';', ',', '.']) + ' in label (' +
|
|
102
|
+
str(self.component()) + ').')
|
|
103
|
+
logging.error(msg)
|
|
104
|
+
raise ValueError(msg)
|
|
105
|
+
|
|
106
|
+
else:
|
|
107
|
+
self.label = label
|
|
108
|
+
|
|
109
|
+
# defaults
|
|
110
|
+
self.new_design = True
|
|
111
|
+
self.design_path = None
|
|
112
|
+
self.design = []
|
|
113
|
+
self.offdesign = []
|
|
114
|
+
self.local_design = False
|
|
115
|
+
self.local_offdesign = False
|
|
116
|
+
self.char_warnings = True
|
|
117
|
+
self.printout = True
|
|
118
|
+
|
|
119
|
+
# add container for components attributes
|
|
120
|
+
self.variables = self.attr()
|
|
121
|
+
self.__dict__.update(self.variables)
|
|
122
|
+
self.set_attr(**kwargs)
|
|
123
|
+
|
|
124
|
+
def set_attr(self, **kwargs):
|
|
125
|
+
r"""
|
|
126
|
+
Set, reset or unset attributes of a component for provided arguments.
|
|
127
|
+
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
design : list
|
|
131
|
+
List containing design parameters (stated as String).
|
|
132
|
+
|
|
133
|
+
offdesign : list
|
|
134
|
+
List containing offdesign parameters (stated as String).
|
|
135
|
+
|
|
136
|
+
design_path: str
|
|
137
|
+
Path to the components design case.
|
|
138
|
+
|
|
139
|
+
**kwargs :
|
|
140
|
+
See the class documentation of desired component for available
|
|
141
|
+
keywords.
|
|
142
|
+
|
|
143
|
+
Note
|
|
144
|
+
----
|
|
145
|
+
Allowed keywords in kwargs are obtained from class documentation as all
|
|
146
|
+
components share the
|
|
147
|
+
:func:`tespy.components.components.component.set_attr` method.
|
|
148
|
+
"""
|
|
149
|
+
# set specified values
|
|
150
|
+
for key in kwargs:
|
|
151
|
+
if key in self.variables.keys():
|
|
152
|
+
if kwargs[key] is None:
|
|
153
|
+
self.get_attr(key).set_attr(is_set=False)
|
|
154
|
+
try:
|
|
155
|
+
self.get_attr(key).set_attr(is_var=False)
|
|
156
|
+
except KeyError:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
# data container specification
|
|
160
|
+
elif isinstance(kwargs[key], data_container):
|
|
161
|
+
if isinstance(kwargs[key], type(self.get_attr(key))):
|
|
162
|
+
self.__dict__.update({key: kwargs[key]})
|
|
163
|
+
|
|
164
|
+
else:
|
|
165
|
+
msg = (
|
|
166
|
+
'The keyword ' + key + ' expects a data_container '
|
|
167
|
+
'of type ' + str(type(self.get_attr(key))) +
|
|
168
|
+
', a data_container of type ' +
|
|
169
|
+
str(type(kwargs[key])) + ' was supplied.')
|
|
170
|
+
logging.error(msg)
|
|
171
|
+
raise TypeError(msg)
|
|
172
|
+
|
|
173
|
+
elif (isinstance(self.get_attr(key), dc_cp) or
|
|
174
|
+
isinstance(self.get_attr(key), dc_simple)):
|
|
175
|
+
# value specification for component properties
|
|
176
|
+
if (isinstance(kwargs[key], float) or
|
|
177
|
+
isinstance(kwargs[key], np.float64) or
|
|
178
|
+
isinstance(kwargs[key], np.int64) or
|
|
179
|
+
isinstance(kwargs[key], int)):
|
|
180
|
+
if np.isnan(kwargs[key]):
|
|
181
|
+
self.get_attr(key).set_attr(is_set=False)
|
|
182
|
+
if isinstance(self.get_attr(key), dc_cp):
|
|
183
|
+
self.get_attr(key).set_attr(is_var=False)
|
|
184
|
+
|
|
185
|
+
else:
|
|
186
|
+
self.get_attr(key).set_attr(
|
|
187
|
+
val=kwargs[key], is_set=True)
|
|
188
|
+
if isinstance(self.get_attr(key), dc_cp):
|
|
189
|
+
self.get_attr(key).set_attr(is_var=False)
|
|
190
|
+
|
|
191
|
+
elif (kwargs[key] == 'var' and
|
|
192
|
+
isinstance(self.get_attr(key), dc_cp)):
|
|
193
|
+
self.get_attr(key).set_attr(is_set=True, is_var=True)
|
|
194
|
+
|
|
195
|
+
elif isinstance(self.get_attr(key), dc_simple):
|
|
196
|
+
self.get_attr(key).set_attr(
|
|
197
|
+
val=kwargs[key], is_set=True)
|
|
198
|
+
|
|
199
|
+
# invalid datatype for keyword
|
|
200
|
+
else:
|
|
201
|
+
msg = (
|
|
202
|
+
'Bad datatype for keyword argument ' + key +
|
|
203
|
+
' at ' + self.label + '.')
|
|
204
|
+
logging.error(msg)
|
|
205
|
+
raise TypeError(msg)
|
|
206
|
+
|
|
207
|
+
elif (isinstance(self.get_attr(key), dc_cc) or
|
|
208
|
+
isinstance(self.get_attr(key), dc_cm)):
|
|
209
|
+
# value specification for characteristics
|
|
210
|
+
if (isinstance(kwargs[key], char_line) or
|
|
211
|
+
isinstance(kwargs[key], char_map) or
|
|
212
|
+
isinstance(kwargs[key], compressor_map)):
|
|
213
|
+
self.get_attr(key).func = kwargs[key]
|
|
214
|
+
|
|
215
|
+
# invalid datatype for keyword
|
|
216
|
+
else:
|
|
217
|
+
msg = (
|
|
218
|
+
'Bad datatype for keyword argument ' + key +
|
|
219
|
+
' at ' + self.label + '.')
|
|
220
|
+
logging.error(msg)
|
|
221
|
+
raise TypeError(msg)
|
|
222
|
+
|
|
223
|
+
elif isinstance(self.get_attr(key), dc_gcp):
|
|
224
|
+
# value specification of grouped component parameter method
|
|
225
|
+
if isinstance(kwargs[key], str):
|
|
226
|
+
self.get_attr(key).method = kwargs[key]
|
|
227
|
+
|
|
228
|
+
# invalid datatype for keyword
|
|
229
|
+
else:
|
|
230
|
+
msg = (
|
|
231
|
+
'Bad datatype for keyword argument ' + key +
|
|
232
|
+
' at ' + self.label + '.')
|
|
233
|
+
logging.error(msg)
|
|
234
|
+
raise TypeError(msg)
|
|
235
|
+
|
|
236
|
+
elif key in ['design', 'offdesign']:
|
|
237
|
+
if not isinstance(kwargs[key], list):
|
|
238
|
+
msg = (
|
|
239
|
+
'Please provide the ' + key + ' parameters as list '
|
|
240
|
+
'at ' + self.label + '.')
|
|
241
|
+
logging.error(msg)
|
|
242
|
+
raise TypeError(msg)
|
|
243
|
+
if set(kwargs[key]).issubset(list(self.variables.keys())):
|
|
244
|
+
self.__dict__.update({key: kwargs[key]})
|
|
245
|
+
|
|
246
|
+
else:
|
|
247
|
+
msg = (
|
|
248
|
+
'Available parameters for (off-)design specification '
|
|
249
|
+
'are: ' + str(list(self.variables.keys())) + ' at ' +
|
|
250
|
+
self.label + '.')
|
|
251
|
+
logging.error(msg)
|
|
252
|
+
raise ValueError(msg)
|
|
253
|
+
|
|
254
|
+
elif key in ['local_design', 'local_offdesign', 'printout']:
|
|
255
|
+
if not isinstance(kwargs[key], bool):
|
|
256
|
+
msg = (
|
|
257
|
+
'Please provide the parameter ' + key + ' as boolean '
|
|
258
|
+
'at component ' + self.label + '.')
|
|
259
|
+
logging.error(msg)
|
|
260
|
+
raise TypeError(msg)
|
|
261
|
+
|
|
262
|
+
else:
|
|
263
|
+
self.__dict__.update({key: kwargs[key]})
|
|
264
|
+
|
|
265
|
+
elif key == 'design_path':
|
|
266
|
+
if isinstance(kwargs[key], str):
|
|
267
|
+
self.__dict__.update({key: kwargs[key]})
|
|
268
|
+
elif kwargs[key] is None:
|
|
269
|
+
self.design_path = None
|
|
270
|
+
elif np.isnan(kwargs[key]):
|
|
271
|
+
self.design_path = None
|
|
272
|
+
else:
|
|
273
|
+
msg = (
|
|
274
|
+
'Please provide the design_path parameter as string. '
|
|
275
|
+
'For unsetting use np.nan or None.')
|
|
276
|
+
logging.error(msg)
|
|
277
|
+
raise TypeError(msg)
|
|
278
|
+
|
|
279
|
+
self.new_design = True
|
|
280
|
+
|
|
281
|
+
# invalid keyword
|
|
282
|
+
else:
|
|
283
|
+
msg = (
|
|
284
|
+
'Component ' + self.label + ' has no attribute ' +
|
|
285
|
+
str(key) + '.')
|
|
286
|
+
logging.error(msg)
|
|
287
|
+
raise KeyError(msg)
|
|
288
|
+
|
|
289
|
+
def get_attr(self, key):
|
|
290
|
+
r"""
|
|
291
|
+
Get the value of a component's attribute.
|
|
292
|
+
|
|
293
|
+
Parameters
|
|
294
|
+
----------
|
|
295
|
+
key : str
|
|
296
|
+
The attribute you want to retrieve.
|
|
297
|
+
|
|
298
|
+
Returns
|
|
299
|
+
-------
|
|
300
|
+
out :
|
|
301
|
+
Value of specified attribute.
|
|
302
|
+
"""
|
|
303
|
+
if key in self.__dict__:
|
|
304
|
+
return self.__dict__[key]
|
|
305
|
+
else:
|
|
306
|
+
msg = ('Component ' + self.label + ' has no attribute \"' +
|
|
307
|
+
key + '\".')
|
|
308
|
+
logging.error(msg)
|
|
309
|
+
raise KeyError(msg)
|
|
310
|
+
|
|
311
|
+
def comp_init(self, nw):
|
|
312
|
+
r"""
|
|
313
|
+
Perform component initialization in network preprocessing.
|
|
314
|
+
|
|
315
|
+
Parameters
|
|
316
|
+
----------
|
|
317
|
+
nw : tespy.networks.network
|
|
318
|
+
Network this component is integrated in.
|
|
319
|
+
"""
|
|
320
|
+
self.num_nw_fluids = len(nw.fluids)
|
|
321
|
+
self.nw_fluids = nw.fluids
|
|
322
|
+
self.num_nw_vars = self.num_nw_fluids + 3
|
|
323
|
+
self.it = 0
|
|
324
|
+
self.residual = []
|
|
325
|
+
self.jacobian = None
|
|
326
|
+
self.num_eq = 0
|
|
327
|
+
self.vars = {}
|
|
328
|
+
self.num_vars = 0
|
|
329
|
+
|
|
330
|
+
for key, val in self.variables.items():
|
|
331
|
+
if isinstance(val, dc_cp):
|
|
332
|
+
if self.get_attr(key).is_var:
|
|
333
|
+
self.get_attr(key).var_pos = self.num_vars
|
|
334
|
+
self.num_vars += 1
|
|
335
|
+
self.vars[self.get_attr(key)] = key
|
|
336
|
+
|
|
337
|
+
# characteristics creation
|
|
338
|
+
elif isinstance(val, dc_cc):
|
|
339
|
+
if self.get_attr(key).func is None:
|
|
340
|
+
try:
|
|
341
|
+
self.get_attr(key).func = ldc(
|
|
342
|
+
self.component(), key, 'DEFAULT', char_line)
|
|
343
|
+
except KeyError:
|
|
344
|
+
self.get_attr(key).func = char_line(x=[0, 1], y=[1, 1])
|
|
345
|
+
|
|
346
|
+
if self.char_warnings is True:
|
|
347
|
+
msg = (
|
|
348
|
+
'Created characteristic line for parameter ' +
|
|
349
|
+
key + ' at component ' + self.label + ' from '
|
|
350
|
+
'default data.\nYou can specify your own data '
|
|
351
|
+
'using component.' + key +
|
|
352
|
+
'.set_attr(func=custom_char).\nIf you want to '
|
|
353
|
+
'disable these warnings use '
|
|
354
|
+
'component.char_warnings=False.')
|
|
355
|
+
logging.warning(msg)
|
|
356
|
+
|
|
357
|
+
msg = (
|
|
358
|
+
'The component ' + self.label + ' has ' + str(self.num_vars) +
|
|
359
|
+
' custom variables.')
|
|
360
|
+
logging.debug(msg)
|
|
361
|
+
|
|
362
|
+
@staticmethod
|
|
363
|
+
def attr():
|
|
364
|
+
return {}
|
|
365
|
+
|
|
366
|
+
@staticmethod
|
|
367
|
+
def inlets():
|
|
368
|
+
return []
|
|
369
|
+
|
|
370
|
+
@staticmethod
|
|
371
|
+
def outlets():
|
|
372
|
+
return []
|
|
373
|
+
|
|
374
|
+
def equations(self):
|
|
375
|
+
return
|
|
376
|
+
|
|
377
|
+
def calc_bus_efficiency(self, bus):
|
|
378
|
+
r"""
|
|
379
|
+
Return the busses' efficiency.
|
|
380
|
+
|
|
381
|
+
Parameters
|
|
382
|
+
----------
|
|
383
|
+
bus : tespy.connections.bus
|
|
384
|
+
Bus to calculate the efficiency value on.
|
|
385
|
+
|
|
386
|
+
Returns
|
|
387
|
+
-------
|
|
388
|
+
efficiency : float
|
|
389
|
+
Efficiency value of the bus.
|
|
390
|
+
|
|
391
|
+
.. math::
|
|
392
|
+
|
|
393
|
+
\eta_\mathrm{bus} = \begin{cases}
|
|
394
|
+
\eta\left(
|
|
395
|
+
\frac{\dot{E}_\mathrm{bus}}{\dot{E}_\mathrm{bus,ref}}\right) &
|
|
396
|
+
\text{bus base = 'bus'}\\
|
|
397
|
+
\eta\left(
|
|
398
|
+
\frac{\dot{E}_\mathrm{component}}
|
|
399
|
+
{\dot{E}_\mathrm{component,ref}}\right) &
|
|
400
|
+
\text{bus base = 'component'}
|
|
401
|
+
\end{cases}
|
|
402
|
+
|
|
403
|
+
Note
|
|
404
|
+
----
|
|
405
|
+
If the base value of the bus is the bus value itself, a newton
|
|
406
|
+
iteration is used to find the bus value satisfying the corresponding
|
|
407
|
+
equation (case 1).
|
|
408
|
+
"""
|
|
409
|
+
b = bus.comps.loc[self]
|
|
410
|
+
comp_val = self.bus_func(b)
|
|
411
|
+
if np.isnan(b['P_ref']):
|
|
412
|
+
expr = 1
|
|
413
|
+
else:
|
|
414
|
+
if b['base'] == 'component':
|
|
415
|
+
expr = abs(comp_val / b['P_ref'])
|
|
416
|
+
else:
|
|
417
|
+
bus_value = newton(
|
|
418
|
+
bus_char_evaluation,
|
|
419
|
+
bus_char_derivative,
|
|
420
|
+
[comp_val, b['P_ref'], b['char']], 0,
|
|
421
|
+
val0=b['P_ref'], valmin=-1e15, valmax=1e15)
|
|
422
|
+
expr = bus_value / b['P_ref']
|
|
423
|
+
|
|
424
|
+
return b['char'].evaluate(expr)
|
|
425
|
+
|
|
426
|
+
def calc_bus_value(self, bus):
|
|
427
|
+
r"""
|
|
428
|
+
Return the busses' value of the component's energy transfer.
|
|
429
|
+
|
|
430
|
+
Parameters
|
|
431
|
+
----------
|
|
432
|
+
bus : tespy.connections.bus
|
|
433
|
+
Bus to calculate energy transfer on.
|
|
434
|
+
|
|
435
|
+
Returns
|
|
436
|
+
-------
|
|
437
|
+
bus_value : float
|
|
438
|
+
Value of the energy transfer on the specified bus.
|
|
439
|
+
|
|
440
|
+
.. math::
|
|
441
|
+
|
|
442
|
+
\dot{E}_\mathrm{bus} = \begin{cases}
|
|
443
|
+
\frac{\dot{E}_\mathrm{component}}{f\left(
|
|
444
|
+
\frac{\dot{E}_\mathrm{bus}}{\dot{E}_\mathrm{bus,ref}}\right)} &
|
|
445
|
+
\text{bus base = 'bus'}\\
|
|
446
|
+
\dot{E}_\mathrm{component} \cdot f\left(
|
|
447
|
+
\frac{\dot{E}_\mathrm{component}}
|
|
448
|
+
{\dot{E}_\mathrm{component,ref}}\right) &
|
|
449
|
+
\text{bus base = 'component'}
|
|
450
|
+
\end{cases}
|
|
451
|
+
|
|
452
|
+
Note
|
|
453
|
+
----
|
|
454
|
+
If the base value of the bus is the bus value itself, a newton
|
|
455
|
+
iteration is used to find the bus value satisfying the corresponding
|
|
456
|
+
equation (case 1).
|
|
457
|
+
"""
|
|
458
|
+
b = bus.comps.loc[self]
|
|
459
|
+
comp_val = self.bus_func(b)
|
|
460
|
+
if np.isnan(b['P_ref']):
|
|
461
|
+
expr = 1
|
|
462
|
+
else:
|
|
463
|
+
if b['base'] == 'component':
|
|
464
|
+
expr = abs(comp_val / b['P_ref'])
|
|
465
|
+
else:
|
|
466
|
+
bus_value = newton(
|
|
467
|
+
bus_char_evaluation,
|
|
468
|
+
bus_char_derivative,
|
|
469
|
+
[comp_val, b['P_ref'], b['char']], 0,
|
|
470
|
+
val0=b['P_ref'], valmin=-1e15, valmax=1e15)
|
|
471
|
+
return bus_value
|
|
472
|
+
|
|
473
|
+
if b['base'] == 'component':
|
|
474
|
+
return comp_val * b['char'].evaluate(expr)
|
|
475
|
+
else:
|
|
476
|
+
return comp_val / b['char'].evaluate(expr)
|
|
477
|
+
|
|
478
|
+
def derivatives(self, increment_filter):
|
|
479
|
+
return
|
|
480
|
+
|
|
481
|
+
def initialise_source(self, c, key):
|
|
482
|
+
r"""
|
|
483
|
+
Return a starting value for pressure and enthalpy at outlet.
|
|
484
|
+
|
|
485
|
+
Parameters
|
|
486
|
+
----------
|
|
487
|
+
c : tespy.connections.connection
|
|
488
|
+
Connection to perform initialisation on.
|
|
489
|
+
|
|
490
|
+
key : str
|
|
491
|
+
Fluid property to retrieve.
|
|
492
|
+
|
|
493
|
+
Returns
|
|
494
|
+
-------
|
|
495
|
+
val : float
|
|
496
|
+
Starting value for pressure/enthalpy in SI units.
|
|
497
|
+
|
|
498
|
+
.. math::
|
|
499
|
+
|
|
500
|
+
val = \begin{cases}
|
|
501
|
+
0 & \text{key = 'p'}\\
|
|
502
|
+
0 & \text{key = 'h'}
|
|
503
|
+
\end{cases}
|
|
504
|
+
"""
|
|
505
|
+
return 0
|
|
506
|
+
|
|
507
|
+
def initialise_target(self, c, key):
|
|
508
|
+
r"""
|
|
509
|
+
Return a starting value for pressure and enthalpy at inlet.
|
|
510
|
+
|
|
511
|
+
Parameters
|
|
512
|
+
----------
|
|
513
|
+
c : tespy.connections.connection
|
|
514
|
+
Connection to perform initialisation on.
|
|
515
|
+
|
|
516
|
+
key : str
|
|
517
|
+
Fluid property to retrieve.
|
|
518
|
+
|
|
519
|
+
Returns
|
|
520
|
+
-------
|
|
521
|
+
val : float
|
|
522
|
+
Starting value for pressure/enthalpy in SI units.
|
|
523
|
+
|
|
524
|
+
.. math::
|
|
525
|
+
|
|
526
|
+
val = \begin{cases}
|
|
527
|
+
0 & \text{key = 'p'}\\
|
|
528
|
+
0 & \text{key = 'h'}
|
|
529
|
+
\end{cases}
|
|
530
|
+
"""
|
|
531
|
+
return 0
|
|
532
|
+
|
|
533
|
+
def set_parameters(self, mode, data):
|
|
534
|
+
r"""
|
|
535
|
+
Set or unset design values of component parameters.
|
|
536
|
+
|
|
537
|
+
Parameters
|
|
538
|
+
----------
|
|
539
|
+
mode : str
|
|
540
|
+
Setting component design values for :code:`mode='offdesign'`
|
|
541
|
+
and unsetting them for :code:`mode='design'`.
|
|
542
|
+
|
|
543
|
+
df : pandas.core.series.Series
|
|
544
|
+
Series containing the component parameters.
|
|
545
|
+
"""
|
|
546
|
+
if mode == 'design' or self.local_design is True:
|
|
547
|
+
self.new_design = True
|
|
548
|
+
|
|
549
|
+
for key, dc in self.variables.items():
|
|
550
|
+
if isinstance(dc, dc_cp):
|
|
551
|
+
if ((mode == 'offdesign' and self.local_design is False) or
|
|
552
|
+
(mode == 'design' and self.local_offdesign is True)):
|
|
553
|
+
self.get_attr(key).design = data[key]
|
|
554
|
+
|
|
555
|
+
else:
|
|
556
|
+
self.get_attr(key).design = np.nan
|
|
557
|
+
|
|
558
|
+
def calc_parameters(self):
|
|
559
|
+
r"""Postprocessing parameter calculation."""
|
|
560
|
+
return
|
|
561
|
+
|
|
562
|
+
def check_parameter_bounds(self):
|
|
563
|
+
for p, data in self.variables.items():
|
|
564
|
+
if isinstance(data, dc_cp):
|
|
565
|
+
val = self.get_attr(p).val
|
|
566
|
+
if round(val, 6) > data.max_val:
|
|
567
|
+
msg = (
|
|
568
|
+
'Invalid value for ' + p + ': ' + p + ' = ' +
|
|
569
|
+
str(val) + ' above maximum value (' +
|
|
570
|
+
str(data.max_val) + ') at component ' + self.label +
|
|
571
|
+
'.')
|
|
572
|
+
logging.warning(msg)
|
|
573
|
+
|
|
574
|
+
elif round(val, 6) < data.min_val:
|
|
575
|
+
msg = (
|
|
576
|
+
'Invalid value for ' + p + ': ' + p + ' = ' +
|
|
577
|
+
str(val) + ' below minimum value (' +
|
|
578
|
+
str(data.min_val) + ') at component ' + self.label +
|
|
579
|
+
'.')
|
|
580
|
+
logging.warning(msg)
|
|
581
|
+
|
|
582
|
+
def initialise_fluids(self, nw):
|
|
583
|
+
return
|
|
584
|
+
|
|
585
|
+
def convergence_check(self, nw):
|
|
586
|
+
return
|
|
587
|
+
|
|
588
|
+
# %%
|
|
589
|
+
|
|
590
|
+
def fluid_func(self):
|
|
591
|
+
r"""
|
|
592
|
+
Calculate the vector of residual values for fluid balance equations.
|
|
593
|
+
|
|
594
|
+
Returns
|
|
595
|
+
-------
|
|
596
|
+
residual : list
|
|
597
|
+
Vector of residual values for component's fluid balance.
|
|
598
|
+
|
|
599
|
+
.. math::
|
|
600
|
+
|
|
601
|
+
0 = fluid_{i,in_{j}} - fluid_{i,out_{j}} \;
|
|
602
|
+
\forall i \in \mathrm{fluid}, \; \forall j \in inlets/outlets
|
|
603
|
+
"""
|
|
604
|
+
residual = []
|
|
605
|
+
|
|
606
|
+
for i in range(self.num_i):
|
|
607
|
+
for fluid, x in self.inl[0].fluid.val.items():
|
|
608
|
+
residual += [x - self.outl[0].fluid.val[fluid]]
|
|
609
|
+
return residual
|
|
610
|
+
|
|
611
|
+
def fluid_deriv(self):
|
|
612
|
+
r"""
|
|
613
|
+
Calculate partial derivatives for all fluid balance equations.
|
|
614
|
+
|
|
615
|
+
Returns
|
|
616
|
+
-------
|
|
617
|
+
deriv : list
|
|
618
|
+
Matrix with partial derivatives for the fluid equations.
|
|
619
|
+
"""
|
|
620
|
+
deriv = np.zeros((self.num_nw_fluids * self.num_i,
|
|
621
|
+
2 * self.num_i + self.num_vars,
|
|
622
|
+
self.num_nw_vars))
|
|
623
|
+
for i in range(self.num_i):
|
|
624
|
+
for j in range(self.num_nw_fluids):
|
|
625
|
+
deriv[i * self.num_nw_fluids + j, i, j + 3] = 1
|
|
626
|
+
deriv[i * self.num_nw_fluids + j, self.num_i + i, j + 3] = -1
|
|
627
|
+
return deriv
|
|
628
|
+
|
|
629
|
+
# %%
|
|
630
|
+
|
|
631
|
+
def mass_flow_func(self):
|
|
632
|
+
r"""
|
|
633
|
+
Calculate the residual value for mass flow balance equation.
|
|
634
|
+
|
|
635
|
+
Returns
|
|
636
|
+
-------
|
|
637
|
+
residual : list
|
|
638
|
+
Vector with residual value for component's mass flow balance.
|
|
639
|
+
|
|
640
|
+
.. math::
|
|
641
|
+
0 = \sum \dot{m}_{in,i} - \sum \dot{m}_{out,j} \;
|
|
642
|
+
\forall i \in inlets, \forall j \in outlets
|
|
643
|
+
"""
|
|
644
|
+
res = 0
|
|
645
|
+
for i in self.inl:
|
|
646
|
+
res += i.m.val_SI
|
|
647
|
+
for o in self.outl:
|
|
648
|
+
res -= o.m.val_SI
|
|
649
|
+
return res
|
|
650
|
+
|
|
651
|
+
def mass_flow_deriv(self):
|
|
652
|
+
r"""
|
|
653
|
+
Calculate the partial derivatives for all mass flow balance equations.
|
|
654
|
+
|
|
655
|
+
Returns
|
|
656
|
+
-------
|
|
657
|
+
deriv : list
|
|
658
|
+
Matrix with partial derivatives for the mass flow balance
|
|
659
|
+
equations.
|
|
660
|
+
"""
|
|
661
|
+
deriv = np.zeros((1, self.num_i + self.num_o +
|
|
662
|
+
self.num_vars, self.num_nw_vars))
|
|
663
|
+
for i in range(self.num_i):
|
|
664
|
+
deriv[0, i, 0] = 1
|
|
665
|
+
for j in range(self.num_o):
|
|
666
|
+
deriv[0, j + i + 1, 0] = -1
|
|
667
|
+
return deriv
|
|
668
|
+
|
|
669
|
+
# %%
|
|
670
|
+
|
|
671
|
+
def numeric_deriv(self, func, dx, pos, **kwargs):
|
|
672
|
+
r"""
|
|
673
|
+
Calculate partial derivative of the function func to dx.
|
|
674
|
+
|
|
675
|
+
Parameters
|
|
676
|
+
----------
|
|
677
|
+
func : function
|
|
678
|
+
Function :math:`f` to calculate the partial derivative for.
|
|
679
|
+
|
|
680
|
+
dx : str
|
|
681
|
+
Partial derivative.
|
|
682
|
+
|
|
683
|
+
pos : int
|
|
684
|
+
Position of connection regarding to inlets and outlet of the
|
|
685
|
+
component, logic: ['in1', 'in2', ..., 'out1', ...] ->
|
|
686
|
+
0, 1, ..., n, n + 1, ..., n + m
|
|
687
|
+
|
|
688
|
+
Returns
|
|
689
|
+
-------
|
|
690
|
+
deriv : float/list
|
|
691
|
+
Partial derivative(s) of the function :math:`f` to variable(s)
|
|
692
|
+
:math:`x`.
|
|
693
|
+
|
|
694
|
+
.. math::
|
|
695
|
+
|
|
696
|
+
\frac{\partial f}{\partial x} = \frac{f(x + d) + f(x - d)}{2 d}
|
|
697
|
+
"""
|
|
698
|
+
dm, dp, dh, df = 0, 0, 0, 0
|
|
699
|
+
if dx == 'm':
|
|
700
|
+
dm = 1e-4
|
|
701
|
+
elif dx == 'p':
|
|
702
|
+
dp = 1e-1
|
|
703
|
+
elif dx == 'h':
|
|
704
|
+
dh = 1e-1
|
|
705
|
+
else:
|
|
706
|
+
df = 1e-5
|
|
707
|
+
|
|
708
|
+
if dx == 'fluid':
|
|
709
|
+
deriv = []
|
|
710
|
+
for f in self.inl[0].fluid.val.keys():
|
|
711
|
+
val = (self.inl + self.outl)[pos].fluid.val[f]
|
|
712
|
+
exp = 0
|
|
713
|
+
if (self.inl + self.outl)[pos].fluid.val[f] + df <= 1:
|
|
714
|
+
(self.inl + self.outl)[pos].fluid.val[f] += df
|
|
715
|
+
else:
|
|
716
|
+
(self.inl + self.outl)[pos].fluid.val[f] = 1
|
|
717
|
+
exp += func(**kwargs)
|
|
718
|
+
if (self.inl + self.outl)[pos].fluid.val[f] - 2 * df >= 0:
|
|
719
|
+
(self.inl + self.outl)[pos].fluid.val[f] -= 2 * df
|
|
720
|
+
else:
|
|
721
|
+
(self.inl + self.outl)[pos].fluid.val[f] = 0
|
|
722
|
+
exp -= func(**kwargs)
|
|
723
|
+
(self.inl + self.outl)[pos].fluid.val[f] = val
|
|
724
|
+
|
|
725
|
+
deriv += [exp / (2 * (dm + dp + dh + df))]
|
|
726
|
+
|
|
727
|
+
elif dx in ['m', 'p', 'h']:
|
|
728
|
+
exp = 0
|
|
729
|
+
(self.inl + self.outl)[pos].m.val_SI += dm
|
|
730
|
+
(self.inl + self.outl)[pos].p.val_SI += dp
|
|
731
|
+
(self.inl + self.outl)[pos].h.val_SI += dh
|
|
732
|
+
exp += func(**kwargs)
|
|
733
|
+
|
|
734
|
+
(self.inl + self.outl)[pos].m.val_SI -= 2 * dm
|
|
735
|
+
(self.inl + self.outl)[pos].p.val_SI -= 2 * dp
|
|
736
|
+
(self.inl + self.outl)[pos].h.val_SI -= 2 * dh
|
|
737
|
+
exp -= func(**kwargs)
|
|
738
|
+
deriv = exp / (2 * (dm + dp + dh + df))
|
|
739
|
+
|
|
740
|
+
(self.inl + self.outl)[pos].m.val_SI += dm
|
|
741
|
+
(self.inl + self.outl)[pos].p.val_SI += dp
|
|
742
|
+
(self.inl + self.outl)[pos].h.val_SI += dh
|
|
743
|
+
|
|
744
|
+
else:
|
|
745
|
+
d = self.get_attr(dx).d
|
|
746
|
+
exp = 0
|
|
747
|
+
self.get_attr(dx).val += d
|
|
748
|
+
exp += func(**kwargs)
|
|
749
|
+
|
|
750
|
+
self.get_attr(dx).val -= 2 * d
|
|
751
|
+
exp -= func(**kwargs)
|
|
752
|
+
deriv = exp / (2 * d)
|
|
753
|
+
|
|
754
|
+
self.get_attr(dx).val += d
|
|
755
|
+
|
|
756
|
+
return deriv
|
|
757
|
+
|
|
758
|
+
# %%
|
|
759
|
+
|
|
760
|
+
def zeta_func(self, zeta='', inconn=0, outconn=0):
|
|
761
|
+
r"""
|
|
762
|
+
Calculate residual value of :math:`\zeta`-function.
|
|
763
|
+
|
|
764
|
+
Parameters
|
|
765
|
+
----------
|
|
766
|
+
zeta : str
|
|
767
|
+
Component parameter to evaluate the zeta_func on, e.g.
|
|
768
|
+
:code:`zeta1`.
|
|
769
|
+
|
|
770
|
+
inconn : int
|
|
771
|
+
Connection index of inlet.
|
|
772
|
+
|
|
773
|
+
outconn : int
|
|
774
|
+
Connection index of outlet.
|
|
775
|
+
|
|
776
|
+
Returns
|
|
777
|
+
-------
|
|
778
|
+
val : float
|
|
779
|
+
Residual value of function.
|
|
780
|
+
|
|
781
|
+
.. math::
|
|
782
|
+
|
|
783
|
+
val = \begin{cases}
|
|
784
|
+
p_{in} - p_{out} & |\dot{m}| < \epsilon \\
|
|
785
|
+
\frac{\zeta}{D^4} - \frac{(p_{in} - p_{out}) \cdot \pi^2}
|
|
786
|
+
{8 \cdot \dot{m}_{in} \cdot |\dot{m}_{in}| \cdot \frac{v_{in} +
|
|
787
|
+
v_{out}}{2}} &
|
|
788
|
+
|\dot{m}| > \epsilon
|
|
789
|
+
\end{cases}
|
|
790
|
+
|
|
791
|
+
Note
|
|
792
|
+
----
|
|
793
|
+
The zeta value is caluclated on the basis of a given pressure loss at
|
|
794
|
+
a given flow rate in the design case. As the cross sectional area A
|
|
795
|
+
will not change, it is possible to handle the equation in this way:
|
|
796
|
+
|
|
797
|
+
.. math::
|
|
798
|
+
|
|
799
|
+
\frac{\zeta}{D^4} = \frac{\Delta p \cdot \pi^2}
|
|
800
|
+
{8 \cdot \dot{m}^2 \cdot v}
|
|
801
|
+
"""
|
|
802
|
+
zeta = self.get_attr(zeta).val
|
|
803
|
+
i = self.inl[inconn].to_flow()
|
|
804
|
+
o = self.outl[outconn].to_flow()
|
|
805
|
+
|
|
806
|
+
if abs(i[0]) < 1e-4:
|
|
807
|
+
return i[1] - o[1]
|
|
808
|
+
|
|
809
|
+
else:
|
|
810
|
+
v_i = v_mix_ph(i, T0=self.inl[inconn].T.val_SI)
|
|
811
|
+
v_o = v_mix_ph(o, T0=self.outl[outconn].T.val_SI)
|
|
812
|
+
return (zeta - (i[1] - o[1]) * np.pi ** 2 /
|
|
813
|
+
(8 * abs(i[0]) * i[0] * (v_i + v_o) / 2))
|