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,729 @@
|
|
|
1
|
+
# -*- coding: utf-8
|
|
2
|
+
|
|
3
|
+
"""Module for custom components.
|
|
4
|
+
|
|
5
|
+
Components in this module:
|
|
6
|
+
|
|
7
|
+
- :func:`tespy.components.customs.orc_evaporator`
|
|
8
|
+
|
|
9
|
+
This file is part of project TESPy (github.com/oemof/tespy). It's copyrighted
|
|
10
|
+
by the contributors recorded in the version control history of the file,
|
|
11
|
+
available from its original location tespy/components/customs.py
|
|
12
|
+
|
|
13
|
+
SPDX-License-Identifier: MIT
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
from tespy.components.components import component
|
|
19
|
+
from tespy.tools.data_containers import dc_cp
|
|
20
|
+
from tespy.tools.data_containers import dc_simple
|
|
21
|
+
from tespy.tools.fluid_properties import dh_mix_dpQ
|
|
22
|
+
from tespy.tools.fluid_properties import h_mix_pQ
|
|
23
|
+
from tespy.tools.fluid_properties import h_mix_pT
|
|
24
|
+
from tespy.tools.fluid_properties import s_mix_ph
|
|
25
|
+
from tespy.tools.fluid_properties import v_mix_ph
|
|
26
|
+
|
|
27
|
+
# %%
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class orc_evaporator(component):
|
|
31
|
+
r"""Evaporator of the geothermal Organic Rankine Cycle (ORC).
|
|
32
|
+
|
|
33
|
+
Generally, the hot side of the geo-fluid from the geothermal wells deliver
|
|
34
|
+
two phases: steam and brine. In order to fully use the energy of the
|
|
35
|
+
geo-fluid, there are 2 inlets at the hot side.
|
|
36
|
+
|
|
37
|
+
The ORC evaporator represents counter current evaporators. Both, two hot
|
|
38
|
+
and one cold side of the evaporator, are simulated.
|
|
39
|
+
|
|
40
|
+
Equations
|
|
41
|
+
|
|
42
|
+
**mandatory equations**
|
|
43
|
+
|
|
44
|
+
- :func:`tespy.components.components.component.fluid_func`
|
|
45
|
+
- :func:`tespy.components.customs.orc_evaporator.mass_flow_func`
|
|
46
|
+
|
|
47
|
+
- :func:`tespy.components.customs.orc_evaporator.energy_func`
|
|
48
|
+
|
|
49
|
+
.. math::
|
|
50
|
+
|
|
51
|
+
0 = p_{1,in} \cdot pr1 - p_{1,out}\\
|
|
52
|
+
0 = p_{2,in} \cdot pr2 - p_{2,out}\\
|
|
53
|
+
0 = p_{3,in} \cdot pr3 - p_{3,out}
|
|
54
|
+
|
|
55
|
+
- hot side steam :func:`tespy.components.components.component.zeta_func`
|
|
56
|
+
- hot side brine :func:`tespy.components.components.component.zeta_func`
|
|
57
|
+
- worling fluid :func:`tespy.components.components.component.zeta_func`
|
|
58
|
+
|
|
59
|
+
**mandatory equations at outlet of the steam
|
|
60
|
+
from geothermal heat source side**
|
|
61
|
+
|
|
62
|
+
.. math::
|
|
63
|
+
|
|
64
|
+
0 = h_{1,out} - h\left(p, x=0 \right)\\
|
|
65
|
+
x: \text{vapour mass fraction}
|
|
66
|
+
|
|
67
|
+
**mandatory equations at outlet of the working fluid
|
|
68
|
+
of being evaporated side**
|
|
69
|
+
|
|
70
|
+
.. math::
|
|
71
|
+
|
|
72
|
+
0 = h_{3,out} - h\left(p, x=1 \right)\\
|
|
73
|
+
x: \text{vapour mass fraction}
|
|
74
|
+
|
|
75
|
+
Inlets/Outlets
|
|
76
|
+
|
|
77
|
+
- in1, in2, in3 (index 1: steam from geothermal heat source,
|
|
78
|
+
index 2: brine from geothermal heat source,
|
|
79
|
+
index 3: working fluid of being evaporated)
|
|
80
|
+
- out1, out2, out3 (index 1: steam from geothermal heat source,
|
|
81
|
+
index 2: brine from geothermal heat source,
|
|
82
|
+
index 3: working fluid of being evaporated)
|
|
83
|
+
|
|
84
|
+
Image
|
|
85
|
+
|
|
86
|
+
.. image:: _images/orc_evaporator.svg
|
|
87
|
+
:scale: 100 %
|
|
88
|
+
:alt: alternative text
|
|
89
|
+
:align: center
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
label : str
|
|
94
|
+
The label of the component.
|
|
95
|
+
|
|
96
|
+
design : list
|
|
97
|
+
List containing design parameters (stated as String).
|
|
98
|
+
|
|
99
|
+
offdesign : list
|
|
100
|
+
List containing offdesign parameters (stated as String).
|
|
101
|
+
|
|
102
|
+
design_path: str
|
|
103
|
+
Path to the components design case.
|
|
104
|
+
|
|
105
|
+
local_offdesign : boolean
|
|
106
|
+
Treat this component in offdesign mode in a design calculation.
|
|
107
|
+
|
|
108
|
+
local_design : boolean
|
|
109
|
+
Treat this component in design mode in an offdesign calculation.
|
|
110
|
+
|
|
111
|
+
char_warnings: boolean
|
|
112
|
+
Ignore warnings on default characteristics usage for this component.
|
|
113
|
+
|
|
114
|
+
printout: boolean
|
|
115
|
+
Include this component in the network's results printout.
|
|
116
|
+
|
|
117
|
+
Q : float/tespy.tools.data_containers.dc_cp
|
|
118
|
+
Heat transfer, :math:`Q/\text{W}`.
|
|
119
|
+
|
|
120
|
+
pr1 : float/tespy.tools.data_containers.dc_cp
|
|
121
|
+
Outlet to inlet pressure ratio at hot side 1 (steam),
|
|
122
|
+
:math:`pr/1`.
|
|
123
|
+
|
|
124
|
+
pr2 : float/tespy.tools.data_containers.dc_cp
|
|
125
|
+
Outlet to inlet pressure ratio at hot side 2 (brine),
|
|
126
|
+
:math:`pr/1`.
|
|
127
|
+
|
|
128
|
+
pr3 : float/tespy.tools.data_containers.dc_cp
|
|
129
|
+
Outlet to inlet pressure ratio at cold side (working fluid),
|
|
130
|
+
:math:`pr/1`.
|
|
131
|
+
|
|
132
|
+
zeta1 : float/tespy.tools.data_containers.dc_cp
|
|
133
|
+
Geometry independent friction coefficient at hot side 1 (steam),
|
|
134
|
+
:math:`\frac{\zeta}{D^4}/\frac{1}{\text{m}^4}`.
|
|
135
|
+
|
|
136
|
+
zeta2 : float/tespy.tools.data_containers.dc_cp
|
|
137
|
+
Geometry independent friction coefficient at hot side 2 (brine),
|
|
138
|
+
:math:`\frac{\zeta}{D^4}/\frac{1}{\text{m}^4}`.
|
|
139
|
+
|
|
140
|
+
zeta3 : float/tespy.tools.data_containers.dc_cp
|
|
141
|
+
Geometry independent friction coefficient at cold side (working fluid),
|
|
142
|
+
:math:`\frac{\zeta}{D^4}/\frac{1}{\text{m}^4}`.
|
|
143
|
+
|
|
144
|
+
subcooling : bool
|
|
145
|
+
Enable/disable subcooling at oulet of the hot side 1,
|
|
146
|
+
default value: disabled (False).
|
|
147
|
+
|
|
148
|
+
overheating : bool
|
|
149
|
+
Enable/disable overheating at oulet of the cold side,
|
|
150
|
+
default value: disabled (False).
|
|
151
|
+
|
|
152
|
+
Note
|
|
153
|
+
----
|
|
154
|
+
The ORC evaporator has an additional equation for enthalpy at the outlet of
|
|
155
|
+
the geothermal steam: The fluid leaves the component in saturated liquid
|
|
156
|
+
state. If code:`subcooling` is activated (:code:`True`), it is possible to
|
|
157
|
+
specify the enthalpy at the outgoing connection manually.
|
|
158
|
+
|
|
159
|
+
Additionally, an equation for enthalpy at the outlet of the working fluid
|
|
160
|
+
is imposed: It leaves the component in saturated gas state. If
|
|
161
|
+
:code:`overheating` is enabled (:code:`True`), it is possible to specify
|
|
162
|
+
the enthalpy at the outgoing connection manually.
|
|
163
|
+
|
|
164
|
+
Example
|
|
165
|
+
-------
|
|
166
|
+
A two-phase geo-fluid is used as the heat source for evaporating the
|
|
167
|
+
working fluid. We calculate the mass flow of the working fluid with known
|
|
168
|
+
steam and brine mass flow.
|
|
169
|
+
|
|
170
|
+
>>> from tespy.connections import connection
|
|
171
|
+
>>> from tespy.networks import network
|
|
172
|
+
>>> from tespy.components import source, sink
|
|
173
|
+
>>> from tespy.components.customs import orc_evaporator
|
|
174
|
+
>>> fluids = ['water', 'Isopentane']
|
|
175
|
+
>>> nw = network(fluids=fluids, iterinfo=False)
|
|
176
|
+
>>> nw.set_attr(p_unit='bar', T_unit='C', h_unit='kJ / kg')
|
|
177
|
+
>>> evaporator = orc_evaporator('geothermal orc evaporator')
|
|
178
|
+
>>> evaporator.component()
|
|
179
|
+
'orc_evaporator'
|
|
180
|
+
>>> source_wf = source('working fluid source')
|
|
181
|
+
>>> sink_wf = sink('working fluid sink')
|
|
182
|
+
>>> source_s = source('steam source')
|
|
183
|
+
>>> source_b = source('brine source')
|
|
184
|
+
>>> sink_s = sink('steam sink')
|
|
185
|
+
>>> sink_b = sink('brine sink')
|
|
186
|
+
>>> eva_wf_in = connection(source_wf, 'out1', evaporator, 'in3')
|
|
187
|
+
>>> eva_wf_out = connection(evaporator, 'out3', sink_wf, 'in1')
|
|
188
|
+
>>> eva_steam_in = connection(source_s, 'out1', evaporator, 'in1')
|
|
189
|
+
>>> eva_sink_s = connection(evaporator, 'out1', sink_s, 'in1')
|
|
190
|
+
>>> eva_brine_in = connection(source_b, 'out1', evaporator, 'in2')
|
|
191
|
+
>>> eva_sink_b = connection(evaporator, 'out2', sink_b, 'in1')
|
|
192
|
+
>>> nw.add_conns(eva_wf_in, eva_wf_out)
|
|
193
|
+
>>> nw.add_conns(eva_steam_in, eva_sink_s)
|
|
194
|
+
>>> nw.add_conns(eva_brine_in, eva_sink_b)
|
|
195
|
+
|
|
196
|
+
The orc working fluids leaves the evaporator in saturated steam state, the
|
|
197
|
+
geothermal steam leaves the component in staturated liquid state. We imply
|
|
198
|
+
the state of geothermal steam and brine with the corresponding mass flow as
|
|
199
|
+
well as the working fluid's state at the evaporator inlet. The pressure
|
|
200
|
+
ratio is specified for each of the three streams.
|
|
201
|
+
|
|
202
|
+
>>> evaporator.set_attr(pr1=0.95, pr2=0.98, pr3=0.99)
|
|
203
|
+
>>> eva_wf_in.set_attr(T=111, p=11,
|
|
204
|
+
... fluid={'water': 0, 'Isopentane': 1})
|
|
205
|
+
>>> eva_steam_in.set_attr(T=147, p=4.3, m=20,
|
|
206
|
+
... fluid={'water': 1, 'Isopentane': 0})
|
|
207
|
+
>>> eva_brine_in.set_attr(T=147, p=10.2, m=190,
|
|
208
|
+
... fluid={'water': 1, 'Isopentane': 0})
|
|
209
|
+
>>> eva_sink_b.set_attr(T=117)
|
|
210
|
+
>>> nw.solve(mode='design')
|
|
211
|
+
|
|
212
|
+
Check the state of the steam and working fluid outlet:
|
|
213
|
+
|
|
214
|
+
>>> eva_wf_out.x.val
|
|
215
|
+
1.0
|
|
216
|
+
>>> eva_sink_s.x.val
|
|
217
|
+
0.0
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
def component():
|
|
222
|
+
return 'orc_evaporator'
|
|
223
|
+
|
|
224
|
+
@staticmethod
|
|
225
|
+
def attr():
|
|
226
|
+
return {
|
|
227
|
+
'Q': dc_cp(max_val=0),
|
|
228
|
+
'pr1': dc_cp(max_val=1), 'pr2': dc_cp(max_val=1),
|
|
229
|
+
'pr3': dc_cp(max_val=1),
|
|
230
|
+
'zeta1': dc_cp(min_val=0), 'zeta2': dc_cp(min_val=0),
|
|
231
|
+
'zeta3': dc_cp(min_val=0),
|
|
232
|
+
'subcooling': dc_simple(val=False),
|
|
233
|
+
'overheating': dc_simple(val=False),
|
|
234
|
+
'SQ1': dc_simple(), 'SQ2': dc_simple(), 'SQ3': dc_simple(),
|
|
235
|
+
'Sirr': dc_simple()
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def inlets():
|
|
240
|
+
return ['in1', 'in2', 'in3']
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def outlets():
|
|
244
|
+
return ['out1', 'out2', 'out3']
|
|
245
|
+
|
|
246
|
+
def comp_init(self, nw):
|
|
247
|
+
|
|
248
|
+
component.comp_init(self, nw)
|
|
249
|
+
|
|
250
|
+
# number of mandatroy equations for
|
|
251
|
+
# fluid balance: num_fl * 3
|
|
252
|
+
# mass flow: 3
|
|
253
|
+
# energy balance: 1
|
|
254
|
+
self.num_eq = self.num_nw_fluids * 3 + 3 + 1
|
|
255
|
+
# enthalpy hot side 1 outlet (if not subcooling): 1
|
|
256
|
+
if self.subcooling.val is False:
|
|
257
|
+
self.num_eq += 1
|
|
258
|
+
# enthalpy cold side outlet (if not overheating): 1
|
|
259
|
+
if self.overheating.val is False:
|
|
260
|
+
self.num_eq += 1
|
|
261
|
+
for var in [self.Q, self.pr1, self.pr2, self.pr3,
|
|
262
|
+
self.zeta1, self.zeta2, self.zeta3, ]:
|
|
263
|
+
if var.is_set is True:
|
|
264
|
+
self.num_eq += 1
|
|
265
|
+
|
|
266
|
+
self.jacobian = np.zeros((
|
|
267
|
+
self.num_eq,
|
|
268
|
+
self.num_i + self.num_o + self.num_vars,
|
|
269
|
+
self.num_nw_vars))
|
|
270
|
+
|
|
271
|
+
self.residual = np.zeros(self.num_eq)
|
|
272
|
+
pos = self.num_nw_fluids * 3
|
|
273
|
+
self.jacobian[0:pos] = self.fluid_deriv()
|
|
274
|
+
self.jacobian[pos:pos + 3] = self.mass_flow_deriv()
|
|
275
|
+
|
|
276
|
+
def equations(self):
|
|
277
|
+
r"""Calculate residual vector with results of equations."""
|
|
278
|
+
k = 0
|
|
279
|
+
|
|
280
|
+
######################################################################
|
|
281
|
+
# equations for fluid balance
|
|
282
|
+
self.residual[k:k + self.num_nw_fluids * 3] = self.fluid_func()
|
|
283
|
+
k += self.num_nw_fluids * 3
|
|
284
|
+
|
|
285
|
+
######################################################################
|
|
286
|
+
# equations for mass flow balance
|
|
287
|
+
self.residual[k:k + 3] = self.mass_flow_func()
|
|
288
|
+
k += 3
|
|
289
|
+
|
|
290
|
+
######################################################################
|
|
291
|
+
# equations for energy balance
|
|
292
|
+
self.residual[k] = self.energy_func()
|
|
293
|
+
k += 1
|
|
294
|
+
|
|
295
|
+
######################################################################
|
|
296
|
+
# equations for specified heat transfer
|
|
297
|
+
if self.Q.is_set:
|
|
298
|
+
self.residual[k] = (
|
|
299
|
+
self.inl[2].m.val_SI * (
|
|
300
|
+
self.outl[2].h.val_SI - self.inl[2].h.val_SI) - self.Q.val)
|
|
301
|
+
k += 1
|
|
302
|
+
|
|
303
|
+
######################################################################
|
|
304
|
+
# equations for specified pressure ratio at hot side 1
|
|
305
|
+
if self.pr1.is_set:
|
|
306
|
+
self.residual[k] = (
|
|
307
|
+
self.pr1.val * self.inl[0].p.val_SI -
|
|
308
|
+
self.outl[0].p.val_SI)
|
|
309
|
+
k += 1
|
|
310
|
+
|
|
311
|
+
######################################################################
|
|
312
|
+
# equations for specified pressure ratio at hot side 2
|
|
313
|
+
if self.pr2.is_set:
|
|
314
|
+
self.residual[k] = (
|
|
315
|
+
self.pr2.val * self.inl[1].p.val_SI -
|
|
316
|
+
self.outl[1].p.val_SI)
|
|
317
|
+
k += 1
|
|
318
|
+
|
|
319
|
+
######################################################################
|
|
320
|
+
# equations for specified pressure ratio at cold side
|
|
321
|
+
if self.pr3.is_set:
|
|
322
|
+
self.residual[k] = (
|
|
323
|
+
self.pr3.val * self.inl[2].p.val_SI -
|
|
324
|
+
self.outl[2].p.val_SI)
|
|
325
|
+
k += 1
|
|
326
|
+
|
|
327
|
+
######################################################################
|
|
328
|
+
# equations for specified zeta at hot side 1
|
|
329
|
+
if self.zeta1.is_set:
|
|
330
|
+
self.residual[k] = self.zeta_func(
|
|
331
|
+
zeta='zeta1', inconn=0, outconn=0)
|
|
332
|
+
k += 1
|
|
333
|
+
|
|
334
|
+
######################################################################
|
|
335
|
+
# equations for specified zeta at hot side 2
|
|
336
|
+
if self.zeta2.is_set:
|
|
337
|
+
self.residual[k] = self.zeta_func(
|
|
338
|
+
zeta='zeta2', inconn=1, outconn=1)
|
|
339
|
+
k += 1
|
|
340
|
+
|
|
341
|
+
######################################################################
|
|
342
|
+
# equations for specified zeta at cold side
|
|
343
|
+
if self.zeta3.is_set:
|
|
344
|
+
self.residual[k] = self.zeta_func(
|
|
345
|
+
zeta='zeta3', inconn=2, outconn=2)
|
|
346
|
+
k += 1
|
|
347
|
+
|
|
348
|
+
######################################################################
|
|
349
|
+
# equation for saturated liquid at hot side 1 outlet
|
|
350
|
+
if self.subcooling.val is False:
|
|
351
|
+
o1 = self.outl[0].to_flow()
|
|
352
|
+
self.residual[k] = o1[2] - h_mix_pQ(o1, 0)
|
|
353
|
+
k += 1
|
|
354
|
+
|
|
355
|
+
######################################################################
|
|
356
|
+
# equation for saturated gas at cold side outlet
|
|
357
|
+
if self.overheating.val is False:
|
|
358
|
+
o3 = self.outl[2].to_flow()
|
|
359
|
+
self.residual[k] = o3[2] - h_mix_pQ(o3, 1)
|
|
360
|
+
k += 1
|
|
361
|
+
|
|
362
|
+
def derivatives(self, increment_filter):
|
|
363
|
+
r"""Calculate matrix of partial derivatives for given equations."""
|
|
364
|
+
######################################################################
|
|
365
|
+
# derivatives fluid and mass balance are static
|
|
366
|
+
k = self.num_nw_fluids * 3 + 3
|
|
367
|
+
|
|
368
|
+
######################################################################
|
|
369
|
+
# derivatives for energy balance equation
|
|
370
|
+
# mat_deriv += self.energy_deriv()
|
|
371
|
+
for i in range(3):
|
|
372
|
+
self.jacobian[k, i, 0] = (
|
|
373
|
+
self.outl[i].h.val_SI - self.inl[i].h.val_SI)
|
|
374
|
+
self.jacobian[k, i, 2] = -self.inl[i].m.val_SI
|
|
375
|
+
|
|
376
|
+
self.jacobian[k, i + 3, 2] = self.inl[i].m.val_SI
|
|
377
|
+
k += 1
|
|
378
|
+
|
|
379
|
+
######################################################################
|
|
380
|
+
# derivatives for specified heat transfer
|
|
381
|
+
if self.Q.is_set:
|
|
382
|
+
self.jacobian[k, 2, 0] = (
|
|
383
|
+
self.outl[2].h.val_SI - self.inl[2].h.val_SI)
|
|
384
|
+
self.jacobian[k, 2, 2] = -self.inl[2].m.val_SI
|
|
385
|
+
self.jacobian[k, 5, 2] = self.inl[2].m.val_SI
|
|
386
|
+
k += 1
|
|
387
|
+
|
|
388
|
+
######################################################################
|
|
389
|
+
# derivatives for specified pressure ratio at hot side 1
|
|
390
|
+
if self.pr1.is_set:
|
|
391
|
+
self.jacobian[k, 0, 1] = self.pr1.val
|
|
392
|
+
self.jacobian[k, 3, 1] = -1
|
|
393
|
+
k += 1
|
|
394
|
+
|
|
395
|
+
######################################################################
|
|
396
|
+
# derivatives for specified pressure ratio at hot side 2
|
|
397
|
+
if self.pr2.is_set:
|
|
398
|
+
self.jacobian[k, 1, 1] = self.pr2.val
|
|
399
|
+
self.jacobian[k, 4, 1] = -1
|
|
400
|
+
k += 1
|
|
401
|
+
|
|
402
|
+
######################################################################
|
|
403
|
+
# derivatives for specified pressure ratio at cold side
|
|
404
|
+
if self.pr3.is_set:
|
|
405
|
+
self.jacobian[k, 2, 1] = self.pr3.val
|
|
406
|
+
self.jacobian[k, 5, 1] = -1
|
|
407
|
+
k += 1
|
|
408
|
+
|
|
409
|
+
######################################################################
|
|
410
|
+
# derivatives for specified zeta at hot side 1
|
|
411
|
+
if self.zeta1.is_set:
|
|
412
|
+
f = self.zeta_func
|
|
413
|
+
if not increment_filter[0, 0]:
|
|
414
|
+
self.jacobian[k, 0, 0] = self.numeric_deriv(
|
|
415
|
+
f, 'm', 0, zeta='zeta1', inconn=0, outconn=0)
|
|
416
|
+
if not increment_filter[0, 1]:
|
|
417
|
+
self.jacobian[k, 0, 1] = self.numeric_deriv(
|
|
418
|
+
f, 'p', 0, zeta='zeta1', inconn=0, outconn=0)
|
|
419
|
+
if not increment_filter[0, 2]:
|
|
420
|
+
self.jacobian[k, 0, 2] = self.numeric_deriv(
|
|
421
|
+
f, 'h', 0, zeta='zeta1', inconn=0, outconn=0)
|
|
422
|
+
if not increment_filter[3, 1]:
|
|
423
|
+
self.jacobian[k, 3, 1] = self.numeric_deriv(
|
|
424
|
+
f, 'p', 3, zeta='zeta1', inconn=0, outconn=0)
|
|
425
|
+
if not increment_filter[3, 2]:
|
|
426
|
+
self.jacobian[k, 3, 2] = self.numeric_deriv(
|
|
427
|
+
f, 'h', 3, zeta='zeta1', inconn=0, outconn=0)
|
|
428
|
+
k += 1
|
|
429
|
+
|
|
430
|
+
######################################################################
|
|
431
|
+
# derivatives for specified zeta at hot side 2
|
|
432
|
+
if self.zeta2.is_set:
|
|
433
|
+
f = self.zeta_func
|
|
434
|
+
if not increment_filter[1, 0]:
|
|
435
|
+
self.jacobian[k, 1, 0] = self.numeric_deriv(
|
|
436
|
+
f, 'm', 1, zeta='zeta2', inconn=1, outconn=1)
|
|
437
|
+
if not increment_filter[1, 1]:
|
|
438
|
+
self.jacobian[k, 1, 1] = self.numeric_deriv(
|
|
439
|
+
f, 'p', 1, zeta='zeta2', inconn=1, outconn=1)
|
|
440
|
+
if not increment_filter[1, 2]:
|
|
441
|
+
self.jacobian[k, 1, 2] = self.numeric_deriv(
|
|
442
|
+
f, 'h', 1, zeta='zeta2', inconn=1, outconn=1)
|
|
443
|
+
if not increment_filter[4, 1]:
|
|
444
|
+
self.jacobian[k, 4, 1] = self.numeric_deriv(
|
|
445
|
+
f, 'p', 4, zeta='zeta2', inconn=1, outconn=1)
|
|
446
|
+
if not increment_filter[4, 2]:
|
|
447
|
+
self.jacobian[k, 4, 2] = self.numeric_deriv(
|
|
448
|
+
f, 'h', 4, zeta='zeta2', inconn=1, outconn=1)
|
|
449
|
+
k += 1
|
|
450
|
+
|
|
451
|
+
######################################################################
|
|
452
|
+
# derivatives for specified zeta at cold side
|
|
453
|
+
if self.zeta3.is_set:
|
|
454
|
+
f = self.zeta_func
|
|
455
|
+
if not increment_filter[2, 0]:
|
|
456
|
+
self.jacobian[k, 2, 0] = self.numeric_deriv(
|
|
457
|
+
f, 'm', 2, zeta='zeta3', inconn=2, outconn=2)
|
|
458
|
+
if not increment_filter[2, 1]:
|
|
459
|
+
self.jacobian[k, 2, 1] = self.numeric_deriv(
|
|
460
|
+
f, 'p', 2, zeta='zeta3', inconn=2, outconn=2)
|
|
461
|
+
if not increment_filter[2, 2]:
|
|
462
|
+
self.jacobian[k, 2, 2] = self.numeric_deriv(
|
|
463
|
+
f, 'h', 2, zeta='zeta3', inconn=2, outconn=2)
|
|
464
|
+
if not increment_filter[5, 1]:
|
|
465
|
+
self.jacobian[k, 5, 1] = self.numeric_deriv(
|
|
466
|
+
f, 'p', 5, zeta='zeta3', inconn=2, outconn=2)
|
|
467
|
+
if not increment_filter[5, 2]:
|
|
468
|
+
self.jacobian[k, 5, 2] = self.numeric_deriv(
|
|
469
|
+
f, 'h', 5, zeta='zeta3', inconn=2, outconn=2)
|
|
470
|
+
k += 1
|
|
471
|
+
|
|
472
|
+
######################################################################
|
|
473
|
+
# derivatives for saturated liquid at hot side 1 outlet equation
|
|
474
|
+
if self.subcooling.val is False:
|
|
475
|
+
o1 = self.outl[0].to_flow()
|
|
476
|
+
self.jacobian[k, 3, 1] = -dh_mix_dpQ(o1, 0)
|
|
477
|
+
self.jacobian[k, 3, 2] = 1
|
|
478
|
+
k += 1
|
|
479
|
+
|
|
480
|
+
######################################################################
|
|
481
|
+
# derivatives for saturated gas at cold side outlet 3 equation
|
|
482
|
+
if self.overheating.val is False:
|
|
483
|
+
o3 = self.outl[2].to_flow()
|
|
484
|
+
self.jacobian[k, 5, 1] = -dh_mix_dpQ(o3, 1)
|
|
485
|
+
self.jacobian[k, 5, 2] = 1
|
|
486
|
+
k += 1
|
|
487
|
+
|
|
488
|
+
def mass_flow_func(self):
|
|
489
|
+
r"""
|
|
490
|
+
Calculate the residual value of mass flow balance equations.
|
|
491
|
+
|
|
492
|
+
Returns
|
|
493
|
+
-------
|
|
494
|
+
residual : list
|
|
495
|
+
Vector with residual value for component's mass flow balance.
|
|
496
|
+
|
|
497
|
+
.. math::
|
|
498
|
+
|
|
499
|
+
0 = \dot{m}_{in,i} - \dot{m}_{out,i} \;
|
|
500
|
+
\forall i \in inlets/outlets
|
|
501
|
+
"""
|
|
502
|
+
residual = []
|
|
503
|
+
for i in range(self.num_i):
|
|
504
|
+
residual += [self.inl[i].m.val_SI - self.outl[i].m.val_SI]
|
|
505
|
+
return residual
|
|
506
|
+
|
|
507
|
+
def mass_flow_deriv(self):
|
|
508
|
+
r"""
|
|
509
|
+
Calculate the partial derivatives for all mass flow balance equations.
|
|
510
|
+
|
|
511
|
+
Returns
|
|
512
|
+
-------
|
|
513
|
+
deriv : list
|
|
514
|
+
Matrix with partial derivatives for the mass flow balance
|
|
515
|
+
equations.
|
|
516
|
+
"""
|
|
517
|
+
deriv = np.zeros((self.num_i, 2 * self.num_i, self.num_nw_vars))
|
|
518
|
+
for i in range(self.num_i):
|
|
519
|
+
deriv[i, i, 0] = 1
|
|
520
|
+
for j in range(self.num_i):
|
|
521
|
+
deriv[j, j + self.num_i, 0] = -1
|
|
522
|
+
return deriv
|
|
523
|
+
|
|
524
|
+
def energy_func(self):
|
|
525
|
+
r"""
|
|
526
|
+
Equation for heat exchanger energy balance.
|
|
527
|
+
|
|
528
|
+
Returns
|
|
529
|
+
-------
|
|
530
|
+
res : float
|
|
531
|
+
Residual value of equation.
|
|
532
|
+
|
|
533
|
+
.. math::
|
|
534
|
+
|
|
535
|
+
\begin{split}
|
|
536
|
+
res = &
|
|
537
|
+
\dot{m}_{1,in} \cdot \left(h_{1,out} - h_{1,in} \right) \\
|
|
538
|
+
&+ \dot{m}_{2,in} \cdot \left(h_{2,out} - h_{2,in} \right) \\
|
|
539
|
+
&+ \dot{m}_{3,in} \cdot \left(h_{3,out} - h_{3,in} \right)
|
|
540
|
+
\end{split}
|
|
541
|
+
"""
|
|
542
|
+
return (
|
|
543
|
+
self.inl[0].m.val_SI * (
|
|
544
|
+
self.outl[0].h.val_SI - self.inl[0].h.val_SI) +
|
|
545
|
+
self.inl[1].m.val_SI * (
|
|
546
|
+
self.outl[1].h.val_SI - self.inl[1].h.val_SI) +
|
|
547
|
+
self.inl[2].m.val_SI * (
|
|
548
|
+
self.outl[2].h.val_SI - self.inl[2].h.val_SI))
|
|
549
|
+
|
|
550
|
+
def bus_func(self, bus):
|
|
551
|
+
r"""
|
|
552
|
+
Calculate the value of the bus function.
|
|
553
|
+
|
|
554
|
+
Parameters
|
|
555
|
+
----------
|
|
556
|
+
bus : tespy.connections.bus
|
|
557
|
+
TESPy bus object.
|
|
558
|
+
|
|
559
|
+
Returns
|
|
560
|
+
-------
|
|
561
|
+
val : float
|
|
562
|
+
Value of energy transfer :math:`\dot{E}`. This value is passed to
|
|
563
|
+
:py:meth:`tespy.components.components.component.calc_bus_value`
|
|
564
|
+
for value manipulation according to the specified characteristic
|
|
565
|
+
line of the bus.
|
|
566
|
+
|
|
567
|
+
.. math::
|
|
568
|
+
|
|
569
|
+
\dot{E} = \dot{m}_{3,in} \cdot \left(
|
|
570
|
+
h_{3,out} - h_{3,in} \right)
|
|
571
|
+
"""
|
|
572
|
+
i = self.inl[2].to_flow()
|
|
573
|
+
o = self.outl[2].to_flow()
|
|
574
|
+
val = i[0] * (o[2] - i[2])
|
|
575
|
+
|
|
576
|
+
return val
|
|
577
|
+
|
|
578
|
+
def bus_deriv(self, bus):
|
|
579
|
+
r"""
|
|
580
|
+
Calculate the matrix of partial derivatives of the bus function.
|
|
581
|
+
|
|
582
|
+
Parameters
|
|
583
|
+
----------
|
|
584
|
+
bus : tespy.connections.bus
|
|
585
|
+
TESPy bus object.
|
|
586
|
+
|
|
587
|
+
Returns
|
|
588
|
+
-------
|
|
589
|
+
mat_deriv : ndarray
|
|
590
|
+
Matrix of partial derivatives.
|
|
591
|
+
"""
|
|
592
|
+
deriv = np.zeros((1, 6, self.num_nw_vars))
|
|
593
|
+
f = self.calc_bus_value
|
|
594
|
+
deriv[0, 2, 0] = self.numeric_deriv(f, 'm', 2, bus=bus)
|
|
595
|
+
deriv[0, 2, 2] = self.numeric_deriv(f, 'h', 2, bus=bus)
|
|
596
|
+
deriv[0, 5, 2] = self.numeric_deriv(f, 'h', 5, bus=bus)
|
|
597
|
+
return deriv
|
|
598
|
+
|
|
599
|
+
def initialise_source(self, c, key):
|
|
600
|
+
r"""
|
|
601
|
+
Return a starting value for pressure and enthalpy at outlet.
|
|
602
|
+
|
|
603
|
+
Parameters
|
|
604
|
+
----------
|
|
605
|
+
c : tespy.connections.connection
|
|
606
|
+
Connection to perform initialisation on.
|
|
607
|
+
|
|
608
|
+
key : str
|
|
609
|
+
Fluid property to retrieve.
|
|
610
|
+
|
|
611
|
+
Returns
|
|
612
|
+
-------
|
|
613
|
+
val : float
|
|
614
|
+
Starting value for pressure/enthalpy in SI units.
|
|
615
|
+
|
|
616
|
+
.. math::
|
|
617
|
+
|
|
618
|
+
val = \begin{cases}
|
|
619
|
+
10 \cdot 10^5 & \text{key = 'p'}\\
|
|
620
|
+
h\left(p, 473.15 \text{K} \right) &
|
|
621
|
+
\text{key = 'h' at outlet 1}\\
|
|
622
|
+
h\left(p, 473.15 \text{K} \right) &
|
|
623
|
+
\text{key = 'h' at outlet 2}\\
|
|
624
|
+
h\left(p, 523.15 \text{K} \right) &
|
|
625
|
+
\text{key = 'h' at outlet 3}
|
|
626
|
+
\end{cases}
|
|
627
|
+
"""
|
|
628
|
+
if key == 'p':
|
|
629
|
+
return 10e5
|
|
630
|
+
elif key == 'h':
|
|
631
|
+
flow = c.to_flow()
|
|
632
|
+
if c.source_id == 'out1':
|
|
633
|
+
T = 200 + 273.15
|
|
634
|
+
return h_mix_pT(flow, T)
|
|
635
|
+
elif c.source_id == 'out2':
|
|
636
|
+
T = 200 + 273.15
|
|
637
|
+
return h_mix_pT(flow, T)
|
|
638
|
+
else:
|
|
639
|
+
T = 250 + 273.15
|
|
640
|
+
return h_mix_pT(flow, T)
|
|
641
|
+
|
|
642
|
+
def initialise_target(self, c, key):
|
|
643
|
+
r"""
|
|
644
|
+
Return a starting value for pressure and enthalpy at inlet.
|
|
645
|
+
|
|
646
|
+
Parameters
|
|
647
|
+
----------
|
|
648
|
+
c : tespy.connections.connection
|
|
649
|
+
Connection to perform initialisation on.
|
|
650
|
+
|
|
651
|
+
key : str
|
|
652
|
+
Fluid property to retrieve.
|
|
653
|
+
|
|
654
|
+
Returns
|
|
655
|
+
-------
|
|
656
|
+
val : float
|
|
657
|
+
Starting value for pressure/enthalpy in SI units.
|
|
658
|
+
|
|
659
|
+
.. math::
|
|
660
|
+
|
|
661
|
+
val = \begin{cases}
|
|
662
|
+
10 \cdot 10^5 & \text{key = 'p'}\\
|
|
663
|
+
h\left(p, 573.15 \text{K} \right) &
|
|
664
|
+
\text{key = 'h' at inlet 1}\\
|
|
665
|
+
h\left(p, 573.15 \text{K} \right) &
|
|
666
|
+
\text{key = 'h' at inlet 2}\\
|
|
667
|
+
h\left(p, 493.15 \text{K} \right) &
|
|
668
|
+
\text{key = 'h' at inlet 3}
|
|
669
|
+
\end{cases}
|
|
670
|
+
"""
|
|
671
|
+
if key == 'p':
|
|
672
|
+
return 10e5
|
|
673
|
+
elif key == 'h':
|
|
674
|
+
flow = c.to_flow()
|
|
675
|
+
if c.target_id == 'in1':
|
|
676
|
+
T = 300 + 273.15
|
|
677
|
+
return h_mix_pT(flow, T)
|
|
678
|
+
elif c.target_id == 'in2':
|
|
679
|
+
T = 300 + 273.15
|
|
680
|
+
return h_mix_pT(flow, T)
|
|
681
|
+
else:
|
|
682
|
+
T = 220 + 273.15
|
|
683
|
+
return h_mix_pT(flow, T)
|
|
684
|
+
|
|
685
|
+
def calc_parameters(self):
|
|
686
|
+
r"""Postprocessing parameter calculation."""
|
|
687
|
+
# connection information
|
|
688
|
+
i1 = self.inl[0].to_flow()
|
|
689
|
+
i2 = self.inl[1].to_flow()
|
|
690
|
+
i3 = self.inl[2].to_flow()
|
|
691
|
+
o1 = self.outl[0].to_flow()
|
|
692
|
+
o2 = self.outl[1].to_flow()
|
|
693
|
+
o3 = self.outl[2].to_flow()
|
|
694
|
+
|
|
695
|
+
# specific volume
|
|
696
|
+
v_i1 = v_mix_ph(i1, T0=self.inl[0].T.val_SI)
|
|
697
|
+
v_i2 = v_mix_ph(i2, T0=self.inl[1].T.val_SI)
|
|
698
|
+
v_i3 = v_mix_ph(i3, T0=self.inl[2].T.val_SI)
|
|
699
|
+
v_o1 = v_mix_ph(o1, T0=self.outl[0].T.val_SI)
|
|
700
|
+
v_o2 = v_mix_ph(o2, T0=self.outl[1].T.val_SI)
|
|
701
|
+
v_o3 = v_mix_ph(o3, T0=self.outl[2].T.val_SI)
|
|
702
|
+
|
|
703
|
+
# specific entropy
|
|
704
|
+
s_i1 = s_mix_ph(i1, T0=self.inl[0].T.val_SI)
|
|
705
|
+
s_i2 = s_mix_ph(i2, T0=self.inl[1].T.val_SI)
|
|
706
|
+
s_i3 = s_mix_ph(i3, T0=self.inl[2].T.val_SI)
|
|
707
|
+
s_o1 = s_mix_ph(o1, T0=self.outl[0].T.val_SI)
|
|
708
|
+
s_o2 = s_mix_ph(o2, T0=self.outl[1].T.val_SI)
|
|
709
|
+
s_o3 = s_mix_ph(o3, T0=self.outl[2].T.val_SI)
|
|
710
|
+
|
|
711
|
+
# component parameters
|
|
712
|
+
self.Q.val = -i3[0] * (o3[2] - i3[2])
|
|
713
|
+
|
|
714
|
+
self.pr1.val = o1[1] / i1[1]
|
|
715
|
+
self.pr2.val = o2[1] / i2[1]
|
|
716
|
+
self.pr3.val = o3[1] / i3[1]
|
|
717
|
+
self.zeta1.val = ((i1[1] - o1[1]) * np.pi ** 2 /
|
|
718
|
+
(8 * i1[0] ** 2 * (v_i1 + v_o1) / 2))
|
|
719
|
+
self.zeta2.val = ((i2[1] - o2[1]) * np.pi ** 2 /
|
|
720
|
+
(8 * i2[0] ** 2 * (v_i2 + v_o2) / 2))
|
|
721
|
+
self.zeta3.val = ((i3[1] - o3[1]) * np.pi ** 2 /
|
|
722
|
+
(8 * i3[0] ** 2 * (v_i3 + v_o3) / 2))
|
|
723
|
+
|
|
724
|
+
self.SQ1.val = self.inl[0].m.val_SI * (s_o1 - s_i1)
|
|
725
|
+
self.SQ2.val = self.inl[1].m.val_SI * (s_o2 - s_i2)
|
|
726
|
+
self.SQ3.val = self.inl[2].m.val_SI * (s_o3 - s_i3)
|
|
727
|
+
self.Sirr.val = self.SQ1.val + self.SQ2.val + self.SQ3.val
|
|
728
|
+
|
|
729
|
+
self.check_parameter_bounds()
|