-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathHeatExchangers.py
1516 lines (1147 loc) · 106 KB
/
HeatExchangers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
###############################################################################
###############################################################################
#Copyright (c) 2016, Andy Schroder
#See the file README.md for licensing information.
###############################################################################
###############################################################################
#import packages needed by functions defined in this file
from scipy.optimize import root
from FluidProperties.REFPROP import *
from numpy import arange,linspace,diff,gradient,zeros_like,isnan,argmin
from helpers import PrintWarning,SmartDictionary,CheckPercentage,PrintKeysAndValues
from copy import deepcopy #import because regular python doesn't automatically have deepcopy imported like sage does
#solve the recuperator and also find the unknown pressure drops which are a function of the tempererature drop, assuming the solution converges rather than diverges from the initial guess.
def GeneralRealRecuperator(Recuperator,RecuperatorInputParameters):
#define the convergence parameters
# ConvergenceCriteria=1.0e-6
ConvergenceCriteria=1.0e-5
SecondaryConvergenceCriteria=3.0e-3
MaxIterations=20
#may want to add a relaxation factor to be able to get these convergence criteria to be lowered
#keep in mind that the resolution of the data being interpolated is the real upper limit on the usefulness of this value
NumberofTemperatures=RecuperatorInputParameters['NumberofTemperatures']
#first define the initial guess, which is a pressure drop based on no temperature difference at the exit of the high and low pressure sides and no heaters and coolers.
TestRecuperator=deepcopy(Recuperator)
#might want to make this smarter if the recuperated values are defined? including the pressures which already may be calculated in the cycle.
TestRecuperator['HighPressure']['RecuperatedProperties']['Temperature']=Recuperator['LowPressure']['StartingProperties']['Temperature']
TestRecuperator['LowPressure']['RecuperatedProperties']['Temperature']=Recuperator['HighPressure']['StartingProperties']['Temperature']
TestRecuperator['HighPressure']['ActualRecuperatedProperties']['Temperature']=Recuperator['LowPressure']['StartingProperties']['Temperature']
TestRecuperator['LowPressure']['ActualRecuperatedProperties']['Temperature']=Recuperator['HighPressure']['StartingProperties']['Temperature']
TestRecuperator['LowPressure']['ActualStartingProperties']['Temperature']=Recuperator['LowPressure']['StartingProperties']['Temperature']
TestRecuperator['HighPressure']['ActualStartingProperties']['Temperature']=Recuperator['HighPressure']['StartingProperties']['Temperature']
for count in arange(0,MaxIterations):
#calculate the recuperated pressures from the currently guessed recuperated temperature
HighPressureDeltaT=TestRecuperator['HighPressure']['RecuperatedProperties']['Temperature']-TestRecuperator['HighPressure']['StartingProperties']['Temperature']
HighPressureActualDeltaT=TestRecuperator['HighPressure']['ActualRecuperatedProperties']['Temperature']-TestRecuperator['HighPressure']['ActualStartingProperties']['Temperature']
HighPressureLowerDeltaT=TestRecuperator['HighPressure']['ActualStartingProperties']['Temperature']-TestRecuperator['HighPressure']['StartingProperties']['Temperature']
#don't care about HighPressureUpperDeltaT because if the others are correct, then it will be, and the properties needed below can be calculated without it.
LowPressureDeltaT=TestRecuperator['LowPressure']['StartingProperties']['Temperature']-TestRecuperator['LowPressure']['RecuperatedProperties']['Temperature']
LowPressureActualDeltaT=TestRecuperator['LowPressure']['ActualStartingProperties']['Temperature']-TestRecuperator['LowPressure']['ActualRecuperatedProperties']['Temperature']
LowPressureUpperDeltaT=TestRecuperator['LowPressure']['StartingProperties']['Temperature']-TestRecuperator['LowPressure']['ActualStartingProperties']['Temperature']
#don't care about LowPressureLowerDeltaT because if the others are correct, then it will be, and the properties needed below can be calculated without it.
LowPressureRatio=1-LowPressureDeltaT*RecuperatorInputParameters['DeltaPPerDeltaT']/TestRecuperator['LowPressure']['StartingProperties']['Pressure']
LowPressureUpperPressureRatio=1-LowPressureUpperDeltaT*RecuperatorInputParameters['DeltaPPerDeltaT']/TestRecuperator['LowPressure']['StartingProperties']['Pressure']
Recuperator['LowPressure']['ActualStartingProperties']['Pressure']=LowPressureUpperPressureRatio*TestRecuperator['LowPressure']['StartingProperties']['Pressure']
LowPressureActualPressureRatio=1-LowPressureActualDeltaT*RecuperatorInputParameters['DeltaPPerDeltaT']/Recuperator['LowPressure']['ActualStartingProperties']['Pressure']
#don't care about LowPressureLowerPressureRatio because if the others are correct, then it will be, and the properties needed below can be calculated without it.
Recuperator['LowPressure']['RecuperatedProperties']['Pressure']=Recuperator['LowPressure']['StartingProperties']['Pressure']*LowPressureRatio
Recuperator['LowPressure']['ActualRecuperatedProperties']['Pressure']=Recuperator['LowPressure']['ActualStartingProperties']['Pressure']*LowPressureActualPressureRatio
if RecuperatorInputParameters['HighPressure']['ConstantVolume'] is True:
#there is constant volume heat addition and the pressure rises instead of drops in the heat exchanger high pressure side.
EstimatedActualTemperatures=linspace(
TestRecuperator['HighPressure']['ActualStartingProperties']['Temperature'],
TestRecuperator['LowPressure']['ActualStartingProperties']['Temperature'],
NumberofTemperatures
)
#need to calculate the pressure as a function of temperature and the fixed density
#get the density
TestRecuperator['HighPressure']['StartingProperties']=GetFluidProperties(
Temperature = TestRecuperator['HighPressure']['StartingProperties']['Temperature'],
Pressure = TestRecuperator['HighPressure']['StartingProperties']['Pressure'],
ExtendedProperties = True
)
#calculate pressure using the constant density and the guessed temperature
TestRecuperator['HighPressure']['ActualStartingProperties']=GetFluidProperties(
Temperature = TestRecuperator['HighPressure']['ActualStartingProperties']['Temperature'],
Density = TestRecuperator['HighPressure']['StartingProperties']['Density'],
ExtendedProperties = True
)
#calculate pressure using the constant density and the guessed temperature
TestRecuperator['HighPressure']['ActualRecuperatedProperties']=GetFluidProperties(
Temperature = TestRecuperator['HighPressure']['ActualRecuperatedProperties']['Temperature'],
Density = TestRecuperator['HighPressure']['ActualStartingProperties']['Density'],
ExtendedProperties = True
)
#calcuate pressure using the constant density and the guessed temperature
TestRecuperator['HighPressure']['RecuperatedProperties']=GetFluidProperties(
Temperature = TestRecuperator['HighPressure']['RecuperatedProperties']['Temperature'],
Density = TestRecuperator['HighPressure']['ActualRecuperatedProperties']['Density'],
ExtendedProperties = True
)
#calculate the pressure ratios
HighPressureRatio=TestRecuperator['HighPressure']['RecuperatedProperties']['Pressure']/TestRecuperator['HighPressure']['StartingProperties']['Pressure']
HighPressureLowerPressureRatio=TestRecuperator['HighPressure']['ActualStartingProperties']['Pressure']/TestRecuperator['HighPressure']['StartingProperties']['Pressure']
HighPressureActualPressureRatio=TestRecuperator['HighPressure']['ActualRecuperatedProperties']['Pressure']/TestRecuperator['HighPressure']['ActualStartingProperties']['Pressure']
#calculate the pressure for each temperature using the constant density
Recuperator['HighPressure']['ActualPressures']=PressureFromTemperatureDensity(
EstimatedActualTemperatures,
TestRecuperator['HighPressure']['ActualStartingProperties']['Density']
)
#assign pressure only, and not everything else, because if temperature is defined, then other parts of the logic think it is an explicit value and not an initial guess and those parts aren't setup for the constant volume case.
Recuperator['HighPressure']['StartingProperties']['Pressure']=TestRecuperator['HighPressure']['StartingProperties']['Pressure']
Recuperator['HighPressure']['ActualStartingProperties']['Pressure']=TestRecuperator['HighPressure']['ActualStartingProperties']['Pressure']
Recuperator['HighPressure']['ActualRecuperatedProperties']['Pressure']=TestRecuperator['HighPressure']['ActualRecuperatedProperties']['Pressure']
Recuperator['HighPressure']['RecuperatedProperties']['Pressure']=TestRecuperator['HighPressure']['RecuperatedProperties']['Pressure']
#still need to consider some pressure loss during the inflow and outflow phases somehow
else:
#constant pressure on both the high and low pressure sides
HighPressureRatio=1-HighPressureDeltaT*RecuperatorInputParameters['DeltaPPerDeltaT']/TestRecuperator['HighPressure']['StartingProperties']['Pressure']
HighPressureLowerPressureRatio=1-HighPressureLowerDeltaT*RecuperatorInputParameters['DeltaPPerDeltaT']/TestRecuperator['HighPressure']['StartingProperties']['Pressure']
Recuperator['HighPressure']['ActualStartingProperties']['Pressure']=HighPressureLowerPressureRatio*TestRecuperator['HighPressure']['StartingProperties']['Pressure']
HighPressureActualPressureRatio=1-HighPressureActualDeltaT*RecuperatorInputParameters['DeltaPPerDeltaT']/Recuperator['HighPressure']['ActualStartingProperties']['Pressure']
#don't care about HighPressureUpperPressureRatio because if the others are correct, then it will be, and the properties needed below can be calculated without it.
Recuperator['HighPressure']['RecuperatedProperties']['Pressure']=Recuperator['HighPressure']['StartingProperties']['Pressure']*HighPressureRatio
Recuperator['HighPressure']['ActualRecuperatedProperties']['Pressure']=Recuperator['HighPressure']['ActualStartingProperties']['Pressure']*HighPressureActualPressureRatio
#normal heat exchanger with a linear pressure drop that occurs during the heat exchanger
Recuperator['HighPressure']['ActualPressures']=linspace(
Recuperator['HighPressure']['ActualStartingProperties']['Pressure'],
Recuperator['HighPressure']['ActualRecuperatedProperties']['Pressure'],
NumberofTemperatures
)
if count==0:
#limit the initial guess to .5 because it seems to be way off sometimes and causes the property data to go out of range
if HighPressureRatio<.5:
HighPressureRatio=.5
HighPressureActualPressureRatio=.5 #on the first iteration these are going to be the same so don't need to test this one in the if statement
if LowPressureRatio<.5:
LowPressureRatio=.5
LowPressureActualPressureRatio=.5 #on the first iteration these are going to be the same so don't need to test this one in the if statement
#print out some indication of the convergence process for debugging
PrintWarning("Pressure Ratio Iteration: "+str(count))
PrintWarning("HighPressureRatio: "+str(HighPressureRatio)+", HighPressureLowerPressureRatio: "+str(HighPressureLowerPressureRatio)+", HighPressureActualPressureRatio: "+str(HighPressureActualPressureRatio))
PrintWarning("LowPressureRatio: "+str(LowPressureRatio)+", LowPressureUpperPressureRatio: "+str(LowPressureUpperPressureRatio)+", LowPressureActualPressureRatio: "+str(LowPressureActualPressureRatio))
#perform the iteration
TestRecuperator=RealRecuperatorWithHeatOrCool(Recuperator,RecuperatorInputParameters)
#check each pressure for convergence
#first see if the residual is really low
if count!=0: #not going to get it on the first iteration and TestRecuperatorOld is also not defined yet
HighPressureResidual=abs(1-TestRecuperator['HighPressure']['RecuperatedProperties']['Pressure']/TestRecuperatorOld['HighPressure']['RecuperatedProperties']['Pressure'])
HighPressureActualRecuperatedPressureResidual=abs(1-TestRecuperator['HighPressure']['ActualRecuperatedProperties']['Pressure']/TestRecuperatorOld['HighPressure']['ActualRecuperatedProperties']['Pressure'])
HighPressureActualStartingPressureResidual=abs(1-TestRecuperator['HighPressure']['ActualStartingProperties']['Pressure']/TestRecuperatorOld['HighPressure']['ActualStartingProperties']['Pressure'])
PrintWarning("high pressure residual: "+str(HighPressureResidual)+", HighPressureActualRecuperatedPressureResidual: "+str(HighPressureActualRecuperatedPressureResidual)+", HighPressureActualStartingPressureResidual: "+str(HighPressureActualStartingPressureResidual))
LowPressureResidual=abs(1-TestRecuperator['LowPressure']['RecuperatedProperties']['Pressure']/TestRecuperatorOld['LowPressure']['RecuperatedProperties']['Pressure'])
LowPressureActualRecuperatedPressureResidual=abs(1-TestRecuperator['LowPressure']['ActualRecuperatedProperties']['Pressure']/TestRecuperatorOld['LowPressure']['ActualRecuperatedProperties']['Pressure'])
LowPressureActualStartingPressureResidual=abs(1-TestRecuperator['LowPressure']['ActualStartingProperties']['Pressure']/TestRecuperatorOld['LowPressure']['ActualStartingProperties']['Pressure'])
PrintWarning("low pressure residual: "+str(LowPressureResidual)+", LowPressureActualRecuperatedPressureResidual: "+str(LowPressureActualRecuperatedPressureResidual)+", LowPressureActualStartingPressureResidual: "+str(LowPressureActualStartingPressureResidual))
if (
(HighPressureResidual<ConvergenceCriteria) and
(HighPressureActualRecuperatedPressureResidual<ConvergenceCriteria) and
(HighPressureActualStartingPressureResidual<ConvergenceCriteria) and
(LowPressureResidual<ConvergenceCriteria) and
(LowPressureActualRecuperatedPressureResidual<ConvergenceCriteria) and
(LowPressureActualStartingPressureResidual<ConvergenceCriteria)
):
return TestRecuperator
#next, see if even if the residual is not low, is it oscillating around a fixed value?
if count>1:
if (
#need to use CheckPercentage because == wasn't working, apparently due to roundoff errors?
(
#are the last and third last iteration the same?
(CheckPercentage(TestRecuperator['HighPressure']['RecuperatedProperties']['Pressure'], TestRecuperatorOldOld['HighPressure']['RecuperatedProperties']['Pressure'], 1.0e-13)) and
(CheckPercentage(TestRecuperator['HighPressure']['ActualRecuperatedProperties']['Pressure'], TestRecuperatorOldOld['HighPressure']['ActualRecuperatedProperties']['Pressure'], 1.0e-13)) and
(CheckPercentage(TestRecuperator['HighPressure']['ActualStartingProperties']['Pressure'], TestRecuperatorOldOld['HighPressure']['ActualStartingProperties']['Pressure'], 1.0e-13)) and
(CheckPercentage(TestRecuperator['LowPressure']['RecuperatedProperties']['Pressure'], TestRecuperatorOldOld['LowPressure']['RecuperatedProperties']['Pressure'], 1.0e-13)) and
(CheckPercentage(TestRecuperator['LowPressure']['ActualRecuperatedProperties']['Pressure'], TestRecuperatorOldOld['LowPressure']['ActualRecuperatedProperties']['Pressure'], 1.0e-13)) and
(CheckPercentage(TestRecuperator['LowPressure']['ActualStartingProperties']['Pressure'], TestRecuperatorOldOld['LowPressure']['ActualStartingProperties']['Pressure'], 1.0e-13))
)
or
(
#are the last and fourth last iteration the same?
(count>2)
and
(
(CheckPercentage(TestRecuperator['HighPressure']['RecuperatedProperties']['Pressure'], TestRecuperatorOldOldOld['HighPressure']['RecuperatedProperties']['Pressure'], 1.0e-13)) and
(CheckPercentage(TestRecuperator['HighPressure']['ActualRecuperatedProperties']['Pressure'], TestRecuperatorOldOldOld['HighPressure']['ActualRecuperatedProperties']['Pressure'], 1.0e-13)) and
(CheckPercentage(TestRecuperator['HighPressure']['ActualStartingProperties']['Pressure'], TestRecuperatorOldOldOld['HighPressure']['ActualStartingProperties']['Pressure'], 1.0e-13)) and
(CheckPercentage(TestRecuperator['LowPressure']['RecuperatedProperties']['Pressure'], TestRecuperatorOldOldOld['LowPressure']['RecuperatedProperties']['Pressure'], 1.0e-13)) and
(CheckPercentage(TestRecuperator['LowPressure']['ActualRecuperatedProperties']['Pressure'], TestRecuperatorOldOldOld['LowPressure']['ActualRecuperatedProperties']['Pressure'], 1.0e-13)) and
(CheckPercentage(TestRecuperator['LowPressure']['ActualStartingProperties']['Pressure'], TestRecuperatorOldOldOld['LowPressure']['ActualStartingProperties']['Pressure'], 1.0e-13))
)
#add another period length here
)
):
if (
(HighPressureResidual<SecondaryConvergenceCriteria) and
(HighPressureActualRecuperatedPressureResidual<SecondaryConvergenceCriteria) and
(HighPressureActualStartingPressureResidual<SecondaryConvergenceCriteria) and
(LowPressureResidual<SecondaryConvergenceCriteria) and
(LowPressureActualRecuperatedPressureResidual<SecondaryConvergenceCriteria) and
(LowPressureActualStartingPressureResidual<SecondaryConvergenceCriteria)
):
PrintWarning("residuals stopped changing but primary convergence criteria not yet met. secondary convergence criteria met.")
return TestRecuperator
elif count==(MaxIterations-1):
print("pressure(s) did not converge in "+str(MaxIterations)+" iterations. residuals stopped changing but primary and secondary convergence criteria not met.")
return TestRecuperator
#raise Exception("pressure(s) did not converge in "+str(MaxIterations)+" iterations")
#save values for the next iteration to test convergence
if count!=0:
if count!=1:
TestRecuperatorOldOldOld=TestRecuperatorOldOld
TestRecuperatorOldOld=TestRecuperatorOld
TestRecuperatorOld=TestRecuperator
if count==(MaxIterations-1):
raise Exception("pressure(s) did not converge in "+str(MaxIterations)+" iterations. residuals did not stop changing")
#make a higher level function that chooses to use this function or the other heat exchanger function and the third boundary condition will kindof be ignored by the other heat exchanger function
#maybe change the function name?
def RealRecuperatorWithHeatOrCool(Recuperator,RecuperatorInputParameters):
#################################
#about this function
#################################
#this heat exchanger function takes high and low side pressures, and two, three, or four temperatures and calculates the unknown temperatures. at least two of the inputs given must be an inlet
#temperature. if an input is required which is not an inlet temperature, then a third [and fourth] input temperature, which is an inlet, must be defined. however, this third [and fourth]
#inlet temperature can be considered a limit (which may be relevant if the heat exchanger is adding heat or taking away heat from the cycle, and not just transferring heat within the cycle).
#with more than two input temperatures given, a heater or cooler may be needed to account for the difference between the boundary conditions desired and that of a heat exchanger
#(because the problem is over constrained with more than two inputs). the heating and cooling needed is calculated. if the third [and fourth] boundary conditions are considered a limit
#then the heating and cooling can be ignored.
#in short, this function represents a heat exchanger, and if over constrained, a heat exchanger and heaters and coolers that can accomodate for the overconstraint
#################################
#this function accepts dictionaries as inputs
#################################
################################
#ACTUAL values are the conditions at the inlet and outlet of the recuperator.
#NON ACTUAL values are the inlet and outlet of the heaters and coolers that may be placed before or after the recuperator, in this special combined recuperator+heaters+coolers, which is described above.
#if ACTUAL and non-ACTUAL values are the same, there is not a heater or cooler at that end of the recuperator.
################################
#note, think all of the logic and restrictions here would still be the same even if a more general heat exchanger formulation were utilized.
#does condensing cycles work?
########################
#areas for improvement and notes
########################
#want to think about the option of mixing fluid streams that merge (which is for the cases where the outlet boundary streams must be defined) in order to eliminate the
#heaters and coolers from the cycle. this may be an iterative process with the entire cycle. how does the mixing process entropy production compare to the heating or cooling entropy production?
#another thing to think about is the possibility of having only heaters or only coolers, but this would be complicated because it would require details of
#neighboring heat exchangers to be already known, which could be an iterative process. it may be better to not do low temperature heating from an entropy production standpoint.
#also, just thinking, if there is any low temperature heating, the mass flow rate will probably always be very low on the heat rejection side in order to keep the pinching on
#the low temperature end such that the heated sides outlet temperature is correct. and a similiar phenomenon for the coolers.
########################
TemperaturePercentError=0.001
#need to check for LowPressureInletTemperature and HighPressureInletTemperature being defined. can't run without these, even if they are just acting as a maximum or minimum (which
#if both outlets are given, is just needed to figure out where pinching occurs(?)).
if ('Temperature' not in Recuperator['LowPressure']['StartingProperties']) or ('Temperature' not in Recuperator['HighPressure']['StartingProperties']):
raise Exception("Recuperator['LowPressure']['StartingProperties']['Temperature'] or Recuperator['HighPressure']['StartingProperties']['Temperature'] not given and required for at least setting limits")
#first, call the heat exchanger function so can know if need heating or cooling and where in order to maintain boundary conditions imposed.
RecuperatorTrial=RealRecuperator(
LowPressureInletTemperature = Recuperator['LowPressure']['StartingProperties']['Temperature'],
LowPressureInletPressure = Recuperator['LowPressure']['StartingProperties']['Pressure'],
ActualLowPressureOutletPressure = Recuperator['LowPressure']['ActualRecuperatedProperties']['Pressure'],
LowPressureMassFraction = RecuperatorInputParameters['LowPressure']['MassFraction'],
HighPressureInletTemperature = Recuperator['HighPressure']['StartingProperties']['Temperature'],
HighPressureInletPressure = Recuperator['HighPressure']['StartingProperties']['Pressure'],
ActualHighPressureOutletPressure = Recuperator['HighPressure']['ActualRecuperatedProperties']['Pressure'],
HighPressureMassFraction = RecuperatorInputParameters['HighPressure']['MassFraction'],
ActualHighPressures = Recuperator['HighPressure']['ActualPressures'],
MinimumDeltaT = RecuperatorInputParameters['MinimumDeltaT'],
)
#now check to see if some heating or cooling needs actually needs to happen to happen and rerun the heat exchanger function if necessary
#note, cases currently implemented have only heating on the high pressure side and only cooling on the low pressure side. case with 4 constraints may not be this way?
if ('Temperature' not in Recuperator['LowPressure']['RecuperatedProperties']) and ('Temperature' not in Recuperator['HighPressure']['RecuperatedProperties']):
#not really anything to do. original outputs obtained above are correct, so the dictionary can be set the the correct variable name
Recuperator=RecuperatorTrial
elif ('Temperature' in Recuperator['LowPressure']['RecuperatedProperties']) and ('Temperature' in Recuperator['HighPressure']['RecuperatedProperties']):
#case where all 4 boundaries are constrained and there can be heating or cooling on the high and low pressure side.
raise Exception("4 boundary conditions not yet implemented in this function (but should be setup in the RealRecuperator function, so it can be done)")
elif ('Temperature' in Recuperator['HighPressure']['RecuperatedProperties']):
#don't think the following two cases work with constant volume
if (
(RecuperatorTrial['HighPressure']['RecuperatedProperties']['Temperature']<Recuperator['HighPressure']['RecuperatedProperties']['Temperature']) and
not CheckPercentage(RecuperatorTrial['HighPressure']['RecuperatedProperties']['Temperature'],Recuperator['HighPressure']['RecuperatedProperties']['Temperature'],TemperaturePercentError) #CheckPercentage is needed because of small errors
):
PrintWarning("need to HEAT up the HIGH pressure side at the EXIT.")
#set the correct recuperated properties (they are initially the same as the ACTUAL values)
RecuperatorTrial['HighPressure']['RecuperatedProperties']=GetFluidProperties(
Temperature = Recuperator['HighPressure']['RecuperatedProperties']['Temperature'],
Pressure = Recuperator['HighPressure']['StartingProperties']['Pressure']
)
#once the correct recuperated properties has been set, the dictionary can be set the the correct variable name now
Recuperator=RecuperatorTrial
#recalculate the heat added at the high temperature end (defaults to 0)
Recuperator['HighPressure']['RecuperatedProperties']['HeatAdded_TotalMassFlow']=(
(
Recuperator['HighPressure']['RecuperatedProperties']['Enthalpy']
-Recuperator['HighPressure']['ActualRecuperatedProperties']['Enthalpy']
)*RecuperatorInputParameters['HighPressure']['MassFraction']
)
elif (
(RecuperatorTrial['HighPressure']['RecuperatedProperties']['Temperature']>Recuperator['HighPressure']['RecuperatedProperties']['Temperature']) and
not CheckPercentage(RecuperatorTrial['HighPressure']['RecuperatedProperties']['Temperature'],Recuperator['HighPressure']['RecuperatedProperties']['Temperature'],TemperaturePercentError) #CheckPercentage is needed because of small errors
):
PrintWarning("need to rerun with a third boundary condition set on the heat exchanger")
#see some additional notes on the accuracy of the heat exchanger function, in the heat exchanger function that may impact this section
PrintWarning("LOW pressure side will be COOLED at the INLET")
Recuperator['HighPressure']['ActualRecuperatedProperties']['Temperature']=Recuperator['HighPressure']['RecuperatedProperties']['Temperature']
Recuperator=RealRecuperator(
ActualHighPressureOutletTemperature = Recuperator['HighPressure']['ActualRecuperatedProperties']['Temperature'],
LowPressureInletTemperature = Recuperator['LowPressure']['StartingProperties']['Temperature'],
LowPressureInletPressure = Recuperator['LowPressure']['StartingProperties']['Pressure'],
ActualLowPressureInletPressure = Recuperator['LowPressure']['ActualStartingProperties']['Pressure'],
ActualLowPressureOutletPressure = Recuperator['LowPressure']['ActualRecuperatedProperties']['Pressure'],
LowPressureMassFraction = RecuperatorInputParameters['LowPressure']['MassFraction'],
HighPressureInletTemperature = Recuperator['HighPressure']['StartingProperties']['Temperature'],
HighPressureInletPressure = Recuperator['HighPressure']['StartingProperties']['Pressure'],
ActualHighPressureOutletPressure = Recuperator['HighPressure']['ActualRecuperatedProperties']['Pressure'],
HighPressureMassFraction = RecuperatorInputParameters['HighPressure']['MassFraction'],
ActualHighPressures = Recuperator['HighPressure']['ActualPressures'],
MinimumDeltaT = RecuperatorInputParameters['MinimumDeltaT'],
)
else: #pretty rare that this case that actually did exist!
#although high pressure recuperated temperature was defined, it was exactly the same as if no heating or cooling were performed
#actually, this seems to be possible for some reason. the CheckPercentage function test is needed to correctly trigger it though.
Recuperator=RecuperatorTrial
elif ('Temperature' in Recuperator['LowPressure']['RecuperatedProperties']):
#don't think the following two cases work with constant volume
if (
(RecuperatorTrial['LowPressure']['RecuperatedProperties']['Temperature']>Recuperator['LowPressure']['RecuperatedProperties']['Temperature']) and
not CheckPercentage(RecuperatorTrial['LowPressure']['RecuperatedProperties']['Temperature'],Recuperator['LowPressure']['RecuperatedProperties']['Temperature'],TemperaturePercentError) #CheckPercentage is needed because of small errors
):
PrintWarning("need to COOL LOW pressure side down more at the EXIT")
#set the correct recuperated properties (they are initially the same as the ACTUAL values)
RecuperatorTrial['LowPressure']['RecuperatedProperties']=GetFluidProperties(
Temperature = Recuperator['LowPressure']['RecuperatedProperties']['Temperature'],
Pressure = Recuperator['LowPressure']['StartingProperties']['Pressure']
)
#once the correct recuperated properties has been set, the dictionary can be set the the correct variable name now
Recuperator=RecuperatorTrial
#recalculate the heat rejected at the low temperature end (defaults to 0)
Recuperator['LowPressure']['RecuperatedProperties']['HeatRejected_TotalMassFlow']=(
(Recuperator['LowPressure']['ActualRecuperatedProperties']['Enthalpy']
-Recuperator['LowPressure']['RecuperatedProperties']['Enthalpy']
)*RecuperatorInputParameters['LowPressure']['MassFraction']
)
elif (
(RecuperatorTrial['LowPressure']['RecuperatedProperties']['Temperature']<Recuperator['LowPressure']['RecuperatedProperties']['Temperature']) and
not CheckPercentage(RecuperatorTrial['LowPressure']['RecuperatedProperties']['Temperature'],Recuperator['LowPressure']['RecuperatedProperties']['Temperature'],TemperaturePercentError) #CheckPercentage is needed because of small errors
):
PrintWarning("need to rerun with a third boundary condition set on the heat exchanger")
PrintWarning("HIGH pressure side will be HEATED at the INLET")
Recuperator['LowPressure']['ActualRecuperatedProperties']['Temperature']=Recuperator['LowPressure']['RecuperatedProperties']['Temperature']
Recuperator=RealRecuperator(
ActualLowPressureOutletTemperature = Recuperator['LowPressure']['ActualRecuperatedProperties']['Temperature'],
LowPressureInletTemperature = Recuperator['LowPressure']['StartingProperties']['Temperature'],
LowPressureInletPressure = Recuperator['LowPressure']['StartingProperties']['Pressure'],
ActualLowPressureOutletPressure = Recuperator['LowPressure']['ActualRecuperatedProperties']['Pressure'],
LowPressureMassFraction = RecuperatorInputParameters['LowPressure']['MassFraction'],
HighPressureInletTemperature = Recuperator['HighPressure']['StartingProperties']['Temperature'],
HighPressureInletPressure = Recuperator['HighPressure']['StartingProperties']['Pressure'],
ActualHighPressureInletPressure = Recuperator['HighPressure']['ActualStartingProperties']['Pressure'],
ActualHighPressureOutletPressure = Recuperator['HighPressure']['ActualRecuperatedProperties']['Pressure'],
HighPressureMassFraction = RecuperatorInputParameters['HighPressure']['MassFraction'],
ActualHighPressures = Recuperator['HighPressure']['ActualPressures'],
MinimumDeltaT = RecuperatorInputParameters['MinimumDeltaT'],
)
else: #pretty rare that this case that actually did exist!
#although low pressure recuperated temperature was defined, it was exactly the same as if no heating or cooling were performed
#actually, this seems to be possible for some reason. the CheckPercentage function test is needed to correctly trigger it though.
Recuperator=RecuperatorTrial
else: #don't think else is even possible given the above inputs, but just check to be safe
raise Exception("unknown permutation of inputs given")
#########
#more notes and todo
#########
#how to add the heating and cooling to the ts diagram states numbers? .... probably want to add an extra number for each possible and then make it appear or dissappear?
# - do this in the main cycle because need to hard code state numbers
#make every heat exchanger always have these heaters or coolers and they will just be set to zero if not used?
#can the existing (meant original???) heater function be used or modified for this?
#it is possible to just specify the temperatures on one side (needed for the future when calculating the temperatures in the coolant and heater flows)
#but will need to at least specify a minimum temperature on the side that is
#receiving the heat (in the case of recuperators, the high pressure side, in the case of coolers or heates, the much lower pressure side)
#or in the case where the temperatures are known on only the side receiving the heat, the other side will need to know the maximum temperature
#so actually, the answer is really no. in the case where the max or min has to be defined, it is the same as the case where the outlet temperatures
#are defined, except you can just ignore the heat added or subtracted by the heater or cooler and take the heater or cooler outlet temperatures as
#the answer for the single sided cases, and then the RealRecuperator function can just be kept the same
#???????
#there will be some cases where the boundary condition can't be met based on the given max or min and in those cases an error will have to be given
#case where the only the outlets are defined is similiar in that it needs a max and min for both the inlets defined, so it pretty much makes sense to also use this same
#approach in order to keep generality
#actually, making the RealRecuperator function accept 3 inputs, but still need to use this function in order to make sure that the case that is sent to the RealRecuperator function
#is always one in which heat has to be cooled on the low pressure inlet side.
#doing it the other way wasn't going to work because cps aren't constant so think calculating the temperature for the required enthalpy change on the low pressure side may not have worked
#????????????
#############
#if debugging, uncomment the following line to see the result of the recuperator
# PrintKeysAndValues("Recuperator",Recuperator)
return Recuperator
#iterate the RealRecuperatorIteration function until the residual in SpecificHeatRatios is low
#actually though, looks like this function is not needed. based running with several iterations, the solution does not work out if SpecificHeatRatios is updated from the guess
#also, when just one iteration is run and swept through different values, the solution process appears to be stable accross the sweep, so the SpecificHeatRatios apparently should
#be based on the extreme temperatures and not the actual temperatures. still not positive this works for every situation though so leaving this iteration process in here
#just in case. if it is later proven that it is definitely not needed and/or a commit has been made to save this iteration process, the code can be simplified to not include iterations.
def RealRecuperator(
LowPressureInletTemperature = None,
ActualLowPressureOutletTemperature = None,
LowPressureInletPressure = None,
ActualLowPressureInletPressure = None,
ActualLowPressureOutletPressure = None,
LowPressureMassFraction = None,
HighPressureInletTemperature = None,
ActualHighPressureOutletTemperature = None,
HighPressureInletPressure = None,
ActualHighPressureInletPressure = None,
ActualHighPressureOutletPressure = None,
HighPressureMassFraction = None,
SpecificHeatRatios = None,
ActualHighPressures = None,
MinimumDeltaT = 0,
): #=None set for all because of "non-default argument follows default argument" error. maybe could have reordered, but didn't want to mess anything up.
SpecificHeatRatios=None #for the first iteration, the SpecificHeatRatio is not known
for iteration in arange(0,1): #do at most xxx iterations
PrintWarning("RealRecuperatorIteration: "+str(iteration))
Recuperator=RealRecuperatorIteration(
LowPressureInletTemperature,
ActualLowPressureOutletTemperature,
LowPressureInletPressure,
ActualLowPressureInletPressure,
ActualLowPressureOutletPressure,
LowPressureMassFraction,
HighPressureInletTemperature,
ActualHighPressureOutletTemperature,
HighPressureInletPressure,
ActualHighPressureInletPressure,
ActualHighPressureOutletPressure,
HighPressureMassFraction,
SpecificHeatRatios,
ActualHighPressures,
MinimumDeltaT,
)
if Recuperator['SpecificHeatRatiosMaxResidual']<1.0: #if the maximum residual is less than 1% then it is good enough
break
else: #if the maximum residual is greater than 1% then pass the latest value of SpecificHeatRatios in as an initial guess
SpecificHeatRatios=Recuperator['SpecificHeatRatios']
return Recuperator
def SolveRecuperator(Guess, ActualLowPressureInletTemperature, ActualLowPressureOutletTemperature, ActualLowPressures, ActualHighPressureInletTemperature, ActualHighPressureOutletTemperature, ActualHighPressures, LowPressureMassFraction, HighPressureMassFraction, HighPressureEnergy, MinimumDeltaT=0, StartEnd='LowTemperature', AssignedSide='LowPressure'):
#note, need to pass the ActualLowPressureOutletTemperature instead of ActualLowPressureOutlet because the optimizer can't work with a dictionary
#calculate the temperatures within the heat exchanger based on a guessed Low Pressure Outlet Temperature, which is guessed based on the heuristics above that are based on comparing specific heats on the high and low pressure sides.
PrintWarning("Starting Heat Exchanger Root Finding Iteration")
#figure out what is the guessed value
Mode='CheckGuess' #set the mode, but it may be overridden if no variable to Guess is set
if ActualLowPressureInletTemperature is 'Guess':
ActualLowPressureInletTemperature=Guess
elif ActualLowPressureOutletTemperature is 'Guess':
ActualLowPressureOutletTemperature=Guess
elif ActualHighPressureInletTemperature is 'Guess':
ActualHighPressureInletTemperature=Guess
else:
if Guess is not None:
raise Exception('improper input combination')
Mode='Calculate'
#note, there is also another variable that is set to 'Solve'. although that value is ignored and the only reason to check for it would be to make sure the inputs are not overconstrained
#actually, maybe could use this variable to eliminate AssignedSide variable, but it will require more logic, so just skipping that for now
#figure out which end to start solving from
if StartEnd is 'LowTemperature':
PrintWarning('start end is low temperature end')
StartIndex=0
CountDirection=1
if AssignedSide is 'LowPressure':
PrintWarning('assigned side is low pressure side')
StartTemperature=ActualHighPressureInletTemperature
elif AssignedSide is 'HighPressure':
PrintWarning('assigned side is high pressure side')
StartTemperature=ActualLowPressureOutletTemperature
elif StartEnd is 'HighTemperature':
PrintWarning('start end is high temperature end')
StartIndex=-1
CountDirection=-1
if AssignedSide is 'LowPressure':
PrintWarning('assigned side is low pressure side')
StartTemperature=ActualHighPressureOutletTemperature
elif AssignedSide is 'HighPressure':
PrintWarning('assigned side is high pressure side')
StartTemperature=ActualLowPressureInletTemperature
else:
raise Exception('improper input combination')
#first ASSIGN temperatures
NumberofTemperatures=len(ActualLowPressures) #calculate this so don't need to pass another parameter
#figure out which side the temperatures are assigned and which side they are calculated
if AssignedSide is 'LowPressure':
ActualLowPressureTemperatures=linspace(ActualLowPressureOutletTemperature,ActualLowPressureInletTemperature,NumberofTemperatures)
#calculate the enthalpies at all of those tempeatures and pressures
ActualLowPressureEnthalpies=EnthalpyFromTemperaturePressure(ActualLowPressureTemperatures,ActualLowPressures)
#preallocate the array of unknown values
ActualHighPressureEnergies=zeros_like(ActualLowPressureEnthalpies)
#now solve for the high pressure side. can this be made more general so it isn't mostly copied and pasted below???
ActualHighPressureEnergies[StartIndex]=GetFluidProperties(Temperature=StartTemperature,Pressure=ActualHighPressures[StartIndex])[HighPressureEnergy] #assign a known value
#then calculate unknown energy on the high pressure side that correspond to the temperatures assigned on the low pressure side
for node in arange(0,ActualLowPressureEnthalpies.size)[::CountDirection][:-1]:
ActualHighPressureEnergies[node+CountDirection]=ActualHighPressureEnergies[node]+(LowPressureMassFraction/HighPressureMassFraction)*(ActualLowPressureEnthalpies[node+CountDirection]-ActualLowPressureEnthalpies[node])
#now that the energies are known, calcuate the temperatures
if HighPressureEnergy=='InternalEnergy':
#there is constant volume heat addition so use internal energy
ActualHighPressureTemperatures=TemperatureFromInternalEnergyPressure(ActualHighPressureEnergies,ActualHighPressures)
else:
#normal heat exchanger with a nearly constant pressure
ActualHighPressureTemperatures=TemperatureFromEnthalpyPressure(ActualHighPressureEnergies,ActualHighPressures)
elif AssignedSide is 'HighPressure':
ActualHighPressureTemperatures=linspace(ActualHighPressureInletTemperature,ActualHighPressureOutletTemperature,NumberofTemperatures)
#calculate the energies at all of those tempeatures and pressures
if HighPressureEnergy=='InternalEnergy':
#there is constant volume heat addition so use internal energy
ActualHighPressureEnergies=InternalEnergyFromTemperaturePressure(ActualHighPressureTemperatures,ActualHighPressures)
else:
#normal heat exchanger with a nearly constant pressure
ActualHighPressureEnergies=EnthalpyFromTemperaturePressure(ActualHighPressureTemperatures,ActualHighPressures)
#preallocate the array of unknown values
ActualLowPressureEnthalpies=zeros_like(ActualHighPressureEnergies)
#now solve for the low pressure side
ActualLowPressureEnthalpies[StartIndex]=GetFluidProperties(Temperature=StartTemperature,Pressure=ActualLowPressures[StartIndex])[HighPressureEnergy] #assign a known value
#then calculate unknown energy on the low pressure side that correspond to the temperatures assigned on the high pressure side
for node in arange(0,ActualHighPressureEnergies.size)[::CountDirection][:-1]:
ActualLowPressureEnthalpies[node+CountDirection]=ActualLowPressureEnthalpies[node]+(HighPressureMassFraction/LowPressureMassFraction)*(ActualHighPressureEnergies[node+CountDirection]-ActualHighPressureEnergies[node])
#now that the enthalpies are known, calcuate the temperatures
ActualLowPressureTemperatures=TemperatureFromEnthalpyPressure(ActualLowPressureEnthalpies,ActualLowPressures)
else:
raise Exception('improper input combination')
#calculate the delta t everywhere now that the temperatures are known
ActualDeltaTs=ActualLowPressureTemperatures-ActualHighPressureTemperatures
if Mode=='CheckGuess':
PrintWarning(ActualDeltaTs.min())
return ActualDeltaTs.min()-MinimumDeltaT
elif Mode=='Calculate':
#determine the SpecificHeats and outlet properties for the high pressure side now that the temperatures are known on the high pressure side
if HighPressureEnergy=='InternalEnergy':
#there is constant volume heat addition and want to return cv instead of cp for the high pressure side
ActualHighPressureSpecificHeats=cvFromTemperaturePressure(ActualHighPressureTemperatures,ActualHighPressures)
#calculate all values at the inlet and outlet
ActualHighPressureInlet=GetFluidProperties(ActualHighPressures[0],InternalEnergy=ActualHighPressureEnergies[0])
ActualHighPressureOutlet=GetFluidProperties(ActualHighPressures[-1],InternalEnergy=ActualHighPressureEnergies[-1])
else:
#normal heat exchanger with a nearly constant pressure
ActualHighPressureSpecificHeats=cpFromTemperaturePressure(ActualHighPressureTemperatures,ActualHighPressures)
#calculate all values at the inlet and outlet
ActualHighPressureInlet=GetFluidProperties(ActualHighPressures[0],Enthalpy=ActualHighPressureEnergies[0])
ActualHighPressureOutlet=GetFluidProperties(ActualHighPressures[-1],Enthalpy=ActualHighPressureEnergies[-1])
#calculate the SpecificHeats on the low pressure side based on the assigned temperatures on the low pressure side. (could have been done above, but doing here to consolidate SpecificHeat calculations)
ActualLowPressureSpecificHeats=cpFromTemperaturePressure(ActualLowPressureTemperatures,ActualLowPressures) #note, at the beginning of this function, just one temperature was used for both the high and low pressure side, now a specific temperature is used
#calculate all values at the low pressure inlet and outlet
ActualLowPressureInlet=GetFluidProperties(ActualLowPressures[-1],Enthalpy=ActualLowPressureEnthalpies[-1])
ActualLowPressureOutlet=GetFluidProperties(ActualLowPressures[0],Enthalpy=ActualLowPressureEnthalpies[0])
ActualSpecificHeatRatios=(ActualHighPressureSpecificHeats*HighPressureMassFraction)/(LowPressureMassFraction*ActualLowPressureSpecificHeats) #need to take the ratio of the specific heats multiplied by their mass fractions because that determines the relative specific heat ratio when there are different mass flow rates
#do some checks
PrintWarning('minimum DeltaT: '+str(ActualDeltaTs.min()))
if isnan(ActualDeltaTs.min()):
print ""
PrintKeysAndValues("Recuperator",Recuperator)
print ""
raise Exception("there is some problem with the heat exchanger logic")
#double check the additional knowns are the same
#why would this be an issue other than interpolation error stackup???????
ActualHighPressureEnergyError=(ActualHighPressureEnergies[-1]-ActualHighPressureOutlet[HighPressureEnergy])/ActualHighPressureOutlet[HighPressureEnergy]
PrintWarning('high pressure recuperated energy error='+str(ActualHighPressureEnergyError))
if isnan(ActualHighPressureEnergyError): #not sure why this isn't checked in more detail other than other checks will probably also fail if this fails
print ""
PrintKeysAndValues("Recuperator",Recuperator)
print ""
raise Exception("there is some problem with the heat exchanger logic")
#return the current solution
return ActualLowPressureTemperatures,ActualHighPressureTemperatures,ActualDeltaTs,ActualHighPressureSpecificHeats,ActualLowPressureSpecificHeats,ActualSpecificHeatRatios,ActualLowPressureInlet,ActualLowPressureOutlet,ActualHighPressureInlet,ActualHighPressureOutlet
else:
raise Exception("don't understand Mode")
def RealRecuperatorIteration(
LowPressureInletTemperature = None,
ActualLowPressureOutletTemperature = None,
LowPressureInletPressure = None,
ActualLowPressureInletPressure = None, #actual value has to be set because it can't be calculated
ActualLowPressureOutletPressure = None,
LowPressureMassFraction = None,
HighPressureInletTemperature = None,
ActualHighPressureOutletTemperature = None,
HighPressureInletPressure = None,
ActualHighPressureInletPressure = None, #actual value has to be set because it can't be calculated
ActualHighPressureOutletPressure = None,
HighPressureMassFraction = None,
SpecificHeatRatios = None,
ActualHighPressures = None,
MinimumDeltaT = 0,
): #=None set for all because of "non-default argument follows default argument" error. maybe could have reordered, but didn't want to mess anything up.
#################################
#about this function
#################################
#this function takes heat exchanger high and low side pressures, and inlet temperatures, and calculates the outlet temperatures.
#if outlet temperatures are defined, then the inlet temperatures (on the same pressure as the outlet temperature is defined) are taken to be limits (and
#not boundaries, so this function does not allow for overconstraining the boundaries) and new inlet temperature(s) is(are) computed (the ACTUAL values)
#(and then the amount of heating or cooling required at the inlets is calculated between the limit and the ACTUAL value????)
#note, before calling with outlet temperatures defined, need to verify that the outlet temperatures given will work within the inlet temperature limits given
#note, the energy equation is solved only (never allowing an entropy reduction of course), and perfect convective and conductive heat transfer is assumed
#within the heat exchanger.
#################################
#this function accepts values as inputs, but returns a dictionary. it doesn't accept a dictionary as an input because many times a custom dictionary would
#have to be constructed anyway, and because all the expressions below would be very long/confusing to read with a dictionary that would probably want to
#deconstruct the dictionary immediately in this function anyway
#################################
#need an option for different fluids on both sides so can use for heaters and coolers when modeling those too
#change terminology so that high pressure side just means the side receiving the heat and the low pressure side just means the side rejecting the heat.
#this is needed because coolers will pretty much always be lower pressure.
#keep in mind that the resolution of the data being interpolated is the real upper limit on the usefulness of this value
NumberofTemperatures=len(ActualHighPressures)
#define a tolerance for errors in the enthalies interpolated
EnergyTolerance=.15 #%
#set these variable so they can be used for more consistent comparisons. or are the comparisons consitent enough already??
LowPressureOutletTemperature=ActualLowPressureOutletTemperature
HighPressureOutletTemperature=ActualHighPressureOutletTemperature
#in case this function is not called by GeneralRealRecuperator, also
#check for LowPressureInletTemperature and HighPressureInletTemperature being defined. can't run without these, even if they are just acting as a maximum or minimum.
if (LowPressureInletTemperature is None) or (HighPressureInletTemperature is None):
raise Exception("LowPressureInletTemperature or HighPressureInletTemperature not given and required for at least setting limits")
#make sure inlet temperatures given make sense
elif LowPressureInletTemperature<HighPressureInletTemperature:
raise Exception("LowPressureInletTemperature<HighPressureInletTemperature")
elif (LowPressureInletTemperature<HighPressureOutletTemperature) and (HighPressureOutletTemperature is not None):
raise Exception("LowPressureInletTemperature<HighPressureOutletTemperature")
elif (HighPressureInletTemperature>LowPressureOutletTemperature) and (LowPressureOutletTemperature is not None):
raise Exception("HighPressureInletTemperature>LowPressureOutletTemperature")
#check for Actual Pressures
if ((ActualLowPressureInletPressure == {}) or (ActualLowPressureInletPressure is None)): #don't know why "is" doesn't work instead of "==" ?
ActualLowPressureInletPressure=LowPressureInletPressure
if (HighPressureOutletTemperature is not None):
PrintWarning("HighPressureOutletTemperature is defined but ActualLowPressureInletPressure is not defined, so setting ActualLowPressureInletPressure=LowPressureInletPressure as an approximation")
#if HighPressureOutletTemperature is None, don't want to do a warning because setting ActualLowPressureInletPressure=LowPressureInletPressure is just done so ActualPressures can be defined more neatly below
elif (LowPressureOutletTemperature is not None):
raise Exception("ActualLowPressureInletPressure defined and LowPressureOutletTemperature defined. there may be a problem with the input combination used")
if ((ActualHighPressureInletPressure == {}) or (ActualHighPressureInletPressure is None)): #don't know why "is" doesn't work instead of "==" ?
ActualHighPressureInletPressure=HighPressureInletPressure
if (LowPressureOutletTemperature is not None):
PrintWarning("LowPressureOutletTemperature is defined but ActualHighPressureInletPressure is not defined, so setting ActualHighPressureInletPressure=HighPressureInletPressure as an approximation")
#if LowPressureOutletTemperature is None, don't want to do a warning because setting ActualHighPressureInletPressure=HighPressureInletPressure is just done so ActualPressures can be defined more neatly below
elif (HighPressureOutletTemperature is not None):
raise Exception("ActualHighPressureInletPressure defined and HighPressureOutletTemperature defined. there may be a problem with the input combination used")
#define the dictionary that is populated by this function
Recuperator=SmartDictionary()
LowPressureInlet=GetFluidProperties(LowPressureInletPressure,LowPressureInletTemperature)
LowPressures=linspace(ActualLowPressureOutletPressure,LowPressureInletPressure,NumberofTemperatures) #note, LowPressureOutlet=ActualLowPressureOutlet below, so using ActualLowPressureOutletPressure is correct
ActualLowPressures=linspace(ActualLowPressureOutletPressure,ActualLowPressureInletPressure,NumberofTemperatures)
LowPressureOutletMin=GetFluidProperties(ActualLowPressureOutletPressure,HighPressureInletTemperature) #same note as above
Temperatures=linspace(LowPressureOutletMin['Temperature'],LowPressureInlet['Temperature'],NumberofTemperatures)
if ActualHighPressureOutletPressure>HighPressureInletPressure:
#there is constant volume heat addition and the pressure rises instead of drops in the heat exchanger high pressure side.
#if this happens then there is a pressure drop that should be considered before and after the heat exchange (although it actually is not modeled yet).
#this issue is also mentioned in the constant volume heater function
HighPressureEnergy='InternalEnergy'
#need to calculate the pressure as a function of temperature and the fixed density
HighPressures=PressureFromTemperatureDensity(Temperatures,GetFluidProperties(Temperature=HighPressureInletTemperature,Pressure=HighPressureInletPressure,ExtendedProperties=True)['Density']) #every property for ActualHighPressureOutlet isn't know yet because temperature may not be known, but the density is and is the same as the inlet, so just fetch it from the inlet state.
#set in the result so can reference elsewhere in CycleParameters in results processing to distinquish from constant pressure cases.
Recuperator['HighPressure']['ConstantVolume']=True
else:
#normal heat exchanger with a pressure drop that occurs during the heat exchange
HighPressureEnergy='Enthalpy'
HighPressures=linspace(HighPressureInletPressure,ActualHighPressureOutletPressure,NumberofTemperatures) #note, HighPressureOutlet=ActualHighPressureOutlet below, so using ActualHighPressureOutletPressure is correct
#set in the result so can reference elsewhere in CycleParameters in results processing to distinquish from constant volume cases.
Recuperator['HighPressure']['ConstantVolume']=False
HighPressureInlet=GetFluidProperties(HighPressureInletPressure,HighPressureInletTemperature)
HighPressureOutletMax=GetFluidProperties(ActualHighPressureOutletPressure,LowPressureInletTemperature) #same note as above
#if the first iteration then make a guess for the specific heat ratios
#note, this logic proves to not do anything positive at present but just leaving it in here in case it is useful in the future for some reason
#it was assumed that the proper SpecificHeatRatio was based on the actual SpecificHeatRatio of the control volume (different temperatures) and not the SpecificHeatRatio at
#the same temperature on the high and low pressure sides. there still could be some truth to this (which could be why the trial and error fallback is needed)
#and what was done below could have not been implemented all the way correctly. the cases the logic does work using common temperatures could have something to do
#with the fact that the for a lot of the cases when SpecificHeatRatio is near 1 the temperatures are a lot of times similiar or equal
#this issue is most significant for large delta T at the outlet/inlet?
#another case of concern is where the pinching occurs near the exit, but not at the exit.
if SpecificHeatRatios is None:
#print "guessing SpecificHeatRatios"
if HighPressureEnergy=='InternalEnergy':
#there is constant volume heat addition and want to return cv instead of cp for the high pressure side
HighPressureSpecificHeats=cvFromTemperaturePressure(Temperatures,HighPressures)
else:
#normal heat exchanger with a nearly constant pressure
HighPressureSpecificHeats=cpFromTemperaturePressure(Temperatures,HighPressures)
LowPressureSpecificHeats=cpFromTemperaturePressure(Temperatures,LowPressures)
SpecificHeatRatios=(HighPressureSpecificHeats*HighPressureMassFraction)/(LowPressureMassFraction*LowPressureSpecificHeats) #need to take the ratio of the specific heats multiplied by their mass fractions because that determines the relative specific heat ratio when there are different mass flow rates
SpecificHeatRatiosOriginal=SpecificHeatRatios.copy()
OneCrossings=abs(diff(1*(SpecificHeatRatios>1))) #locate where the specific heat ratio crosses 1
NumberOfOneCrossings=sum(OneCrossings) #count the number of times the specific heat ratio crosses 1
#if more than one specific heat ratio crossing of 1, find the index of the specific heat ratio crossing closest 1 at the highest temperature
#or lowest temperature, based on a guess of which one it should be based on inspecting the concavity of SpecificHeatRatios
#note, originally had index=argmin(abs(SpecificHeatRatios-1)) but it assumes there is only one ones crossing
if NumberOfOneCrossings>0:
HighestTemperatureOneCrossingIndex=OneCrossings.nonzero()[0].max()
LowestTemperatureOneCrossingIndex=OneCrossings.nonzero()[0].min()
SpecificHeatRatiosDeviationFromOne=abs(SpecificHeatRatios-1)
Previousindex=None
SpecificHeatRatiosSecondDerivative=gradient(gradient(SpecificHeatRatios))
if SpecificHeatRatiosSecondDerivative.mean()>=0: #can't use all(SpecificHeatRatiosSecondDerivative>=0) because too noisy data so not all concavities come out right, and there might be a small region with a change in concavity
#concave up
index=HighestTemperatureOneCrossingIndex+SpecificHeatRatiosDeviationFromOne[[HighestTemperatureOneCrossingIndex,HighestTemperatureOneCrossingIndex+1]].argmin()
elif SpecificHeatRatiosSecondDerivative.mean()<=0: #can't use all(SpecificHeatRatiosSecondDerivative<=0) because too noisy data so not all concavities come out right, and there might be a small region with a change in concavity
#concave down
index=LowestTemperatureOneCrossingIndex+SpecificHeatRatiosDeviationFromOne[[LowestTemperatureOneCrossingIndex,LowestTemperatureOneCrossingIndex+1]].argmin()
else:
raise Exception("don't don't understand the curvature of SpecificHeatRatios")
else:
Previousindex=0 #don't know that this is actually needed, but added it in here. the case where it did cause a problem ended up being another problem that caused the code to get somewhere it shouldn't have even been.
index=0 #there are no ones crossings so set to 0 so that there is some value
###############################################################################################################################
#first go through all the assumed conditions and behaviour. if the condition is understood indicate how calculate unknowns (which will be done later)
#note, this is done instead of a general (and slower?) discretization and solving the nonlinear function technique that could have been used.
#there could have been a lot of stability issues with the general nonlinear function solving technique due to the property variations and accuracy of the property data
#and not sure if could have done all the combinations of boundary conditions that way?
CalculationType=None
if NumberOfOneCrossings>1:
PrintWarning("warning: SpecificHeatRatio crosses one more than once")
pass
#check to see if the ratio of specific heats changes within the heat exchanger from less than 1 to greater than 1 or vice versa
if index!=0 and index!=SpecificHeatRatios.shape[0] and SpecificHeatRatios.max()>1 and SpecificHeatRatios.min()<1: #not sure if there are any cases where triggers the wrong case and causes it to go into trial and error mode
if gradient(SpecificHeatRatios).max()>0 and gradient(SpecificHeatRatios).min()<0: #skipping the deltaT part of this condition because just checking sign so it doesn't matter
#can't just search for a zero because the discretization may miss it, so check if it crosses zero
PrintWarning("note, SpecificHeatRatios slope changes sign.")
pass
if gradient(SpecificHeatRatios).mean()<0:
#skipping the deltaT part of this condition because just checking sign so it doesn't matter. note, used to have gradient(SpecificHeatRatios).max()<0,
#but that didn't seem to work for a lot of cases (error in the interpolation(?) and causing a(n artificial) or real local minimum, for example)
#case where all SpecificHeatRatios are always decreasing
PrintWarning("SpecificHeat ratio decreasing")
if SpecificHeatRatios.mean()>1:
#assuming same as SpecificHeatRatios always greater than 1 case.
#if not, a trial and error fallback method will result
CalculationType='LowTemperaturePinch'
elif SpecificHeatRatios.mean()<1:
if SpecificHeatRatiosSecondDerivative.mean()>=0 and index>argmin(SpecificHeatRatios): #see above concavity check for why all(SpecificHeatRatiosSecondDerivative>=0) is not used
#if the average slope is decreasing and the SpecificHeatRatios average is less than 1 and the concavity is up and the chosen index is greater than the minimum SpecificHeatRatio
CalculationType='MiddlePinch'
else:
#assuming same as SpecificHeatRatios always less than 1 case
#if not, a trial and error fallback method will result
CalculationType='HighTemperaturePinch'
else:
#very rare case (partially due to computer precision) where SpecificHeatRatios.mean() is exactly 1, is always decreasing, and crosses 1
#pinching occurs at both ends, which is a kindof neat phenomenon
CalculationType='HighLowTemperaturePinch'
elif gradient(SpecificHeatRatios).mean()>0:
#skipping the deltaT part of this condition because just checking sign so it doesn't matter. note, used to have gradient(SpecificHeatRatios).min()>0,
#but that didn't seem to work for a lot of cases (error in the interpolation(?) and causing a(n artificial) or real local maximum, for example)
#maybe elif is redundant compared to else?
#case where all SpecificHeatRatios are always increasing
#pinch is in the middle
PrintWarning("SpecificHeat ratio increasing")
CalculationType='MiddlePinch'
elif (SpecificHeatRatios.max()>1 and SpecificHeatRatios.min()>1) or (SpecificHeatRatios.max()>1 and SpecificHeatRatios.max()!=SpecificHeatRatios[index] and (index==0 or index==SpecificHeatRatios.shape[0])):
#SpecificHeat ratio is [almost] always greater than 1. one control volume needed. pinch occurs at high pressure inlet/low pressure outlet (low temperature)
CalculationType='LowTemperaturePinch'
elif (SpecificHeatRatios.max()<1 and SpecificHeatRatios.min()<1) or (SpecificHeatRatios.min()<1 and SpecificHeatRatios.min()!=SpecificHeatRatios[index] and (index==0 or index==SpecificHeatRatios.shape[0])):
#SpecificHeat ratio is [almost] always less than 1. one control volume needed. pinch occurs at low pressure inlet/high pressure outlet (high temperature)
CalculationType='HighTemperaturePinch'
elif NumberOfOneCrossings==0:
#there are no specific heat ratio crossings of 1, specific heat ratios are never always less than 1 or greater than 1.
#this is the case of constant and equal specific heats where the heat exchanger is pinched everywhere
CalculationType='HighLowTemperaturePinch'
else:
#pinch occured between an end point and 1 point away from the end???
raise Exception("don't know why all other cases didn't match. need a finer resolution to resolve?")
###############################################################################################################################
###############################################################################################################################
#now do calculation for cases
#separated here because multiple conditions could require the same calculation.
#cases where only one condition required a certain calculation were still placed here just to keep the calculations consolidated
#may have been able to avoid this step but wanted to keep things separate so different cases are more clearly differentiated, partially for documentation and understanding purposes
#but also in case it turns out they actually aren't common the rework is easier.
#do up to 4 trials in case all the logic above didn't actually identify the correct calculation method
PreviousCalculationType=None
for trial in arange(0,4):
PrintWarning('CalculationType:'+CalculationType)
#set some values, but if they are overridden, then that is okay
ActualLowPressureInlet=LowPressureInlet
ActualHighPressureInlet=HighPressureInlet
if CalculationType is 'LowTemperaturePinch':
PrintWarning("SpecificHeat greater than 1")
#note, cooling at the outlets is handled by the higher level function, so don't care if the outlet is defined here and there isn't any logic to take care of it.
if ActualLowPressureOutletTemperature is not None:
#high pressure side is heated at the inlet
#note, if ActualHighPressureOutletTemperature is not None (below), then there will be both heating and cooling and the configuration will be very inefficient. dont think the calling function will ever constrain both outlets at once though.
#change the ActualHighPressureInlet so that some heating happens
ActualHighPressureInlet=GetFluidProperties(ActualHighPressureInletPressure,ActualLowPressureOutletTemperature-MinimumDeltaT)
#if pinching occurs at the low temperature then the low pressure outlet temperature is the same as the high pressure inlet temperature plus the MinimumDeltaT
#note, not just using ActualLowPressureOutletTemperature here because it may be set to None and don't want to make an else statement above that sets it because some logic below will be messed up.
ActualLowPressureOutlet=GetFluidProperties(ActualLowPressureOutletPressure,ActualHighPressureInlet['Temperature']+MinimumDeltaT)
#check to see if an alternate boundary condition is specified
if ActualHighPressureOutletTemperature is not None: #ActualLowPressureInlet is unknown. there may be some cooling at the low pressure inlet
ActualHighPressureOutlet=GetFluidProperties(ActualHighPressureOutletPressure,ActualHighPressureOutletTemperature) #get rest of the properties if the temperature is known
#check the quality of the interpolated enthalpies or internal energies
if (ActualHighPressureOutlet[HighPressureEnergy]<ActualHighPressureInlet[HighPressureEnergy]):
if CheckPercentage(ActualHighPressureOutlet[HighPressureEnergy],ActualHighPressureInlet[HighPressureEnergy],EnergyTolerance):
#close enough, so just set them to be equal because there was probably supposed to be 0 enthalpy or internal energy based on the inputs
ActualHighPressureOutlet[HighPressureEnergy]=ActualHighPressureInlet[HighPressureEnergy]
PrintWarning("warning, ActualHighPressureOutlet[HighPressureEnergy]<ActualHighPressureInlet[HighPressureEnergy] but within "+str(EnergyTolerance)+"%, setting ActualHighPressureOutlet[HighPressureEnergy]=ActualHighPressureInlet[HighPressureEnergy]")
else:
raise Exception('too much error in interopolated enthalpies or internal energies')
#note, constant volume cooling on the low pressure side is not implemented
ActualLowPressureInletEnthalpy=(ActualHighPressureOutlet[HighPressureEnergy]-ActualHighPressureInlet[HighPressureEnergy])*(HighPressureMassFraction/LowPressureMassFraction)+ActualLowPressureOutlet['Enthalpy'] #calculate the other enthalpy
ActualLowPressureInlet=GetFluidProperties(ActualLowPressureInletPressure,Enthalpy=ActualLowPressureInletEnthalpy) #get rest of the properties once the enthalpy is known