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,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))