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.
@@ -0,0 +1,1890 @@
1
+ # -*- coding: utf-8
2
+
3
+ """Module for components of type node.
4
+
5
+ Components in this module:
6
+
7
+ - :func:`tespy.components.nodes.droplet_separator`
8
+ - :func:`tespy.components.nodes.drum`
9
+ - :func:`tespy.components.nodes.node`
10
+ - :func:`tespy.components.nodes.merge`
11
+ - :func:`tespy.components.nodes.splitter`
12
+ - :func:`tespy.components.nodes.separator`
13
+
14
+
15
+ This file is part of project TESPy (github.com/oemof/tespy). It's copyrighted
16
+ by the contributors recorded in the version control history of the file,
17
+ available from its original location tespy/components/nodes.py
18
+
19
+ SPDX-License-Identifier: MIT
20
+ """
21
+
22
+ import logging
23
+
24
+ import numpy as np
25
+
26
+ from tespy.components.components import component
27
+ from tespy.tools.data_containers import dc_simple
28
+ from tespy.tools.fluid_properties import T_mix_ph
29
+ from tespy.tools.fluid_properties import dh_mix_dpQ
30
+ from tespy.tools.fluid_properties import dT_mix_dph
31
+ from tespy.tools.fluid_properties import dT_mix_pdh
32
+ from tespy.tools.fluid_properties import dT_mix_ph_dfluid
33
+ from tespy.tools.fluid_properties import h_mix_pQ
34
+ from tespy.tools.helpers import num_fluids
35
+
36
+ # %%
37
+
38
+
39
+ class node(component):
40
+ r"""
41
+ Class node is the parent class for splitter, separator and merge.
42
+
43
+ Equations
44
+
45
+ **mandatory equations**
46
+
47
+ - :func:`tespy.components.components.component.mass_flow_func`
48
+
49
+ .. math::
50
+
51
+ 0 = p_{in,1} - p_{out,i}\\
52
+ \forall i \in \mathrm{outlets}
53
+
54
+ **additional equations**
55
+
56
+ - :func:`tespy.components.nodes.splitter.additional_equations`
57
+ - :func:`tespy.components.nodes.separator.additional_equations`
58
+ - :func:`tespy.components.nodes.merge.additional_equations`
59
+
60
+ Inlets/Outlets
61
+
62
+ - specify number of outlets with :code:`num_in` (default value: 2)
63
+ - specify number of outlets with :code:`num_out` (default value: 2)
64
+
65
+ Image
66
+
67
+ .. image:: _images/node.svg
68
+ :scale: 100 %
69
+ :alt: alternative text
70
+ :align: center
71
+
72
+ Parameters
73
+ ----------
74
+ label : str
75
+ The label of the component.
76
+
77
+ design : list
78
+ List containing design parameters (stated as String).
79
+
80
+ offdesign : list
81
+ List containing offdesign parameters (stated as String).
82
+
83
+ design_path: str
84
+ Path to the components design case.
85
+
86
+ local_offdesign : boolean
87
+ Treat this component in offdesign mode in a design calculation.
88
+
89
+ local_design : boolean
90
+ Treat this component in design mode in an offdesign calculation.
91
+
92
+ char_warnings: boolean
93
+ Ignore warnings on default characteristics usage for this component.
94
+
95
+ printout: boolean
96
+ Include this component in the network's results printout.
97
+
98
+ num_in : float/tespy.tools.data_containers.dc_simple
99
+ Number of inlets for this component, default value: 2.
100
+
101
+ num_out : float/tespy.tools.data_containers.dc_simple
102
+ Number of outlets for this component, default value: 2.
103
+
104
+ Note
105
+ ----
106
+ - Node: Fluid composition and enthalpy at all **outgoing** connections
107
+ (mass flow leaves the node) is result of mixture of the properties of
108
+ the incoming connections (mass flow enters node).
109
+ Incoming and outgoing connections can be a result of the calculation and
110
+ are not identical to the inlets and outlets!
111
+ - Splitter: Fluid composition and enthalpy at all outlets is the same as
112
+ the inlet's properties.
113
+ - Separator: Fluid composition is variable for all outlets, temperature at
114
+ all outlets is the same as the inlet's temperature.
115
+ - Merge: Fluid composition and enthalpy at outlet is result of mixture of
116
+ the inlet's properties.
117
+
118
+ Example
119
+ -------
120
+ The node can serve as merge or as splitter at the same time: The sum of
121
+ all mass flow going into the node is identical to the mass flow leaving it.
122
+ All incoming fluids are mixed in the node (mass flow weighted fluid mass
123
+ fractions and enthalpy). All outgoing fluids have the composition of the
124
+ mixture at the mixtures enthalpy/temperature.
125
+
126
+ >>> from tespy.components import sink, source, node
127
+ >>> from tespy.connections import connection
128
+ >>> from tespy.networks import network
129
+ >>> import shutil
130
+ >>> import numpy as np
131
+ >>> fluid_list = ['O2', 'N2']
132
+ >>> nw = network(fluids=fluid_list, p_unit='bar', T_unit='C',
133
+ ... h_unit='kJ / kg', iterinfo=False)
134
+ >>> so1 = source('source1')
135
+ >>> so2 = source('source2')
136
+ >>> si1 = sink('sink1')
137
+ >>> si2 = sink('sink2')
138
+ >>> n = node('node', num_in=2, num_out=2)
139
+ >>> n.component()
140
+ 'node'
141
+ >>> inc1 = connection(so1, 'out1', n, 'in1')
142
+ >>> inc2 = connection(so2, 'out1', n, 'in2')
143
+ >>> outg1 = connection(n, 'out1', si1, 'in1')
144
+ >>> outg2 = connection(n, 'out2', si2, 'in1')
145
+ >>> nw.add_conns(inc1, inc2, outg1, outg2)
146
+
147
+ 2 kg/s of pure oxygen is mixed with 5 kg/s mixture (50 % nitrogen and
148
+ 50 % oxygen). One of the outgoing connections should be at 3 kg/s.
149
+
150
+ >>> inc1.set_attr(fluid={'O2': 1, 'N2': 0}, p=1, T=20, m=2)
151
+ >>> inc2.set_attr(fluid={'O2': 0.5, 'N2': 0.5}, T=50, m=5)
152
+ >>> outg1.set_attr(m=3)
153
+ >>> nw.solve('design')
154
+ >>> (round(outg1.fluid.val['O2'], 3), round(outg1.fluid.val['N2'], 3))
155
+ (0.643, 0.357)
156
+ >>> round(outg1.T.val, 1)
157
+ 41.8
158
+
159
+ Now calculate at what mass flow of the 50/50 mixture the total
160
+ oxygen fraction of the nodes outlets will be at 80 %.
161
+
162
+ >>> inc2.set_attr(m=np.nan)
163
+ >>> outg1.set_attr(fluid={'O2': 0.8})
164
+ >>> nw.solve('design')
165
+ >>> round(inc2.m.val_SI, 3)
166
+ 1.333
167
+ """
168
+
169
+ @staticmethod
170
+ def component():
171
+ return 'node'
172
+
173
+ @staticmethod
174
+ def attr():
175
+ return {'num_in': dc_simple(), 'num_out': dc_simple()}
176
+
177
+ def inlets(self):
178
+ if self.num_in.is_set:
179
+ return ['in' + str(i + 1) for i in range(self.num_in.val)]
180
+ else:
181
+ self.set_attr(num_in=2)
182
+ return self.inlets()
183
+
184
+ def outlets(self):
185
+ if self.num_out.is_set:
186
+ return ['out' + str(i + 1) for i in range(self.num_out.val)]
187
+ else:
188
+ self.set_attr(num_out=2)
189
+ return self.outlets()
190
+
191
+ def comp_init(self, nw):
192
+
193
+ component.comp_init(self, nw)
194
+
195
+ # number of mandatroy equations for
196
+ # mass flow: 1
197
+ # pressure: number of inlets + number of outlets - 1
198
+ # the number of fluid and enthalpy equations is variable for a node!
199
+
200
+ self.num_eq = 1 + self.num_i + self.num_o - 1
201
+
202
+ # number of fluid equations
203
+ self.num_eq += self.num_nw_fluids * self.num_o
204
+ # number of enthalpy equations
205
+ self.num_eq += self.num_o
206
+
207
+ self.jacobian = np.zeros((
208
+ self.num_eq,
209
+ self.num_i + self.num_o + self.num_vars,
210
+ self.num_nw_vars))
211
+
212
+ self.residual = np.zeros(self.num_eq)
213
+ self.jacobian[0:1] = self.mass_flow_deriv()
214
+ self.jacobian[1:self.num_i + self.num_o] = self.pressure_deriv()
215
+
216
+ def equations(self):
217
+ r"""Calculate residual vector with results of equations."""
218
+ k = 0
219
+ ######################################################################
220
+ # eqation for mass flow balance
221
+ self.residual[k] = self.mass_flow_func()
222
+ k += 1
223
+
224
+ ######################################################################
225
+ # equations for pressure
226
+ inl = []
227
+ if self.num_i > 1:
228
+ inl = self.inl[1:]
229
+ for c in inl + self.outl:
230
+ self.residual[k] = self.inl[0].p.val_SI - c.p.val_SI
231
+ k += 1
232
+
233
+ ######################################################################
234
+ # additional eqations
235
+ self.additional_equations(k)
236
+
237
+ def derivatives(self, increment_filter):
238
+ r"""Calculate partial derivatives for given equations."""
239
+ ######################################################################
240
+ # derivatives for mass and pressure are static
241
+ k = self.num_i + self.num_o
242
+ ######################################################################
243
+ # additional derivatives
244
+ self.additional_derivatives(increment_filter, k)
245
+
246
+ def additional_equations(self, k):
247
+ r"""
248
+ Calculate the residual vector of the additional equations.
249
+
250
+ Equations
251
+
252
+ **mandatroy equations**
253
+
254
+ .. math::
255
+
256
+ 0 = \sum_i \left(\dot{m}_{i} \cdot x_{i,j}\right) - x_{o,j}
257
+ \cdot \sum_i \dot{m}_{i}\\
258
+ \forall j \in \text{fluids}\\
259
+ \forall o \in \text{outgoing mass flows}\\
260
+ \text{i: incoming mass flows}
261
+
262
+ .. math::
263
+
264
+ 0 = \sum_i \dot{m}_{i} \cdot h_{i}
265
+ - \sum_o \dot{m}_{o} \cdot h_{o}
266
+ \forall o \in \text{outgoing mass flows}\\
267
+ \text{i: incoming mass flows}
268
+ """
269
+ ######################################################################
270
+ # check for incoming/outgoing mass flows in inlets and outlets
271
+ loc = 0
272
+ # total incoming enthalpy
273
+ h = 0
274
+ # total incoming mass flow (constant within every iteration)
275
+ self.m_inc = 0
276
+
277
+ self.inc = []
278
+ self.outg = []
279
+ for c in self.inl:
280
+ # incoming
281
+ if c.m.val_SI >= 0:
282
+ self.inc += [[c, loc]]
283
+ self.m_inc += c.m.val_SI
284
+ h += c.m.val_SI * c.h.val_SI
285
+ # outgoing
286
+ else:
287
+ self.outg += [[c, loc]]
288
+ loc += 1
289
+
290
+ for c in self.outl:
291
+ # inconming
292
+ if c.m.val_SI < 0:
293
+ self.inc += [[c, loc]]
294
+ self.m_inc -= c.m.val_SI
295
+ h -= c.m.val_SI * c.h.val_SI
296
+ # outgoing
297
+ else:
298
+ self.outg += [[c, loc]]
299
+ loc += 1
300
+
301
+ self.jacobian = self.jacobian[0:k]
302
+ self.jacobian = np.append(self.jacobian, np.zeros((
303
+ len(self.outg) * (self.num_nw_fluids + 1),
304
+ self.num_i + self.num_o + self.num_vars,
305
+ self.num_nw_vars)), axis=0)
306
+
307
+ ######################################################################
308
+ # equations for fluid composition
309
+
310
+ for fluid in self.nw_fluids:
311
+ m = 0
312
+ for i in self.inc:
313
+ m += abs(i[0].m.val_SI) * i[0].fluid.val[fluid]
314
+ for o in self.outg:
315
+ self.residual[k] = m - o[0].fluid.val[fluid] * self.m_inc
316
+ k += 1
317
+
318
+ ######################################################################
319
+ # equations for energy balance
320
+ for o in self.outg:
321
+ self.residual[k] = h - o[0].h.val_SI * self.m_inc
322
+ k += 1
323
+
324
+ def additional_derivatives(self, increment_filter, k):
325
+ r"""Calculate partial derivatives for given additional equations."""
326
+ ######################################################################
327
+ # derivatives for fluid balance equations
328
+ j = 0
329
+ for fluid in self.nw_fluids:
330
+ for o in self.outg:
331
+ self.jacobian[k, o[1], j + 3] = -self.m_inc
332
+ for i in self.inc:
333
+ self.jacobian[k, i[1], 0] = -i[0].fluid.val[fluid]
334
+ self.jacobian[k, i[1], j + 3] = -abs(i[0].m.val_SI)
335
+ k += 1
336
+ j += 1
337
+
338
+ ######################################################################
339
+ # derivatives for energy balance equations
340
+ for o in self.outg:
341
+ self.jacobian[k, o[1], 2] = -self.m_inc
342
+ for i in self.inc:
343
+ self.jacobian[k, i[1], 0] = i[0].h.val_SI - o[0].h.val_SI
344
+ self.jacobian[k, i[1], 2] = abs(i[0].m.val_SI)
345
+ k += 1
346
+
347
+ logging.info(str(self.jacobian))
348
+
349
+ def pressure_deriv(self):
350
+ r"""
351
+ Calculate partial derivatives for all pressure equations.
352
+
353
+ Returns
354
+ -------
355
+ deriv : list
356
+ Matrix with partial derivatives for the fluid equations.
357
+ """
358
+ deriv = np.zeros((
359
+ self.num_i + self.num_o - 1,
360
+ self.num_i + self.num_o,
361
+ self.num_nw_vars))
362
+
363
+ inl = []
364
+ if self.num_i > 1:
365
+ inl = self.inl[1:]
366
+ for k in range(len(inl + self.outl)):
367
+ deriv[k, 0, 1] = 1
368
+ deriv[k, k + 1, 1] = -1
369
+ return deriv
370
+
371
+ def initialise_fluids(self, nw):
372
+ r"""
373
+ Fluid initialisation for fluid mixture at outlet of the node.
374
+
375
+ Parameters
376
+ ----------
377
+ nw : tespy.networks.network
378
+ Network using this component object.
379
+ """
380
+ num_fl = {}
381
+ for o in self.outl:
382
+ num_fl[o] = num_fluids(o.fluid.val)
383
+
384
+ for i in self.inl:
385
+ num_fl[i] = num_fluids(i.fluid.val)
386
+
387
+ ls = []
388
+ if any(num_fl.values()) and not all(num_fl.values()):
389
+ for conn, num in num_fl.items():
390
+ if num == 1:
391
+ ls += [conn]
392
+
393
+ for c in ls:
394
+ for fluid in nw.fluids:
395
+ for o in self.outl:
396
+ if not o.fluid.val_set[fluid]:
397
+ o.fluid.val[fluid] = c.fluid.val[fluid]
398
+ for i in self.inl:
399
+ if not i.fluid.val_set[fluid]:
400
+ i.fluid.val[fluid] = c.fluid.val[fluid]
401
+
402
+ @staticmethod
403
+ def initialise_source(c, key):
404
+ r"""
405
+ Return a starting value for pressure and enthalpy at outlet.
406
+
407
+ Parameters
408
+ ----------
409
+ c : tespy.connections.connection
410
+ Connection to perform initialisation on.
411
+
412
+ key : str
413
+ Fluid property to retrieve.
414
+
415
+ Returns
416
+ -------
417
+ val : float
418
+ Starting value for pressure/enthalpy in SI units.
419
+
420
+ .. math::
421
+
422
+ val = \begin{cases}
423
+ 10^5 & \text{key = 'p'}\\
424
+ 5 \cdot 10^5 & \text{key = 'h'}
425
+ \end{cases}
426
+ """
427
+ if key == 'p':
428
+ return 1e5
429
+ elif key == 'h':
430
+ return 5e5
431
+
432
+ @staticmethod
433
+ def initialise_target(c, key):
434
+ r"""
435
+ Return a starting value for pressure and enthalpy at inlet.
436
+
437
+ Parameters
438
+ ----------
439
+ c : tespy.connections.connection
440
+ Connection to perform initialisation on.
441
+
442
+ key : str
443
+ Fluid property to retrieve.
444
+
445
+ Returns
446
+ -------
447
+ val : float
448
+ Starting value for pressure/enthalpy in SI units.
449
+
450
+ .. math::
451
+
452
+ val = \begin{cases}
453
+ 10^5 & \text{key = 'p'}\\
454
+ 5 \cdot 10^5 & \text{key = 'h'}
455
+ \end{cases}
456
+ """
457
+ if key == 'p':
458
+ return 1e5
459
+ elif key == 'h':
460
+ return 5e5
461
+
462
+ # %%
463
+
464
+
465
+ class droplet_separator(component):
466
+ r"""
467
+ Separate liquid phase from gas phase of a single fluid.
468
+
469
+ Equations
470
+
471
+ **mandatory equations**
472
+
473
+ - :func:`tespy.components.nodes.droplet_separator.fluid_func`
474
+ - :func:`tespy.components.components.component.mass_flow_func`
475
+
476
+ .. math::
477
+
478
+ 0 = \dot{m}_{in} \cdot h_{in} -
479
+ \sum_j \left(\dot{m}_{j,out} \cdot h_{j,out} \right)\\
480
+ \forall j \in outlet
481
+
482
+ 0 = p_{in} - p_{out,i}\\
483
+ \forall i \in \mathrm{outlets}
484
+
485
+ 0 = h_{1,out} - h\left(p, x=1 \right)
486
+
487
+ 0 = h_{2,out} - h\left(p, x=0 \right)\\
488
+ x: \text{vapour mass fraction}
489
+
490
+ Inlets/Outlets
491
+
492
+ - in1
493
+ - out1, out2 (index 1: gas phase, index 2: liquid phase)
494
+
495
+ Image
496
+
497
+ .. image:: _images/droplet_separator.svg
498
+ :scale: 100 %
499
+ :alt: alternative text
500
+ :align: center
501
+
502
+ Parameters
503
+ ----------
504
+ label : str
505
+ The label of the component.
506
+
507
+ design : list
508
+ List containing design parameters (stated as String).
509
+
510
+ offdesign : list
511
+ List containing offdesign parameters (stated as String).
512
+
513
+ design_path: str
514
+ Path to the components design case.
515
+
516
+ local_offdesign : boolean
517
+ Treat this component in offdesign mode in a design calculation.
518
+
519
+ local_design : boolean
520
+ Treat this component in design mode in an offdesign calculation.
521
+
522
+ char_warnings: boolean
523
+ Ignore warnings on default characteristics usage for this component.
524
+
525
+ printout: boolean
526
+ Include this component in the network's results printout.
527
+
528
+ Note
529
+ ----
530
+ If you are using a drum in a network with multiple fluids, it is likely
531
+ the fluid propagation causes trouble. If this is the case, try to
532
+ specify the fluid composition at another connection of your network.
533
+
534
+ This component assumes, that the fluid composition between outlet 1 and
535
+ inlet 2 does not change, thus there is no equation for the fluid mass
536
+ fraction at the inlet 2!
537
+
538
+ Example
539
+ -------
540
+ The droplet separator separates gas from liquid phase. From a stream of
541
+ water the liquid phase will be separated.
542
+
543
+ >>> from tespy.components import sink, source, droplet_separator
544
+ >>> from tespy.connections import connection
545
+ >>> from tespy.networks import network
546
+ >>> from tespy.tools.fluid_properties import Q_ph, T_bp_p
547
+ >>> import shutil
548
+ >>> nw = network(fluids=['water'], T_unit='C', p_unit='bar',
549
+ ... h_unit='kJ / kg', iterinfo=False)
550
+ >>> so = source('two phase inflow')
551
+ >>> sig = sink('gas outflow')
552
+ >>> sil = sink('liquid outflow')
553
+ >>> ds = droplet_separator('droplet separator')
554
+ >>> ds.component()
555
+ 'droplet separator'
556
+ >>> so_ds = connection(so, 'out1', ds, 'in1')
557
+ >>> ds_sig = connection(ds, 'out1', sig, 'in1')
558
+ >>> ds_sil = connection(ds, 'out2', sil, 'in1')
559
+ >>> nw.add_conns(so_ds, ds_sig, ds_sil)
560
+
561
+ We specify the fluid's state at the inlet. At the gas outflow saturated
562
+ gas enthalpy is expected, at the liquid gas outflow saturated liquid
563
+ enthalpy. The mass flow at the outlets is expected to split according to
564
+ the vapor mass fraction:
565
+
566
+ .. math::
567
+
568
+ \dot{m}_\mathrm{out,1} = \frac{h_\mathrm{in} - h'}{h'' - h'} \cdot
569
+ \dot{m}_\mathrm{in}
570
+
571
+ \dot{m}_\mathrm{out,2} = \left(1 - \frac{h_\mathrm{in} - h'}{h'' - h'}
572
+ \right) \cdot \dot{m}_\mathrm{in}
573
+
574
+ >>> so_ds.set_attr(fluid={'water': 1}, p=1, h=1500, m=10)
575
+ >>> nw.solve('design')
576
+ >>> Q_in = Q_ph(so_ds.p.val_SI, so_ds.h.val_SI, 'water')
577
+ >>> round(Q_in * so_ds.m.val_SI, 6) == round(ds_sig.m.val_SI, 6)
578
+ True
579
+ >>> round((1 - Q_in) * so_ds.m.val_SI, 6) == round(ds_sil.m.val_SI, 6)
580
+ True
581
+ >>> Q_ph(ds_sig.p.val_SI, ds_sig.h.val_SI, 'water')
582
+ 1.0
583
+ >>> Q_ph(ds_sil.p.val_SI, ds_sil.h.val_SI, 'water')
584
+ 0.0
585
+
586
+ In a different setup, we unset pressure and enthalpy and specify gas
587
+ temperature and mass flow instead. The temperature specification must yield
588
+ the corresponding boiling point pressure and the mass flow must yield the
589
+ inlet enthalpy. The inlet vapor mass fraction must be equal to fraction of
590
+ gas mass flow to inlet mass flow (0.95 in this example).
591
+
592
+ >>> so_ds.set_attr(fluid={'water': 1}, p=None, h=None, T=150, m=10)
593
+ >>> ds_sig.set_attr(m=9.5)
594
+ >>> nw.solve('design')
595
+ >>> round(Q_ph(so_ds.p.val_SI, so_ds.h.val_SI, 'water'), 6)
596
+ 0.95
597
+ >>> T_boil = T_bp_p(so_ds.to_flow())
598
+ >>> round(T_boil, 6) == round(so_ds.T.val_SI, 6)
599
+ True
600
+ """
601
+
602
+ @staticmethod
603
+ def component():
604
+ return 'droplet separator'
605
+
606
+ @staticmethod
607
+ def inlets():
608
+ return ['in1']
609
+
610
+ @staticmethod
611
+ def outlets():
612
+ return ['out1', 'out2']
613
+
614
+ def comp_init(self, nw):
615
+
616
+ component.comp_init(self, nw)
617
+
618
+ # number of mandatroy equations for
619
+ # fluid balance: num_fl * 2
620
+ # mass flow: 1
621
+ # pressure: 2
622
+ # enthalpy: 1
623
+ # saturated liquid outlet: 1
624
+ # saturated gas outlet: 1
625
+ self.num_eq = self.num_nw_fluids * 2 + 6
626
+
627
+ self.jacobian = np.zeros((
628
+ self.num_eq,
629
+ self.num_i + self.num_o + self.num_vars,
630
+ self.num_nw_vars))
631
+
632
+ self.residual = np.zeros(self.num_eq)
633
+ pos = self.num_nw_fluids * 2
634
+ self.jacobian[0:pos] = self.fluid_deriv()
635
+ self.jacobian[pos:pos + 1] = self.mass_flow_deriv()
636
+ self.jacobian[pos + 1:pos + 3] = self.pressure_deriv()
637
+
638
+ def equations(self):
639
+ r"""Calculate residual vector with results of equations."""
640
+ k = 0
641
+ ######################################################################
642
+ # eqations for fluid balance
643
+ self.residual[k:k + self.num_nw_fluids * 2] = self.fluid_func()
644
+ k += self.num_nw_fluids * 2
645
+
646
+ ######################################################################
647
+ # eqations for mass flow balance
648
+ self.residual[k] = self.mass_flow_func()
649
+ k += 1
650
+
651
+ ######################################################################
652
+ # eqations for pressure
653
+ p = self.inl[0].p.val_SI
654
+ for c in self.outl:
655
+ self.residual[k] = p - c.p.val_SI
656
+ k += 1
657
+
658
+ ######################################################################
659
+ # eqations for enthalpy
660
+ val = 0
661
+ for i in self.inl:
662
+ val += i.m.val_SI * i.h.val_SI
663
+ for o in self.outl:
664
+ val -= o.m.val_SI * o.h.val_SI
665
+ self.residual[k] = val
666
+ k += 1
667
+
668
+ ######################################################################
669
+ # eqations for staturated fluid state at outlets
670
+ self.residual[k] = h_mix_pQ(
671
+ self.outl[0].to_flow(), 1) - self.outl[0].h.val_SI
672
+ k += 1
673
+ self.residual[k] = h_mix_pQ(
674
+ self.outl[1].to_flow(), 0) - self.outl[1].h.val_SI
675
+ k += 1
676
+
677
+ def derivatives(self, increment_filter):
678
+ r"""Calculate partial derivatives for given equations."""
679
+ ######################################################################
680
+ # derivatives fluid, mass flow and pressure balance are static
681
+ k = self.num_nw_fluids * 2 + 3
682
+
683
+ ######################################################################
684
+ # derivatives for energy balance equation
685
+ self.jacobian[k, 0, 0] = self.inl[0].h.val_SI
686
+ self.jacobian[k, 0, 2] = self.inl[0].m.val_SI
687
+ j = 0
688
+ for outl in self.outl:
689
+ self.jacobian[k, j + 1, 0] = -outl.h.val_SI
690
+ self.jacobian[k, j + 1, 2] = -outl.m.val_SI
691
+ j += 1
692
+ k += 1
693
+
694
+ ######################################################################
695
+ # derivatives of equations for saturated states at outlets
696
+ self.jacobian[k, 1, 1] = dh_mix_dpQ(self.outl[0].to_flow(), 0)
697
+ self.jacobian[k, 1, 2] = -1
698
+ k += 1
699
+ self.jacobian[k, 2, 1] = dh_mix_dpQ(self.outl[1].to_flow(), 1)
700
+ self.jacobian[k, 2, 2] = -1
701
+ k += 1
702
+
703
+ def fluid_func(self):
704
+ r"""
705
+ Calculate the vector of residual values for fluid balance equations.
706
+
707
+ Returns
708
+ -------
709
+ residual : list
710
+ Vector of residual values for component's fluid balance.
711
+
712
+ .. math::
713
+
714
+ 0 = fluid_{i,in_1} - fluid_{i,out_{j}}\\
715
+ \forall i \in \mathrm{fluid}, \; \forall j \in inlets
716
+
717
+ """
718
+ residual = []
719
+
720
+ for o in self.outl:
721
+ for fluid, x in self.inl[0].fluid.val.items():
722
+ residual += [x - o.fluid.val[fluid]]
723
+ return residual
724
+
725
+ def fluid_deriv(self):
726
+ r"""
727
+ Calculate partial derivatives for all fluid balance equations.
728
+
729
+ Returns
730
+ -------
731
+ deriv : ndarray
732
+ Matrix with partial derivatives for the fluid equations.
733
+ """
734
+ deriv = np.zeros((2 * self.num_nw_fluids, 3, self.num_nw_vars))
735
+ for k in range(2):
736
+ for i in range(self.num_nw_fluids):
737
+ deriv[i + k * self.num_nw_fluids, 0, i + 3] = 1
738
+ deriv[i + k * self.num_nw_fluids, k + 1, i + 3] = -1
739
+ return deriv
740
+
741
+ def pressure_deriv(self):
742
+ r"""
743
+ Calculate partial derivatives for pressure equations.
744
+
745
+ Returns
746
+ -------
747
+ deriv : list
748
+ Matrix with partial derivatives for the pressure equations.
749
+ """
750
+ deriv = np.zeros((2, 3, self.num_nw_vars))
751
+ for k in range(2):
752
+ deriv[k, 0, 1] = 1
753
+ deriv[k, k + 1, 1] = -1
754
+ return deriv
755
+
756
+ @staticmethod
757
+ def initialise_source(c, key):
758
+ r"""
759
+ Return a starting value for pressure and enthalpy at outlet.
760
+
761
+ Parameters
762
+ ----------
763
+ c : tespy.connections.connection
764
+ Connection to perform initialisation on.
765
+
766
+ key : str
767
+ Fluid property to retrieve.
768
+
769
+ Returns
770
+ -------
771
+ val : float
772
+ Starting value for pressure/enthalpy in SI units.
773
+
774
+ .. math::
775
+
776
+ val = \begin{cases}
777
+ 10^6 & \text{key = 'p'}\\
778
+ h\left(p, x=1 \right) & \text{key = 'h' at outlet 1}\\
779
+ h\left(p, x=0 \right) & \text{key = 'h' at outlet 2}
780
+ \end{cases}
781
+ """
782
+ if key == 'p':
783
+ return 10e5
784
+ elif key == 'h':
785
+ if c.source_id == 'out1':
786
+ return h_mix_pQ(c.to_flow(), 1)
787
+ else:
788
+ return h_mix_pQ(c.to_flow(), 0)
789
+
790
+ @staticmethod
791
+ def initialise_target(c, key):
792
+ r"""
793
+ Return a starting value for pressure and enthalpy at inlet.
794
+
795
+ Parameters
796
+ ----------
797
+ c : tespy.connections.connection
798
+ Connection to perform initialisation on.
799
+
800
+ key : str
801
+ Fluid property to retrieve.
802
+
803
+ Returns
804
+ -------
805
+ val : float
806
+ Starting value for pressure/enthalpy in SI units.
807
+
808
+ .. math::
809
+
810
+ val = \begin{cases}
811
+ 10^6 & \text{key = 'p'}\\
812
+ h\left(p, x=0.5 \right) & \text{key = 'h' at inlet 1}
813
+ \end{cases}
814
+ """
815
+ if key == 'p':
816
+ return 10e5
817
+ elif key == 'h':
818
+ return h_mix_pQ(c.to_flow(), 0.5)
819
+
820
+ # %%
821
+
822
+
823
+ class drum(component):
824
+ r"""
825
+ A drum separates saturated gas from saturated liquid.
826
+
827
+ Equations
828
+
829
+ **mandatory equations**
830
+
831
+ - :func:`tespy.components.nodes.drum.fluid_func`
832
+ - :func:`tespy.components.components.component.mass_flow_func`
833
+
834
+ .. math::
835
+
836
+ 0 = \sum_i \left(\dot{m}_{i,in} \cdot h_{i,in} \right) -
837
+ \sum_j \left(\dot{m}_{j,out} \cdot h_{j,out} \right)\\
838
+ \forall i \in inlets, \; \forall j \in outlet
839
+
840
+ 0 = p_{in,1} - p_{out,i}\\
841
+ \forall i \in \mathrm{outlets}
842
+
843
+ 0 = h_{1,out} - h\left(p, x=0 \right)
844
+
845
+ 0 = h_{2,out} - h\left(p, x=1 \right)\\
846
+ x: \text{vapour mass fraction}
847
+
848
+ Inlets/Outlets
849
+
850
+ - in1, in2 (index 1: from economiser, index 2: from evaporator)
851
+ - out1, out2 (index 1: to evaporator, index 2: to superheater)
852
+
853
+ Image
854
+
855
+ .. image:: _images/drum.svg
856
+ :scale: 100 %
857
+ :alt: alternative text
858
+ :align: center
859
+
860
+ Parameters
861
+ ----------
862
+ label : str
863
+ The label of the component.
864
+
865
+ design : list
866
+ List containing design parameters (stated as String).
867
+
868
+ offdesign : list
869
+ List containing offdesign parameters (stated as String).
870
+
871
+ design_path: str
872
+ Path to the components design case.
873
+
874
+ local_offdesign : boolean
875
+ Treat this component in offdesign mode in a design calculation.
876
+
877
+ local_design : boolean
878
+ Treat this component in design mode in an offdesign calculation.
879
+
880
+ char_warnings: boolean
881
+ Ignore warnings on default characteristics usage for this component.
882
+
883
+ printout: boolean
884
+ Include this component in the network's results printout.
885
+
886
+ Note
887
+ ----
888
+ If you are using a drum in a network with multiple fluids, it is likely
889
+ the fluid propagation causes trouble. If this is the case, try to
890
+ specify the fluid composition at another connection of your network.
891
+
892
+ This component assumes, that the fluid composition between outlet 1 and
893
+ inlet 2 does not change, thus there is no equation for the fluid mass
894
+ fraction at the inlet 2!
895
+
896
+ Example
897
+ -------
898
+ The drum separates saturated gas from saturated liquid. The liquid phase is
899
+ transported to an evaporator, the staturated gas phase is extracted from
900
+ the drum. In this example ammonia is evaporated using ambient air. A
901
+ characteristic function is applied for the heat transfer coefficient of the
902
+ evaporator. It is possible to load the char_line with the function
903
+ :code:`load_default_char` from the default lines. We want to use the
904
+ 'EVAPORATING FLUID' lines of the heat exchanger.
905
+
906
+ >>> from tespy.components import sink, source, drum, pump, heat_exchanger
907
+ >>> from tespy.connections import connection, ref
908
+ >>> from tespy.networks import network
909
+ >>> from tespy.tools.characteristics import char_line
910
+ >>> from tespy.tools.characteristics import load_default_char as ldc
911
+ >>> import shutil
912
+ >>> import numpy as np
913
+ >>> nw = network(fluids=['NH3', 'air'], T_unit='C', p_unit='bar',
914
+ ... h_unit='kJ / kg', iterinfo=False)
915
+ >>> fa = source('feed ammonia')
916
+ >>> amb_in = source('air inlet')
917
+ >>> amb_out = sink('air outlet')
918
+ >>> s = sink('steam')
919
+ >>> dr = drum('drum')
920
+ >>> dr.component()
921
+ 'drum'
922
+ >>> ev = heat_exchanger('evaporator')
923
+ >>> erp = pump('evaporator reciculation pump')
924
+ >>> f_dr = connection(fa, 'out1', dr, 'in1')
925
+ >>> dr_erp = connection(dr, 'out1', erp, 'in1')
926
+ >>> erp_ev = connection(erp, 'out1', ev, 'in2')
927
+ >>> ev_dr = connection(ev, 'out2', dr, 'in2')
928
+ >>> dr_s = connection(dr, 'out2', s, 'in1')
929
+ >>> nw.add_conns(f_dr, dr_erp, erp_ev, ev_dr, dr_s)
930
+ >>> amb_ev = connection(amb_in, 'out1', ev, 'in1')
931
+ >>> ev_amb = connection(ev, 'out1', amb_out, 'in1')
932
+ >>> nw.add_conns(amb_ev, ev_amb)
933
+
934
+ The ambient air enters the evaporator at 30 °C. The pinch point temperature
935
+ difference (ttd_l) of the evaporator is at 5 K, and 1 MW of heat should be
936
+ transferred. State of ammonia at the inlet is at -5 °C and 5 bar. From this
937
+ design it is possible to calculate offdesign performance at 75 % part load.
938
+
939
+ >>> char1 = ldc('heat exchanger', 'kA_char1', 'DEFAULT',
940
+ ... char_line)
941
+ >>> char2 = ldc('heat exchanger', 'kA_char2', 'EVAPORATING FLUID',
942
+ ... char_line)
943
+ >>> ev.set_attr(pr1=0.999, pr2=0.99, ttd_l=5, kA_char1=char1,
944
+ ... kA_char2=char2, design=['pr1', 'ttd_l'],
945
+ ... offdesign=['zeta1', 'kA_char'])
946
+ >>> ev.set_attr(Q=-1e6)
947
+ >>> erp.set_attr(eta_s=0.8)
948
+ >>> f_dr.set_attr(p=5, T=-5)
949
+ >>> erp_ev.set_attr(m=ref(f_dr, 4, 0), fluid={'air': 0, 'NH3': 1})
950
+ >>> amb_ev.set_attr(fluid={'air': 1, 'NH3': 0}, T=30)
951
+ >>> ev_amb.set_attr(p=1)
952
+ >>> nw.solve('design')
953
+ >>> nw.save('tmp')
954
+ >>> round(ev_amb.T.val - erp_ev.T.val ,1)
955
+ 5.0
956
+ >>> round(f_dr.h.val, 1)
957
+ 322.7
958
+ >>> round(dr_erp.h.val, 1)
959
+ 364.9
960
+ >>> round(ev_dr.h.val, 1)
961
+ 687.2
962
+ >>> round(f_dr.m.val, 2)
963
+ 0.78
964
+ >>> ev.set_attr(Q=-0.75e6)
965
+ >>> nw.solve('offdesign', design_path='tmp')
966
+ >>> round(f_dr.m.val, 2)
967
+ 0.58
968
+ >>> round(ev_amb.T.val - erp_ev.T.val ,1)
969
+ 3.0
970
+ >>> shutil.rmtree('./tmp', ignore_errors=True)
971
+ """
972
+
973
+ @staticmethod
974
+ def component():
975
+ return 'drum'
976
+
977
+ @staticmethod
978
+ def inlets():
979
+ return ['in1', 'in2']
980
+
981
+ @staticmethod
982
+ def outlets():
983
+ return ['out1', 'out2']
984
+
985
+ def comp_init(self, nw):
986
+
987
+ component.comp_init(self, nw)
988
+
989
+ # number of mandatroy equations for
990
+ # fluid balance: num_fl * 2
991
+ # mass flow: 1
992
+ # pressure: 3
993
+ # enthalpy: 1
994
+ # saturated liquid outlet: 1
995
+ # saturated gas outlet: 1
996
+ self.num_eq = self.num_nw_fluids * 2 + 7
997
+
998
+ self.jacobian = np.zeros((
999
+ self.num_eq,
1000
+ self.num_i + self.num_o + self.num_vars,
1001
+ self.num_nw_vars))
1002
+
1003
+ self.residual = np.zeros(self.num_eq)
1004
+ pos = self.num_nw_fluids * 2
1005
+ self.jacobian[0:pos] = self.fluid_deriv()
1006
+ self.jacobian[pos:pos + 1] = self.mass_flow_deriv()
1007
+ self.jacobian[pos + 1:pos + 4] = self.pressure_deriv()
1008
+
1009
+ def equations(self):
1010
+ r"""Calculate residual vector with results of equations."""
1011
+ k = 0
1012
+ ######################################################################
1013
+ # eqations for fluid balance
1014
+ self.residual[k:k + self.num_nw_fluids * 2] = self.fluid_func()
1015
+ k += self.num_nw_fluids * 2
1016
+
1017
+ ######################################################################
1018
+ # eqations for mass flow balance
1019
+ self.residual[k] = self.mass_flow_func()
1020
+ k += 1
1021
+
1022
+ ######################################################################
1023
+ # eqations for pressure
1024
+ p = self.inl[0].p.val_SI
1025
+ for c in [self.inl[1]] + self.outl:
1026
+ self.residual[k] = p - c.p.val_SI
1027
+ k += 1
1028
+
1029
+ ######################################################################
1030
+ # eqations for enthalpy
1031
+ val = 0
1032
+ for i in self.inl:
1033
+ val += i.m.val_SI * i.h.val_SI
1034
+ for o in self.outl:
1035
+ val -= o.m.val_SI * o.h.val_SI
1036
+ self.residual[k] = val
1037
+ k += 1
1038
+
1039
+ ######################################################################
1040
+ # eqations for staturated fluid state at outlets
1041
+ self.residual[k] = h_mix_pQ(
1042
+ self.outl[0].to_flow(), 0) - self.outl[0].h.val_SI
1043
+ k += 1
1044
+ self.residual[k] = h_mix_pQ(
1045
+ self.outl[1].to_flow(), 1) - self.outl[1].h.val_SI
1046
+ k += 1
1047
+
1048
+ def derivatives(self, increment_filter):
1049
+ r"""Calculate partial derivatives for given equations."""
1050
+ ######################################################################
1051
+ # derivatives fluid, mass flow and pressure balance are static
1052
+ k = self.num_nw_fluids * 2 + 4
1053
+
1054
+ ######################################################################
1055
+ # derivatives for energy balance equation
1056
+ i = 0
1057
+ for inl in self.inl:
1058
+ self.jacobian[k, i, 0] = inl.h.val_SI
1059
+ self.jacobian[k, i, 2] = inl.m.val_SI
1060
+ i += 1
1061
+ j = 0
1062
+ for outl in self.outl:
1063
+ self.jacobian[k, j + i, 0] = -outl.h.val_SI
1064
+ self.jacobian[k, j + i, 2] = -outl.m.val_SI
1065
+ j += 1
1066
+ k += 1
1067
+
1068
+ ######################################################################
1069
+ # derivatives of equations for saturated states at outlets
1070
+ self.jacobian[k, 2, 1] = dh_mix_dpQ(self.outl[0].to_flow(), 0)
1071
+ self.jacobian[k, 2, 2] = -1
1072
+ k += 1
1073
+ self.jacobian[k, 3, 1] = dh_mix_dpQ(self.outl[1].to_flow(), 1)
1074
+ self.jacobian[k, 3, 2] = -1
1075
+ k += 1
1076
+
1077
+ def fluid_func(self):
1078
+ r"""
1079
+ Calculate the vector of residual values for fluid balance equations.
1080
+
1081
+ Returns
1082
+ -------
1083
+ residual : list
1084
+ Vector of residual values for component's fluid balance.
1085
+
1086
+ .. math::
1087
+
1088
+ 0 = fluid_{i,in_1} - fluid_{i,out_{j}}\\
1089
+ \forall i \in \mathrm{fluid}, \; \forall j \in inlets
1090
+
1091
+ """
1092
+ residual = []
1093
+
1094
+ for o in self.outl:
1095
+ for fluid, x in self.inl[0].fluid.val.items():
1096
+ residual += [x - o.fluid.val[fluid]]
1097
+ return residual
1098
+
1099
+ def fluid_deriv(self):
1100
+ r"""
1101
+ Calculate partial derivatives for all fluid balance equations.
1102
+
1103
+ Returns
1104
+ -------
1105
+ deriv : ndarray
1106
+ Matrix with partial derivatives for the fluid equations.
1107
+ """
1108
+ deriv = np.zeros((2 * self.num_nw_fluids, 4, self.num_nw_vars))
1109
+ for k in range(2):
1110
+ for i in range(self.num_nw_fluids):
1111
+ deriv[i + k * self.num_nw_fluids, 0, i + 3] = 1
1112
+ deriv[i + k * self.num_nw_fluids, k + 2, i + 3] = -1
1113
+ return deriv
1114
+
1115
+ def pressure_deriv(self):
1116
+ r"""
1117
+ Calculate partial derivatives for pressure equations.
1118
+
1119
+ Returns
1120
+ -------
1121
+ deriv : list
1122
+ Matrix with partial derivatives for the pressure equations.
1123
+ """
1124
+ deriv = np.zeros((3, 4, self.num_nw_vars))
1125
+ for k in range(3):
1126
+ deriv[k, 0, 1] = 1
1127
+ deriv[k, k + 1, 1] = -1
1128
+ return deriv
1129
+
1130
+ @staticmethod
1131
+ def initialise_source(c, key):
1132
+ r"""
1133
+ Return a starting value for pressure and enthalpy at outlet.
1134
+
1135
+ Parameters
1136
+ ----------
1137
+ c : tespy.connections.connection
1138
+ Connection to perform initialisation on.
1139
+
1140
+ key : str
1141
+ Fluid property to retrieve.
1142
+
1143
+ Returns
1144
+ -------
1145
+ val : float
1146
+ Starting value for pressure/enthalpy in SI units.
1147
+
1148
+ .. math::
1149
+
1150
+ val = \begin{cases}
1151
+ 10^6 & \text{key = 'p'}\\
1152
+ h\left(p, x=0 \right) & \text{key = 'h' at outlet 1}\\
1153
+ h\left(p, x=1 \right) & \text{key = 'h' at outlet 2}
1154
+ \end{cases}
1155
+ """
1156
+ if key == 'p':
1157
+ return 10e5
1158
+ elif key == 'h':
1159
+ if c.source_id == 'out1':
1160
+ return h_mix_pQ(c.to_flow(), 0)
1161
+ else:
1162
+ return h_mix_pQ(c.to_flow(), 1)
1163
+
1164
+ @staticmethod
1165
+ def initialise_target(c, key):
1166
+ r"""
1167
+ Return a starting value for pressure and enthalpy at inlet.
1168
+
1169
+ Parameters
1170
+ ----------
1171
+ c : tespy.connections.connection
1172
+ Connection to perform initialisation on.
1173
+
1174
+ key : str
1175
+ Fluid property to retrieve.
1176
+
1177
+ Returns
1178
+ -------
1179
+ val : float
1180
+ Starting value for pressure/enthalpy in SI units.
1181
+
1182
+ .. math::
1183
+
1184
+ val = \begin{cases}
1185
+ 10^6 & \text{key = 'p'}\\
1186
+ h\left(p, x=0 \right) & \text{key = 'h' at inlet 1}\\
1187
+ h\left(p, x=0.7 \right) & \text{key = 'h' at inlet 2}
1188
+ \end{cases}
1189
+ """
1190
+ if key == 'p':
1191
+ return 10e5
1192
+ elif key == 'h':
1193
+ if c.target_id == 'in1':
1194
+ return h_mix_pQ(c.to_flow(), 0)
1195
+ else:
1196
+ return h_mix_pQ(c.to_flow(), 0.7)
1197
+
1198
+ # %%
1199
+
1200
+
1201
+ class merge(node):
1202
+ r"""
1203
+ The component node is the parent class for splitter, separator and merge.
1204
+
1205
+ Equations
1206
+
1207
+ **mandatory equations**
1208
+
1209
+ - :func:`tespy.components.components.component.mass_flow_func`
1210
+
1211
+ .. math::
1212
+
1213
+ 0 = p_{in} - p_{out,i} \;
1214
+ \forall i \in \mathrm{outlets}
1215
+
1216
+ **additional equations**
1217
+
1218
+ - :func:`tespy.components.components.merge.additional_equations`
1219
+
1220
+ Inlets/Outlets
1221
+
1222
+ - specify number of outlets with :code:`num_in` (default value: 2)
1223
+ - out1
1224
+
1225
+ Image
1226
+
1227
+ .. image:: _images/merge.svg
1228
+ :scale: 100 %
1229
+ :alt: alternative text
1230
+ :align: center
1231
+
1232
+ Parameters
1233
+ ----------
1234
+ label : str
1235
+ The label of the component.
1236
+
1237
+ design : list
1238
+ List containing design parameters (stated as String).
1239
+
1240
+ offdesign : list
1241
+ List containing offdesign parameters (stated as String).
1242
+
1243
+ design_path: str
1244
+ Path to the components design case.
1245
+
1246
+ local_offdesign : boolean
1247
+ Treat this component in offdesign mode in a design calculation.
1248
+
1249
+ local_design : boolean
1250
+ Treat this component in design mode in an offdesign calculation.
1251
+
1252
+ char_warnings: boolean
1253
+ Ignore warnings on default characteristics usage for this component.
1254
+
1255
+ printout: boolean
1256
+ Include this component in the network's results printout.
1257
+
1258
+ num_in : float/tespy.tools.data_containers.dc_simple
1259
+ Number of inlets for this component, default value: 2.
1260
+
1261
+ Example
1262
+ -------
1263
+ The merge mixes a specified number of mass flows and has a single outlet.
1264
+ At the outlet, fluid composition and enthalpy are calculated by mass
1265
+ weighted fluid composition and enthalpy of the inlets.
1266
+
1267
+ >>> from tespy.components import sink, source, merge
1268
+ >>> from tespy.connections import connection
1269
+ >>> from tespy.networks import network
1270
+ >>> import shutil
1271
+ >>> import numpy as np
1272
+ >>> fluid_list = ['O2', 'N2']
1273
+ >>> nw = network(fluids=fluid_list, p_unit='bar', iterinfo=False)
1274
+ >>> so1 = source('source1')
1275
+ >>> so2 = source('source2')
1276
+ >>> so3 = source('source3')
1277
+ >>> si1 = sink('sink')
1278
+ >>> m = merge('merge', num_in=3)
1279
+ >>> m.component()
1280
+ 'merge'
1281
+ >>> inc1 = connection(so1, 'out1', m, 'in1')
1282
+ >>> inc2 = connection(so2, 'out1', m, 'in2')
1283
+ >>> inc3 = connection(so3, 'out1', m, 'in3')
1284
+ >>> outg = connection(m, 'out1', si1, 'in1')
1285
+ >>> nw.add_conns(inc1, inc2, inc3, outg)
1286
+
1287
+ A merge with three inlets mixes air (simplified) with pure nitrogen and
1288
+ pure oxygen. All gases enter the component at the same temperature. As
1289
+ mixing effects are not considered, the outlet temperature should thus be
1290
+ similar to the three inlet temperatures (difference might occur due to
1291
+ rounding in fluid property functions, let's check it for two different
1292
+ temperatures). It is e.g. possible to find the required mass flow of pure
1293
+ nitrogen given the nitrogen mass fraction in the outlet.
1294
+
1295
+ >>> T = 293.15
1296
+ >>> inc1.set_attr(fluid={'O2': 0.23, 'N2': 0.77}, p=1, T=T, m=5)
1297
+ >>> inc2.set_attr(fluid={'O2': 1, 'N2':0}, T=T, m=5)
1298
+ >>> inc3.set_attr(fluid={'O2': 0, 'N2': 1}, T=T)
1299
+ >>> outg.set_attr(fluid={'N2': 0.4})
1300
+ >>> nw.solve('design')
1301
+ >>> round(inc3.m.val_SI, 2)
1302
+ 0.25
1303
+ >>> abs(round((outg.T.val_SI - T) / T, 5)) < 0.01
1304
+ True
1305
+ >>> T = 173.15
1306
+ >>> inc1.set_attr(T=T)
1307
+ >>> inc2.set_attr(T=T)
1308
+ >>> inc3.set_attr(T=T)
1309
+ >>> nw.solve('design')
1310
+ >>> abs(round((outg.T.val_SI - T) / T, 5)) < 0.01
1311
+ True
1312
+ """
1313
+
1314
+ @staticmethod
1315
+ def component():
1316
+ return 'merge'
1317
+
1318
+ @staticmethod
1319
+ def attr():
1320
+ return {'num_in': dc_simple()}
1321
+
1322
+ def inlets(self):
1323
+ if self.num_in.is_set:
1324
+ return ['in' + str(i + 1) for i in range(self.num_in.val)]
1325
+ else:
1326
+ self.set_attr(num_in=2)
1327
+ return self.inlets()
1328
+
1329
+ @staticmethod
1330
+ def outlets():
1331
+ return ['out1']
1332
+
1333
+ def comp_init(self, nw):
1334
+
1335
+ component.comp_init(self, nw)
1336
+
1337
+ # number of mandatroy equations for
1338
+ # mass flow: 1
1339
+ # pressure: number of inlets + number of outlets - 1
1340
+ # fluid: number of fluids
1341
+ # enthalpy: 1
1342
+
1343
+ self.num_eq = self.num_i + self.num_o + self.num_nw_fluids + 1
1344
+
1345
+ self.jacobian = np.zeros((
1346
+ self.num_eq,
1347
+ self.num_i + self.num_o + self.num_vars,
1348
+ self.num_nw_vars))
1349
+
1350
+ self.residual = np.zeros(self.num_eq)
1351
+ self.jacobian[0:1] = self.mass_flow_deriv()
1352
+ end = self.num_i + self.num_o
1353
+ self.jacobian[1:end] = self.pressure_deriv()
1354
+
1355
+ def additional_equations(self, k):
1356
+ r"""
1357
+ Calculate results of additional equations.
1358
+
1359
+ Equations
1360
+
1361
+ **mandatroy equations**
1362
+
1363
+ .. math::
1364
+
1365
+ 0 = \dot{m}_{in_{j}} \cdot fluid_{i,in_{j}} -
1366
+ \dot {m}_{out} \cdot fluid_{i,out} \\
1367
+ \forall i \in \mathrm{fluid}, \; \forall j \in inlets
1368
+
1369
+ .. math::
1370
+
1371
+ 0 = h_{in} - h_{out,i} \;
1372
+ \forall i \in \mathrm{outlets}\\
1373
+ """
1374
+ ######################################################################
1375
+ # equations for fluid balance
1376
+ for fluid, x in self.outl[0].fluid.val.items():
1377
+ res = -x * self.outl[0].m.val_SI
1378
+ for i in self.inl:
1379
+ res += i.fluid.val[fluid] * i.m.val_SI
1380
+ self.residual[k] = res
1381
+ k += 1
1382
+
1383
+ ######################################################################
1384
+ # equation for energy balance
1385
+ h_res = -self.outl[0].m.val_SI * self.outl[0].h.val_SI
1386
+ for i in self.inl:
1387
+ h_res += i.m.val_SI * i.h.val_SI
1388
+ self.residual[k] = h_res
1389
+ k += 1
1390
+
1391
+ def additional_derivatives(self, increment_filter, k):
1392
+ r"""Calculate partial derivatives for given additional equations."""
1393
+ ######################################################################
1394
+ # derivatives for fluid balance equations
1395
+ i = 0
1396
+ for fluid, x in self.outl[0].fluid.val.items():
1397
+ j = 0
1398
+ for inl in self.inl:
1399
+ self.jacobian[k, j, 0] = inl.fluid.val[fluid]
1400
+ self.jacobian[k, j, i + 3] = inl.m.val_SI
1401
+ j += 1
1402
+ self.jacobian[k, j, 0] = -x
1403
+ self.jacobian[k, j, i + 3] = -self.outl[0].m.val_SI
1404
+ i += 1
1405
+ k += 1
1406
+
1407
+ ######################################################################
1408
+ # derivatives for energy balance equations
1409
+ self.jacobian[k, self.num_i, 0] = -self.outl[0].h.val_SI
1410
+ self.jacobian[k, self.num_i, 2] = -self.outl[0].m.val_SI
1411
+ j = 0
1412
+ for i in self.inl:
1413
+ self.jacobian[k, j, 0] = i.h.val_SI
1414
+ self.jacobian[k, j, 2] = i.m.val_SI
1415
+ j += 1
1416
+ k += 1
1417
+
1418
+ # %%
1419
+
1420
+
1421
+ class separator(node):
1422
+ r"""
1423
+ A separator separates fluid components from a mass flow.
1424
+
1425
+ Equations
1426
+
1427
+ **mandatory equations**
1428
+
1429
+ - :func:`tespy.components.components.component.mass_flow_func`
1430
+
1431
+ .. math::
1432
+
1433
+ 0 = p_{in} - p_{out,i} \;
1434
+ \forall i \in \mathrm{outlets}
1435
+
1436
+ **additional equations**
1437
+
1438
+ - :func:`tespy.components.nodes.separator.additional_equations`
1439
+
1440
+ Inlets/Outlets
1441
+
1442
+ - in1
1443
+ - specify number of outlets with :code:`num_out` (default value: 2)
1444
+
1445
+ Image
1446
+
1447
+ .. image:: _images/split.svg
1448
+ :scale: 100 %
1449
+ :alt: alternative text
1450
+ :align: center
1451
+
1452
+ Note
1453
+ ----
1454
+ Fluid separation requires power and cooling, equations have not been
1455
+ implemented, yet!
1456
+
1457
+ Parameters
1458
+ ----------
1459
+ label : str
1460
+ The label of the component.
1461
+
1462
+ design : list
1463
+ List containing design parameters (stated as String).
1464
+
1465
+ offdesign : list
1466
+ List containing offdesign parameters (stated as String).
1467
+
1468
+ design_path: str
1469
+ Path to the components design case.
1470
+
1471
+ local_offdesign : boolean
1472
+ Treat this component in offdesign mode in a design calculation.
1473
+
1474
+ local_design : boolean
1475
+ Treat this component in design mode in an offdesign calculation.
1476
+
1477
+ char_warnings: boolean
1478
+ Ignore warnings on default characteristics usage for this component.
1479
+
1480
+ printout: boolean
1481
+ Include this component in the network's results printout.
1482
+
1483
+ num_out : float/tespy.tools.data_containers.dc_simple
1484
+ Number of outlets for this component, default value: 2.
1485
+
1486
+ Example
1487
+ -------
1488
+ The separator is used to split up a single mass flow into a specified
1489
+ number of different parts at identical pressure and temperature but
1490
+ different fluid composition. Fluids can be separated from each other.
1491
+
1492
+ >>> from tespy.components import sink, source, separator
1493
+ >>> from tespy.connections import connection
1494
+ >>> from tespy.networks import network
1495
+ >>> import shutil
1496
+ >>> import numpy as np
1497
+ >>> fluid_list = ['O2', 'N2']
1498
+ >>> nw = network(fluids=fluid_list, p_unit='bar', T_unit='C',
1499
+ ... iterinfo=False)
1500
+ >>> so = source('source')
1501
+ >>> si1 = sink('sink1')
1502
+ >>> si2 = sink('sink2')
1503
+ >>> s = separator('separator', num_out=2)
1504
+ >>> s.component()
1505
+ 'separator'
1506
+ >>> inc = connection(so, 'out1', s, 'in1')
1507
+ >>> outg1 = connection(s, 'out1', si1, 'in1')
1508
+ >>> outg2 = connection(s, 'out2', si2, 'in1')
1509
+ >>> nw.add_conns(inc, outg1, outg2)
1510
+
1511
+ An Air (simplified) mass flow of 5 kg/s is split up into two mass flows.
1512
+ One mass flow of 1 kg/s containing 10 % oxygen and 90 % nitrogen leaves the
1513
+ separator. It is possible to calculate the fluid composition of the second
1514
+ mass flow. Specify starting values for the second mass flow fluid
1515
+ composition for calculation stability.
1516
+
1517
+ >>> inc.set_attr(fluid={'O2': 0.23, 'N2': 0.77}, p=1, T=20, m=5)
1518
+ >>> outg1.set_attr(fluid={'O2': 0.1, 'N2': 0.9}, m=1)
1519
+ >>> outg2.set_attr(fluid0={'O2': 0.5, 'N2': 0.5})
1520
+ >>> nw.solve('design')
1521
+ >>> outg2.fluid.val['O2']
1522
+ 0.2625
1523
+
1524
+ In the same way, it is possible to specify one of the fluid components in
1525
+ the second mass flow instead of the first mass flow. The solver will find
1526
+ the mass flows matching the desired composition. 65 % of the mass flow
1527
+ will leave the separator at the second outlet the case of 30 % oxygen
1528
+ mass fraction for this outlet.
1529
+
1530
+ >>> outg1.set_attr(m=np.nan)
1531
+ >>> outg2.set_attr(fluid={'O2': 0.3})
1532
+ >>> nw.solve('design')
1533
+ >>> outg2.fluid.val['O2']
1534
+ 0.3
1535
+ >>> round(outg2.m.val_SI / inc.m.val_SI, 2)
1536
+ 0.65
1537
+ """
1538
+
1539
+ @staticmethod
1540
+ def component():
1541
+ return 'separator'
1542
+
1543
+ @staticmethod
1544
+ def attr():
1545
+ return {'num_out': dc_simple()}
1546
+
1547
+ @staticmethod
1548
+ def inlets():
1549
+ return ['in1']
1550
+
1551
+ def outlets(self):
1552
+ if self.num_out.is_set:
1553
+ return ['out' + str(i + 1) for i in range(self.num_out.val)]
1554
+ else:
1555
+ self.set_attr(num_out=2)
1556
+ return self.outlets()
1557
+
1558
+ def comp_init(self, nw):
1559
+
1560
+ component.comp_init(self, nw)
1561
+
1562
+ # number of mandatroy equations for
1563
+ # mass flow: 1
1564
+ # pressure: number of inlets + number of outlets - 1
1565
+ # fluid: number of fluids
1566
+ # enthalpy: number of outlets
1567
+
1568
+ self.num_eq = self.num_i + self.num_o * 2 + self.num_nw_fluids
1569
+
1570
+ self.jacobian = np.zeros((
1571
+ self.num_eq,
1572
+ self.num_i + self.num_o + self.num_vars,
1573
+ self.num_nw_vars))
1574
+
1575
+ self.residual = np.zeros(self.num_eq)
1576
+ self.jacobian[0:1] = self.mass_flow_deriv()
1577
+ end = self.num_i + self.num_o
1578
+ self.jacobian[1:end] = self.pressure_deriv()
1579
+
1580
+ def additional_equations(self, k):
1581
+ r"""
1582
+ Calculate results of additional equations.
1583
+
1584
+ Equations
1585
+
1586
+ **mandatroy equations**
1587
+
1588
+ .. math:: 0 = fluid_{i,in} - fluid_{i,out_{j}} \;
1589
+ \forall i \in \mathrm{fluid}, \; \forall j \in outlets
1590
+
1591
+ .. math::
1592
+
1593
+ 0 = T_{in} - T_{out,i} \;
1594
+ \forall i \in \mathrm{outlets}
1595
+ """
1596
+ ######################################################################
1597
+ # equations for fluid balance
1598
+ for fluid, x in self.inl[0].fluid.val.items():
1599
+ res = x * self.inl[0].m.val_SI
1600
+ for o in self.outl:
1601
+ res -= o.fluid.val[fluid] * o.m.val_SI
1602
+ self.residual[k] = res
1603
+ k += 1
1604
+
1605
+ ######################################################################
1606
+ # equations for energy balance
1607
+ for o in self.outl:
1608
+ self.residual[k] = (
1609
+ T_mix_ph(self.inl[0].to_flow(), T0=self.inl[0].T.val_SI) -
1610
+ T_mix_ph(o.to_flow(), T0=o.T.val_SI))
1611
+ k += 1
1612
+
1613
+ def additional_derivatives(self, increment_filter, k):
1614
+ r"""Calculate partial derivatives for given additional equations."""
1615
+ ######################################################################
1616
+ # derivatives for fluid balance equations
1617
+ i = 0
1618
+ for fluid in self.nw_fluids:
1619
+ j = 0
1620
+ for o in self.outl:
1621
+ self.jacobian[k, j + 1, 0] = -o.fluid.val[fluid]
1622
+ self.jacobian[k, j + 1, i + 3] = -o.m.val_SI
1623
+ j += 1
1624
+ self.jacobian[k, 0, 0] = self.inl[0].fluid.val[fluid]
1625
+ self.jacobian[k, 0, i + 3] = self.inl[0].m.val_SI
1626
+ k += 1
1627
+ i += 1
1628
+
1629
+ ######################################################################
1630
+ # derivatives for energy balance equations
1631
+ i = self.inl[0].to_flow()
1632
+ j = 0
1633
+ for o in self.outl:
1634
+ o = o.to_flow()
1635
+ self.jacobian[k, 0, 1] = dT_mix_dph(i)
1636
+ self.jacobian[k, 0, 2] = dT_mix_pdh(i)
1637
+ self.jacobian[k, 0, 3:] = dT_mix_ph_dfluid(i)
1638
+ self.jacobian[k, j + 1, 1] = -dT_mix_dph(o)
1639
+ self.jacobian[k, j + 1, 2] = -dT_mix_pdh(o)
1640
+ self.jacobian[k, j + 1, 3:] = -1 * dT_mix_ph_dfluid(o)
1641
+ j += 1
1642
+ k += 1
1643
+
1644
+ @staticmethod
1645
+ def initialise_fluids(nw):
1646
+ r"""
1647
+ Fluid initialisation for fluid mixture at outlet of the node.
1648
+
1649
+ Parameters
1650
+ ----------
1651
+ nw : tespy.networks.network
1652
+ Network using this component object.
1653
+ """
1654
+ return
1655
+
1656
+ # %%
1657
+
1658
+
1659
+ class splitter(node):
1660
+ r"""
1661
+ Split up a mass flow in parts of identical enthalpy and fluid composition.
1662
+
1663
+ Equations
1664
+
1665
+ **mandatory equations**
1666
+
1667
+ - :func:`tespy.components.components.component.mass_flow_func`
1668
+
1669
+ .. math::
1670
+
1671
+ 0 = p_{in} - p_{out,i} \;
1672
+ \forall i \in \mathrm{outlets}
1673
+
1674
+ **additional equations**
1675
+
1676
+ - :func:`tespy.components.nodes.splitter.additional_equations`
1677
+
1678
+ Inlets/Outlets
1679
+
1680
+ - in1
1681
+ - specify number of outlets with :code:`num_out` (default value: 2)
1682
+
1683
+ Image
1684
+
1685
+ .. image:: _images/split.svg
1686
+ :scale: 100 %
1687
+ :alt: alternative text
1688
+ :align: center
1689
+
1690
+ Parameters
1691
+ ----------
1692
+ label : str
1693
+ The label of the component.
1694
+
1695
+ design : list
1696
+ List containing design parameters (stated as String).
1697
+
1698
+ offdesign : list
1699
+ List containing offdesign parameters (stated as String).
1700
+
1701
+ design_path: str
1702
+ Path to the components design case.
1703
+
1704
+ local_offdesign : boolean
1705
+ Treat this component in offdesign mode in a design calculation.
1706
+
1707
+ local_design : boolean
1708
+ Treat this component in design mode in an offdesign calculation.
1709
+
1710
+ char_warnings: boolean
1711
+ Ignore warnings on default characteristics usage for this component.
1712
+
1713
+ printout: boolean
1714
+ Include this component in the network's results printout.
1715
+
1716
+ num_out : float/tespy.tools.data_containers.dc_simple
1717
+ Number of outlets for this component, default value: 2.
1718
+
1719
+ Example
1720
+ -------
1721
+ A splitter is used to split up a single mass flow into a specified number
1722
+ of different parts at identical pressure, enthalpy and fluid composition.
1723
+
1724
+ >>> from tespy.components import sink, source, splitter
1725
+ >>> from tespy.connections import connection
1726
+ >>> from tespy.networks import network
1727
+ >>> import shutil
1728
+ >>> import numpy as np
1729
+ >>> fluid_list = ['O2', 'N2']
1730
+ >>> nw = network(fluids=fluid_list, p_unit='bar', T_unit='C',
1731
+ ... iterinfo=False)
1732
+ >>> so = source('source')
1733
+ >>> si1 = sink('sink1')
1734
+ >>> si2 = sink('sink2')
1735
+ >>> si3 = sink('sink3')
1736
+ >>> s = splitter('splitter', num_out=3)
1737
+ >>> s.component()
1738
+ 'splitter'
1739
+ >>> inc = connection(so, 'out1', s, 'in1')
1740
+ >>> outg1 = connection(s, 'out1', si1, 'in1')
1741
+ >>> outg2 = connection(s, 'out2', si2, 'in1')
1742
+ >>> outg3 = connection(s, 'out3', si3, 'in1')
1743
+ >>> nw.add_conns(inc, outg1, outg2, outg3)
1744
+
1745
+ An Air (simplified) mass flow is split up into three mass flows. The total
1746
+ incoming mass flow is 5 kg/s, 3 kg/s and 1 kg/s respectively are leaving
1747
+ the splitter into the first two outlets. The residual mass flow will
1748
+ drain in the last outlet. Temperature and fluid composition will not
1749
+ change.
1750
+
1751
+ >>> inc.set_attr(fluid={'O2': 0.23, 'N2': 0.77}, p=1, T=20, m=5)
1752
+ >>> outg1.set_attr(m=3)
1753
+ >>> outg2.set_attr(m=1)
1754
+ >>> nw.solve('design')
1755
+ >>> round(outg3.m.val_SI, 1)
1756
+ 1.0
1757
+ >>> round(inc.T.val, 1)
1758
+ 20.0
1759
+ >>> round(outg3.T.val, 1)
1760
+ 20.0
1761
+ """
1762
+
1763
+ @staticmethod
1764
+ def component():
1765
+ return 'splitter'
1766
+
1767
+ @staticmethod
1768
+ def attr():
1769
+ return {'num_out': dc_simple()}
1770
+
1771
+ @staticmethod
1772
+ def inlets():
1773
+ return ['in1']
1774
+
1775
+ def outlets(self):
1776
+ if self.num_out.is_set:
1777
+ return ['out' + str(i + 1) for i in range(self.num_out.val)]
1778
+ else:
1779
+ self.set_attr(num_out=2)
1780
+ return self.outlets()
1781
+
1782
+ def comp_init(self, nw):
1783
+
1784
+ component.comp_init(self, nw)
1785
+
1786
+ # number of mandatroy equations for
1787
+ # mass flow: 1
1788
+ # pressure: number of inlets + number of outlets - 1
1789
+ # fluid: number of outlets * number of fluid_set
1790
+ # enthalpy: number of outlets
1791
+
1792
+ self.num_eq = self.num_i + self.num_o * (2 + self.num_nw_fluids)
1793
+
1794
+ self.jacobian = np.zeros((
1795
+ self.num_eq,
1796
+ self.num_i + self.num_o + self.num_vars,
1797
+ self.num_nw_vars))
1798
+
1799
+ self.residual = np.zeros(self.num_eq)
1800
+ self.jacobian[0:1] = self.mass_flow_deriv()
1801
+ end = self.num_i + self.num_o
1802
+ self.jacobian[1:end] = self.pressure_deriv()
1803
+ start = end
1804
+ end = start + self.num_o * self.num_nw_fluids
1805
+ self.jacobian[start:end] = self.fluid_deriv()
1806
+ start = end
1807
+ end = start + self.num_o
1808
+ self.jacobian[start:end] = self.enthalpy_deriv()
1809
+
1810
+ def additional_equations(self, k):
1811
+ r"""
1812
+ Calculate results of additional equations.
1813
+
1814
+ Equations
1815
+
1816
+ **mandatroy equations**
1817
+
1818
+ .. math:: 0 = fluid_{i,in} - fluid_{i,out_{j}} \;
1819
+ \forall i \in \mathrm{fluid}, \; \forall j \in outlets
1820
+
1821
+ .. math::
1822
+ 0 = h_{in} - h_{out,i} \;
1823
+ \forall i \in \mathrm{outlets}\\
1824
+ """
1825
+ ######################################################################
1826
+ # equations for fluid balance
1827
+ for o in self.outl:
1828
+ for fluid, x in self.inl[0].fluid.val.items():
1829
+ self.residual[k] = x - o.fluid.val[fluid]
1830
+ k += 1
1831
+
1832
+ ######################################################################
1833
+ # equations for energy balance
1834
+ for o in self.outl:
1835
+ self.residual[k] = self.inl[0].h.val_SI - o.h.val_SI
1836
+ k += 1
1837
+
1838
+ def additional_derivatives(self, increment_filter, k):
1839
+ r"""Calculate partial derivatives for given additional equations."""
1840
+ return
1841
+
1842
+ def fluid_deriv(self):
1843
+ r"""
1844
+ Calculate partial derivatives for all fluid balance equations.
1845
+
1846
+ Returns
1847
+ -------
1848
+ deriv : list
1849
+ Matrix with partial derivatives for the fluid equations.
1850
+ """
1851
+ deriv = np.zeros((self.num_nw_fluids * self.num_o, 1 + self.num_o,
1852
+ self.num_nw_vars))
1853
+ k = 0
1854
+ for o in self.outl:
1855
+ i = 0
1856
+ for fluid in self.nw_fluids:
1857
+ deriv[i + k * self.num_nw_fluids, 0, i + 3] = 1
1858
+ deriv[i + k * self.num_nw_fluids, k + 1, i + 3] = -1
1859
+ i += 1
1860
+ k += 1
1861
+ return deriv
1862
+
1863
+ def enthalpy_deriv(self):
1864
+ r"""
1865
+ Calculate partial derivatives for enthalpy balance equation.
1866
+
1867
+ Returns
1868
+ -------
1869
+ deriv : list
1870
+ Matrix of partial derivatives.
1871
+ """
1872
+ deriv = np.zeros((self.num_o, 1 + self.num_o, self.num_nw_vars))
1873
+ k = 0
1874
+ for o in self.outl:
1875
+ deriv[k, 0, 2] = 1
1876
+ deriv[k, k + 1, 2] = -1
1877
+ k += 1
1878
+ return deriv
1879
+
1880
+ @staticmethod
1881
+ def initialise_fluids(nw):
1882
+ r"""
1883
+ Fluid initialisation for fluid mixture at outlet of the node.
1884
+
1885
+ Parameters
1886
+ ----------
1887
+ nw : tespy.networks.network
1888
+ Network using this component object.
1889
+ """
1890
+ return