thermofluids 2.0.0__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,1004 @@
1
+ import re
2
+ import sys
3
+ import warnings
4
+
5
+ #import matplotlib.pyplot as #plt
6
+ from thermofluids import Statepoint, Quantity
7
+ from CoolProp.CoolProp import PropsSI
8
+ from thermofluids.Properties.BrineProps import get_C_vap_sat, get_C_liq_sat
9
+ from thermofluids.Properties.VLE_data import VLE
10
+ from numpy import linspace, zeros
11
+
12
+ from thermofluids.HeatTransfer.lmtd import lmtd
13
+
14
+
15
+ class Column():
16
+ """
17
+ This class models distillation processes. There are three performance models:
18
+
19
+ 1. ME_balance - This model just does mass and energy balances to determine the inputs and outputs. Use this model when you need high speed calculations and you don't need any info about column design
20
+ 2. mccabe - This model does tray-wise mass and (approx.) energy balances using the McCabe Thiele model Use it to get more detailed information about the internal performance of the column with minimal computation time.
21
+ 3. mccabe_diabatic - This model does tray-wise mass and (approx.) energy balances using the McCabe Thiele model. The operational line for this model is parallel to the VLE line to model a diabatic process. It solves the model from the top tray downward such that inlet feed and outlet vapor are defined. Use this model as a detailed, functional analysis of a diabatic column.
22
+ 4. mccabe_diabatic_bottomup - This model is the same as mccabe_diabatic except that it attempts to solve it from the bottom up such that inlet feed and outlet liquid are defined. DON'T use this model... this function is currently buggy (go with mccabe_diabatic instead)
23
+ 5. FullDetail - This model does a complete mass energy balance on every tray. Use it when you need the most internal performance detail, require a column design, and have some time to let it run. This function is currently buggy (go with mccabe_diabatic instead)
24
+
25
+ """
26
+ #currently only setup for 0 reflux columns
27
+ def __init__(self, P_col=None, inlet=None, outlet_vap=None, outlet_liq_c=None, fluid=None):
28
+ self.P_col = P_col
29
+ self.inlet = inlet
30
+ self.outlet_vap = outlet_vap
31
+ self.c_outlet_vap = outlet_vap
32
+ self.outlet_liq_c = outlet_liq_c
33
+ self.fluid = fluid
34
+
35
+ def set_P_col(self, P_col):
36
+ self.P_col = P_col
37
+
38
+ def set_inlet(self, inlet):
39
+ self.inlet = inlet
40
+
41
+ def set_outlet_vap(self, outlet_vap):
42
+ self.outlet_vap = outlet_vap
43
+
44
+ def set_outlet_liq_c(self, outlet_liq_c):
45
+ self.outlet_liq_c = outlet_liq_c
46
+
47
+ def set_fluid(self, fluid):
48
+ self.fluid = fluid
49
+
50
+ def set_Q_sens(self, Q_sens):
51
+ self.Q_sens = Q_sens
52
+
53
+ def set_diabatic(self, dia_targ):
54
+ self.dia_targ = dia_targ
55
+
56
+ def ME_balance(self):
57
+ """
58
+ This is the simplest column model. It just does mass and energy balances to determine how much heat is
59
+ required to get the desired top and bottom products from the inlet stream.
60
+
61
+ You can use the following results from this model:
62
+ Column.bottoms
63
+ Column.Q_input
64
+ """
65
+ assert isinstance(self.inlet, Statepoint), '\'inlet\' must be set to a Statepoint'
66
+ assert isinstance(self.outlet_vap, Statepoint), '\'outlet_vap\' must be set to a Statepoint'
67
+
68
+ self.outlet_vap.set_m((self.inlet.m * self.outlet_liq_c - self.inlet.m * self.inlet.c ) / (self.outlet_liq_c - self.outlet_vap.c))
69
+
70
+ self.bottoms = Statepoint(name='bottoms', fluid=self.fluid)
71
+ self.bottoms.set_p(self.P_col)
72
+ self.bottoms.set_x(0.)
73
+ self.bottoms.set_c(self.outlet_liq_c)
74
+ self.bottoms.set_m(self.inlet.m-self.outlet_vap.m)
75
+
76
+ self.Q_input = self.outlet_vap.m*self.outlet_vap.h + self.bottoms.m*self.bottoms.h - self.inlet.m*self.inlet.h
77
+
78
+
79
+ def mccabe(self, plotfile=None):
80
+ """
81
+ This is a graphical way to do an approximate mass and energy balance on each tray
82
+ For more info: https://en.wikipedia.org/wiki/McCabe%E2%80%93Thiele_method
83
+
84
+ You can use the following results from this model:
85
+ Column.n_trays
86
+ Produces a png picture file with the McCabe Theile diagram
87
+ """
88
+
89
+ # do a mass energy balance to get the bottoms
90
+ self.bottoms = Statepoint(name='bottoms', fluid=self.fluid)
91
+ self.bottoms.set_p(self.P_col)
92
+ self.bottoms.set_x(0.)
93
+ self.bottoms.set_c(self.outlet_liq_c)
94
+ self.bottoms.set_m(self.inlet.m-self.outlet_vap.m)
95
+
96
+ # Convert the flow rates to molar
97
+ fluid2, fluid1 = re.sub(r'^REFPROP-MIX:', '', self.fluid).split('&')
98
+ MW1 = PropsSI('M', 'T', 298., 'P', 1.e5, fluid1)
99
+ MW2 = PropsSI('M', 'T', 298., 'P', 1.e5, fluid2)
100
+
101
+ con_mol_inject = self.inlet.c/MW2 / (self.inlet.c/MW2 + (1.-self.inlet.c)/MW1)
102
+ con_mol_dist = self.outlet_vap.c/MW2 / (self.outlet_vap.c/MW2 + (1.-self.outlet_vap.c)/MW1)
103
+ con_mol_bottoms = self.bottoms.c/MW2 / (self.bottoms.c/MW2 + (1.-self.bottoms.c)/MW1)
104
+
105
+ # Solve for trays
106
+ rect_slope = 0.
107
+ operating_inject_y = con_mol_dist
108
+ strip_slope = (con_mol_dist - con_mol_bottoms) / (con_mol_inject - con_mol_bottoms)
109
+ operating_x = [con_mol_bottoms, con_mol_inject, con_mol_dist] #save operating line data for plotting
110
+ operating_y = [con_mol_bottoms, con_mol_dist, con_mol_dist]
111
+ feed_x = [con_mol_inject]*2 #save injection line data for plotting
112
+ feed_y = [0., con_mol_dist]
113
+
114
+ trays_x = [con_mol_bottoms]*2
115
+ trays_y = [con_mol_bottoms]
116
+ under_target = True
117
+ while under_target:
118
+ trays_y.append(VLE(fluid1,fluid2,trays_x[-1]))
119
+ if trays_y[-1]>con_mol_dist:
120
+ under_target=False #this is enough trays to hit target
121
+ trays_x.append(con_mol_dist)
122
+ trays_y.append(trays_y[-1])
123
+ else:
124
+ if trays_y[-1]<operating_inject_y:
125
+ trays_x.append(trays_x[-1]+(trays_y[-1]-trays_y[-2])/strip_slope)
126
+ else:
127
+ trays_x.append(con_mol_inject+(trays_y[-1]-operating_inject_y)/rect_slope)
128
+ trays_x.append(trays_x[-1])
129
+ trays_y.append(trays_y[-1])
130
+ if len(trays_x)>100:
131
+ raise Exception('Too many trays!')
132
+
133
+ vle_range = [i/100. for i in range(100)]
134
+ vle = [VLE(fluid1, fluid2, con) for con in vle_range]
135
+
136
+ self.n_trays = (len(trays_x)-1)/2
137
+
138
+ #convert to mass
139
+ trays_x_mass = trays_x
140
+ trays_y_mass = trays_y
141
+ for i in range(len(trays_x)):
142
+ trays_x_mass[i] = trays_x[i]*MW2/(trays_x[i]*MW2+(1.-trays_x[i])*MW1)
143
+ trays_y_mass[i] = trays_y[i]*MW2/(trays_y[i]*MW2+(1.-trays_y[i])*MW1)
144
+ vle_range_mass = vle_range
145
+ vle_mass=vle
146
+ for i in range(len(vle)):
147
+ vle_range_mass[i] = vle_range[i]*MW2/(vle_range[i]*MW2+(1.-vle_range[i])*MW1)
148
+ vle_mass[i] = vle[i]*MW2/(vle[i]*MW2+(1.-vle[i])*MW1)
149
+ operating_x_mass=operating_x
150
+ operating_y_mass=operating_y
151
+ for i in range(len(operating_x)):
152
+ operating_x_mass[i] = operating_x_mass[i]*MW2/(operating_x_mass[i]*MW2+(1.-operating_x_mass[i])*MW1)
153
+ operating_y_mass[i] = operating_y_mass[i]*MW2/(operating_y_mass[i]*MW2+(1.-operating_y_mass[i])*MW1)
154
+ feed_x_mass=feed_x
155
+ feed_y_mass=feed_y
156
+ for i in range(len(feed_x)):
157
+ feed_x_mass[i] = feed_x[i]*MW2/(feed_x[i]*MW2+(1.-feed_x[i])*MW1)
158
+ feed_y_mass[i] = feed_y[i]*MW2/(feed_y[i]*MW2+(1.-feed_y[i])*MW1)
159
+
160
+ # plot
161
+ if plotfile != None:
162
+ print('')
163
+ #plt.figure() #make mccabe-thiele diagram
164
+ #plt.subplot(1,1,1)
165
+ #plt.plot(list(range(2)),list(range(2))) #y=x line
166
+ ##plt.plot(vle_range,vle) #vapor liquid equilibrium line
167
+ ##plt.plot(trays_x,trays_y) #trays line
168
+ ##plt.plot(operating_x,operating_y) #flowpath lines
169
+ ##plt.plot(feed_x,feed_y)
170
+ #plt.plot(vle_range_mass,vle_mass) #vapor liquid equilibrium line
171
+ #plt.plot(trays_x_mass,trays_y_mass) #trays line
172
+ #plt.plot(operating_x_mass,operating_y_mass) #flowpath lines
173
+ #plt.plot(feed_x_mass,feed_y_mass)
174
+ #plt.xlim(0,1)
175
+ ##plt.xlabel('Liquid conc [mol/mol]')
176
+ #plt.xlabel('Liquid conc [kg/kg]')
177
+ #plt.ylim(0,1)
178
+ ##plt.ylabel('Vapor conc [mol/mol]')
179
+ #plt.ylabel('Vapor conc [kg/kg]')
180
+ #plt.savefig(plotfile)
181
+
182
+
183
+ def get_diabatic_curves(self, shift, plotfile=None, printmode=True):
184
+ """
185
+ This calculates the coordinates for the vapor-liquid equilibrium line and operating line for a given column.
186
+ The operating line is that which minimized exergy destruction. In order to find this operating line, this
187
+ function finds the shift value that will move the VLE line at a 45deg angle southeast such that it satisfies
188
+ the inlet liquid and outlet vapor concentrations. That shift value is used in diabatic_mccabe during the
189
+ McCabe-Thiele calculations.
190
+ (See "Diabatic Column Optimization Compared to Isoforce Columns" by Sauar et al)
191
+
192
+ You can use the following results from this function:
193
+ column.x_sat <-- saturated liquid concentrations for temperature range
194
+ column.y_sat <-- saturated vapor concentrations for temperature range
195
+ column.x_oper <-- operating liquid concentrations for temperature range
196
+ column.y_oper <-- operating vapor concentrations for temperature range
197
+ column.oper_shift <-- value used to make oper. line from VLE line (x_VLE + oper_shift, y_VLE - oper_shift)
198
+
199
+ Written by Fiona Majeau for Rebound Technology July 1 2016
200
+ """
201
+
202
+ # Set up bounds for bisect to find operating line shift
203
+ if shift == None:
204
+ y = self.outlet_vap.c # vapor conc for outlet vap and inlet
205
+ x_min = get_C_liq_sat(self.fluid, y, self.P_col) # sat liq conc for outlet vap (lower bound)
206
+ x_max = self.inlet.c # sat liq conc for inlet (upper bound)
207
+ if x_min > x_max:
208
+ raise Exception('Liquid concentration of outlet vapor (%f) is higher than that of inlet feed (%f). '
209
+ 'Decrease outlet vapor conc or increase inlet liquid feed conc.' % (x_min, x_max))
210
+
211
+ # Set up bisect variables
212
+ x_lower = x_min
213
+ x_upper = x_max
214
+ tol = 0.01
215
+ i = 0
216
+ # print 'Bisecting...'
217
+ while True:
218
+ # print 'bisect ', i
219
+ x_guess = (x_lower + x_upper) / 2 # bisect
220
+ shift = x_max - x_guess # find x shift
221
+ y_guess = y + shift # make guess along y = 1-x line from inlet
222
+ y_sat_guess = get_C_vap_sat(self.fluid, x_guess, self.P_col) # find y_sat for given x_guess
223
+
224
+ # Compare y's from y=1-x and VLE calc, check if they are close enough to be done
225
+ if abs(y_guess - y_sat_guess) < tol:
226
+ break # shift is correct
227
+
228
+ # Reassign upper/lower bound depending on which side of root you are on
229
+ if y_guess < y_sat_guess:
230
+ x_upper = x_guess # move upper bound in
231
+ else:
232
+ x_lower = x_guess # move lower bound in
233
+
234
+ # i = i+1
235
+
236
+ if plotfile != None:
237
+ if printmode==True:
238
+ print('\nGETTING VLE AND OPERATING LINES...')
239
+ # Get saturation line for FPS mixture (for plotting)
240
+ x_sat = linspace(0, 1, 101)
241
+ y_sat = zeros(len(x_sat))
242
+ for i in range(len(x_sat)):
243
+ # print 'i =', i
244
+ y_sat[i] = get_C_vap_sat(self.fluid, x_sat[i], self.P_col)
245
+
246
+ # Create min. exergy dest. operational line (for plotting)
247
+ # NOTE: This is the operational line that gives minimum exergy destruction. It is parallel to the saturation
248
+ # line and is calculated by shifting the saturation line diagonally southeast by the outlet liquid composition.
249
+ x_oper = [z + shift for z in x_sat]
250
+ y_oper = [z - shift for z in y_sat]
251
+
252
+ self.x_sat = x_sat
253
+ self.y_sat = y_sat
254
+ self.x_oper = x_oper
255
+ self.y_oper = y_oper
256
+
257
+ # Plot
258
+ #plt.figure() # make McCabe-Thiele diagram
259
+ #plt.subplot(1, 1, 1)
260
+ #plt.plot(list(range(2)), list(range(2))) # y = x line
261
+ #plt.plot(self.x_sat, self.y_sat) # VLE line
262
+ #plt.plot(self.x_oper, self.y_oper) # operational line
263
+ ##plt.plot(trays_x, trays_y) # McCabe Thiele points
264
+ #plt.xlim(0, 1)
265
+ #plt.xlabel('Liquid conc [kg/kg]')
266
+ #plt.ylim(0, 1)
267
+ #plt.ylabel('Vapor conc [kg/kg]')
268
+ #plt.savefig(plotfile)
269
+
270
+ self.oper_shift = shift
271
+ # print 'shift =', shift
272
+
273
+
274
+ def mccabe_diabatic(self, shift=None, plotfile=None, printmode=True):
275
+ """
276
+ This is a graphical way to do an approximate mass and energy balance on each tray for a diabatic column.
277
+ Creates McCabe-Thiele diagram using an minimum exergy destruction operational line that sets the liquid outlet
278
+ state (bottoms) given Statepoints for the inlet liquid and outlet vapor. Using COE, the necessary Q_sens is
279
+ calculated for each tray.
280
+ (See "Diabatic Column Optimization Compared to Isoforce Columns" by Sauar et al)
281
+
282
+ You can use the following results from this model:
283
+ Column.num_trays <-- number of trays
284
+ Column.Q_feed_to_satliq <-- Q to heat inlet feed to saturated liquid (if subcooled)
285
+ Column.Q_sens <-- array of Q_sens to each tray (1st val is top tray)
286
+ Column.Q_trays_total <-- total Q going to trays (sum of Q_sens)
287
+ Column.Q_total <-- total Q going to column (Q_trays_total + Q_feed_to_satliq)
288
+ Column.tray_temps <-- array of temperature for each tray (1st val is top tray)
289
+ Column.tray_concs <-- array of concentration arrays for each tray (1st val is top tray)
290
+ Column.tray_masses <-- array of mass flow arrays for each tray (1st val is top tray)
291
+ Column.bottoms <-- Statepoint of liquid outlet
292
+ As well as the outputs from get_diabatic_curves (called within)
293
+ column.x_sat <-- saturated liquid concentrations for temperature range
294
+ column.y_sat <-- saturated vapor concentrations for temperature range
295
+ column.x_oper <-- operating liquid concentrations for temperature range
296
+ column.y_oper <-- operating vapor concentrations for temperature range
297
+ column.oper_shift <-- value used to make oper. line from VLE line (x_VLE + oper_shift, y_VLE - oper_shift)
298
+
299
+ Produces a png picture file with the McCabe-Theile diagram (includes VLE line and operating line)
300
+
301
+ Written by Fiona Majeau for Rebound Technology June 30 2016
302
+ Rectifying section added by Luke Erickson for Rebound Technology Nov 30 2016
303
+ """
304
+
305
+ if printmode==True:
306
+ print('\nMcCabe-Thiele method for diabatic column (minimum exergy destruction)')
307
+ print('---------------------------------------------------------------------')
308
+ print('*** TOP DOWN VERSION -- GOOD ***')
309
+
310
+ # Create Statepoint for liquid outlet "bottoms"
311
+ self.bottoms = Statepoint(name='bottoms', fluid=self.fluid, p=self.P_col, x=0.)
312
+ outlet_liq_c_max = self.outlet_liq_c # maximum outlet liquid concentration (used as bound during tray calculations)
313
+
314
+ # Get operating line shift (plus VLE and operating lines for plotting)
315
+ self.get_diabatic_curves(shift=shift, plotfile=plotfile, printmode=printmode)
316
+ # if shift == None:
317
+
318
+ # Create arrays in which to store x and y values for each McCabe point, starting from the top
319
+ trays_x = [self.inlet.c] # top inlet x
320
+ if shift==None:
321
+ shift = self.oper_shift
322
+ trays_y = [self.outlet_vap.c] # top inlet y
323
+ else:
324
+ y_sat_xshift = float(get_C_vap_sat(self.fluid, trays_x[0] - shift, self.P_col))
325
+ trays_y = [y_sat_xshift - shift]
326
+ trays_x.append(float(get_C_liq_sat(self.fluid, trays_y[-1], self.P_col))) # top outlet x
327
+ trays_y.append(trays_y[-1]) # top outlet y
328
+
329
+ # Cleanly store inflows/outflows for each tray for later use in COE
330
+ # NOTE: tray_concs is an array (with num_trays elements) where each element is an array [Vin Lout Vout Lin]
331
+ Vin = 0 # for indexing
332
+ Lout = 1
333
+ Vout = 2
334
+ Lin = 3
335
+ tray_concs = [[0., 0., 0., 0.], [0., 0., 0., 0.]] # store concentrations (tray at 0 index will stay empty)
336
+ num_trays = 1 # start by populating the top tray (tray_conc[1])
337
+ tray_concs[num_trays][Lin] = trays_x[0] # Lin to the top tray
338
+ tray_concs[num_trays][Vout] = trays_y[-1] # Vout of the top tray
339
+
340
+ # Solve for trays, start from the top
341
+ if printmode==True:
342
+ print('\nSOLVING FOR CONCENTRATION IN EACH TRAY... ')
343
+ over_target = True
344
+ while over_target:
345
+
346
+ # Move vertically downward based on operating line shift
347
+ y_sat_xshift = float(get_C_vap_sat(self.fluid, trays_x[-1] - shift, self.P_col))
348
+
349
+ # Check whether movement downward will go into negative y values
350
+ if y_sat_xshift <= shift: # can't have McCabe point there, done with trays
351
+ # Remove last McCabe point (VLE point from horiz. calc)
352
+ trays_x.remove(trays_x[-1])
353
+ trays_y.remove(trays_y[-1])
354
+ num_trays = num_trays - 1
355
+ tray_concs[num_trays][Vin] = 0. # no vapor into bottom tray
356
+ print(tray_concs[num_trays-1])
357
+ print(tray_concs[num_trays])
358
+ print(tray_concs[num_trays+1])
359
+
360
+ #tray_concs[num_trays][Lout] = trays_x[-1] # same as previous point
361
+ break
362
+
363
+ # Add point array
364
+ trays_y.append(y_sat_xshift - shift)
365
+ trays_x.append(trays_x[-1])
366
+
367
+ # Move horizontally with VLE calc
368
+ x_sat_horiz = float(get_C_liq_sat(self.fluid, trays_y[-1], self.P_col))
369
+ trays_x.append(x_sat_horiz)
370
+ trays_y.append(trays_y[-1])
371
+
372
+ # Update tray concentrations
373
+ tray_concs[num_trays][Lout] = trays_x[-2] # second to last value appended
374
+ tray_concs[num_trays][Vin] = trays_y[-1] # last value appended
375
+
376
+
377
+ # Check whether there is an operating line directly below in anticipation of next vertical calc
378
+ if trays_x[-1] <= shift: # if so, can't have McCabe point there, done with trays
379
+ # Remove last McCabe point (VLE point from horiz. calc)
380
+ trays_x.remove(trays_x[-1])
381
+ trays_y.remove(trays_y[-1])
382
+ tray_concs[num_trays][Vin] = 0. # no vapor into bottom tray
383
+ break
384
+
385
+ # Check whether this tray overshot the target outlet liquid
386
+ elif trays_x[-1] < outlet_liq_c_max: # below target outlet liquid conc, target reached
387
+ # Move vertically downward based on operating line shift to finish off final tray
388
+ num_trays = num_trays + 1
389
+ y_sat_xshift = float(get_C_vap_sat(self.fluid, trays_x[-1] - shift, self.P_col))
390
+ trays_y.append(y_sat_xshift - shift)
391
+ trays_x.append(trays_x[-1])
392
+ tray_concs.append([0., 0., 0., 0.]) # populating last tray
393
+ tray_concs[num_trays][Lin] = tray_concs[num_trays - 1][Lout] # Lin of next tray = Lout of current tray
394
+ tray_concs[num_trays][Vout] = tray_concs[num_trays - 1][Vin] # Vout of next tray = Vin of current tray
395
+ tray_concs[num_trays][Lout] = trays_x[-1] # same as previous point
396
+ tray_concs[num_trays][Vin] = 0. # no vapor into bottom tray
397
+ break
398
+
399
+ # Print current tray
400
+ # print '--------------Tray %.2G --------------' % num_trays
401
+ # print 'Vap_in C = %.3f' % tray_concs[num_trays][Vin], ' Liq_out C = %.3f' % tray_concs[num_trays][Lout]
402
+ # print 'Vap_out C = %.3f' % tray_concs[num_trays][Vout], 'Liq_in C = %.3f' % tray_concs[num_trays][Lin]
403
+
404
+ # More trays to go...
405
+ num_trays = num_trays + 1
406
+ tray_concs.append([0., 0., 0., 0.]) # start populating next tray
407
+ tray_concs[num_trays][Lin] = tray_concs[num_trays - 1][Lout] # Lin of next tray = Lout of current tray
408
+ tray_concs[num_trays][Vout] = tray_concs[num_trays - 1][Vin] # Vout of next tray = Vin of current tray
409
+
410
+ # Check if number of trays has become impractical
411
+ if num_trays > 100:
412
+ raise Exception('Too many trays!')
413
+
414
+ # Solve for rectifier trays from inlet upwards
415
+ if tray_concs[1][Vout] < self.outlet_vap.c - 0.001:
416
+ num_trays = num_trays + 1
417
+ tray_concs[0][Lout] = tray_concs[1][Lin] # Lin of next tray = Lout of current tray
418
+ tray_concs[0][Vin] = tray_concs[1][Vout] # Vout of next tray = Vin of current tray
419
+ while tray_concs[1][Vout] < self.outlet_vap.c - 0.001:
420
+ # Move vertically upward based on operating line shift
421
+ y_sat_xshift = float(get_C_vap_sat(self.fluid, trays_x[0], self.P_col))
422
+ trays_y.insert(0, y_sat_xshift)
423
+ trays_x.insert(0, trays_x[0])
424
+
425
+ # Move horizontally with VLE calc
426
+ x_sat_horiz = float(get_C_liq_sat(self.fluid, trays_y[0] + shift, self.P_col))
427
+ trays_x.insert(0, x_sat_horiz + shift)
428
+ trays_y.insert(0, trays_y[0])
429
+
430
+ # Update tray concentrations
431
+ tray_concs[0][Lin] = trays_x[0] # second value appended
432
+ tray_concs[0][Vout] = trays_y[0] # first value appended
433
+
434
+ # Check whether there is an operating line directly below in anticipation of next vertical calc
435
+ if 1. - trays_x[0] <= shift or trays_y[0] > self.outlet_vap.c - 0.001: # if so, can't have McCabe point there, done with trays
436
+ trays_x.remove(trays_x[0]) # Remove last McCabe point (VLE point from horiz. calc)
437
+ trays_y.remove(trays_y[0])
438
+ tray_concs[0][Lin] = 1. # no liquid into top tray
439
+ tray_concs.insert(0, [0., 0., 0., 0.]) # I'm not sure why but Fiona had a blank tray at the beginning
440
+ break
441
+
442
+ # Check whether this tray overshot the target outlet liquid
443
+ elif trays_y[0] > self.outlet_vap.c - 0.001: # below target outlet liquid conc, target reached
444
+ # Move vertically upward based on operating line shift to finish off final tray
445
+ num_trays = num_trays + 1
446
+ y_sat_xshift = float(get_C_vap_sat(self.fluid, trays_x[0], self.P_col))
447
+ trays_y.insert(0, y_sat_xshift)
448
+ trays_x.insert(0, trays_x[0])
449
+ tray_concs.insert(0, [0., 0., 0., 0.]) # populating last tray
450
+ tray_concs[0][Lin] = 1. # No liquid into top tray
451
+ tray_concs[0][Vout] = trays_y[1] # Outlet vapor from this round of calcs
452
+ tray_concs[0][Lout] = tray_concs[1][Lin] # Outputs liquid demanded by lower tray
453
+ tray_concs[0][Vin] = tray_concs[1][Vout] # Input vapor from lower tray
454
+ break
455
+
456
+ # More trays to go...
457
+ num_trays = num_trays + 1
458
+ tray_concs.insert(0, [0., 0., 0., 0.]) # start populating next tray
459
+ tray_concs[0][Lout] = tray_concs[1][Lin] # Lin of next tray = Lout of current tray
460
+ tray_concs[0][Vin] = tray_concs[1][Vout] # Vout of next tray = Vin of current tray
461
+
462
+ # Check if number of trays has become impractical
463
+ if num_trays > 100:
464
+ raise Exception('Too many trays!')
465
+
466
+ # for tray_conc in tray_concs:
467
+ # print tray_conc
468
+
469
+ # Find Q into each tray, starting from top tray
470
+ if printmode==True:
471
+ print('\nSOLVING FOR Q_SENS INTO EACH TRAY... \n')
472
+ print('(NOTE: Tray 1 is top-most tray)')
473
+ Q_sens = []
474
+ tray_temps = []
475
+
476
+ # Get starting masses
477
+ m_liq_in_i = Quantity(0., 'kg*s^-1')
478
+ self.bottoms.set_c(tray_concs[-1][Lout])
479
+ if not hasattr(self.outlet_vap, 'm'):
480
+ self.outlet_vap.set_m(self.inlet.m * (self.inlet.c - self.bottoms.c) / (self.outlet_vap.c - self.bottoms.c)) # mass balance
481
+ self.outlet_vap.set_p(self.P_col)
482
+ self.outlet_vap.set_x(1.)
483
+ m_vap_out_i = self.outlet_vap.m
484
+ self.bottoms.set_m(self.inlet.m - self.outlet_vap.m)
485
+ feed_tray = [tray_concs.index(t) for t in tray_concs if t[Lin]==self.inlet.c][0]
486
+
487
+ # Check whether inlet feed was subcooled, if so account for Q to bring it up to saturation
488
+ sat_inlet = Statepoint(fluid=self.fluid)
489
+ sat_inlet.set_c(self.inlet.c)
490
+ sat_inlet.set_p(self.P_col)
491
+ sat_inlet.set_x(0.)
492
+ # if sat_inlet.t != self.inlet.t:
493
+ # Account for Q to bring it up to saturation (small delta T at roomish temp, assume same cp)
494
+ Q_feed_to_satliq = self.inlet.m * self.inlet.cp * (sat_inlet.t - self.inlet.t)
495
+
496
+ if printmode==True:
497
+ print('sat_inlet.t =', sat_inlet.t)
498
+
499
+ # Keep track of mass flows in and out of trays
500
+ tray_masses = [[0., 0., 0., 0.], [0., 0., 0., 0.]] # store concentrations (tray at 0 index will stay empty)
501
+ #num_trays = 1 # start by populating the top tray (tray_conc[1])
502
+
503
+ # COE and COM calculations to solve for Q
504
+ self.V = []
505
+ self.L = []
506
+ for i in range(1, num_trays + 1):
507
+ # Find enthalpies using Statepoint calcs
508
+ vap_in_i = Statepoint(fluid=self.fluid, p=self.P_col, x=1., c=float(tray_concs[i][Vin]))
509
+ vap_out_i = Statepoint(fluid=self.fluid, p=self.P_col, x=1., c=float(tray_concs[i][Vout]))
510
+ liq_in_i = Statepoint(fluid=self.fluid, p=self.P_col, x=0., c=float(tray_concs[i][Lin]))
511
+ liq_out_i = Statepoint(fluid=self.fluid, p=self.P_col, x=0., c=float(tray_concs[i][Lout]))
512
+
513
+ tray_temps.append(liq_out_i.t) # liq_out and vap_out will both give tray temp
514
+
515
+ # Mass balance (methanol and total)
516
+ if i == feed_tray:
517
+ m_liq_in_i = m_liq_in_i + self.inlet.m
518
+ ##TODO: Figure out what the problem with the mass calc in the last step is
519
+ if i == num_trays:
520
+ m_liq_out_i = self.bottoms.m # small errors become highly magnified at low concentrations
521
+ else:
522
+ m_liq_out_i = (m_liq_in_i * (vap_in_i.c - liq_in_i.c) + m_vap_out_i * (vap_out_i.c - vap_in_i.c)) / \
523
+ (vap_in_i.c - liq_out_i.c)
524
+ m_vap_in_i = m_vap_out_i + m_liq_out_i - m_liq_in_i
525
+
526
+ liq_out_i.set_m(m_liq_out_i)
527
+ liq_in_i.set_m(m_liq_in_i)
528
+ vap_in_i.set_m(m_vap_in_i)
529
+ vap_out_i.set_m(m_vap_out_i)
530
+ self.V.append(vap_out_i)
531
+ self.L.append(liq_in_i)
532
+
533
+ # COE to get heat transfer required
534
+ Q_sens_i = m_vap_out_i * vap_out_i.h + m_liq_out_i * liq_out_i.h - m_vap_in_i * vap_in_i.h - m_liq_in_i * liq_in_i.h
535
+ Q_sens.append(Q_sens_i)
536
+ if printmode==True:
537
+ print('------------------Tray %.2G ------------------' % i)
538
+ print('Q_sens: %.2G kW' % Q_sens_i)
539
+ print('Vap C: %.3f' % vap_out_i.c, 'Vap M: %.4f kg/s' % m_vap_out_i, 'Vap T: %.2f C' % vap_out_i.t)
540
+ print('Liq C: %.3f' % liq_out_i.c, 'Liq M: %.4f kg/s' % m_liq_out_i, 'Liq T: %.2f C' % liq_out_i.t)
541
+
542
+ # Record mass flow rates for tray
543
+ tray_masses.append([0., 0., 0., 0.]) # start populating next tray
544
+ tray_masses[i][Lin] = m_liq_in_i # Lin to the top tray
545
+ tray_masses[i][Vout] = m_vap_out_i # Vout of the top tray
546
+ tray_masses[i][Lout] = m_liq_out_i # Lout to the top tray
547
+ tray_masses[i][Vin] = m_vap_in_i # Vin of the top tray
548
+
549
+ # Move down one tray
550
+ m_vap_out_i = m_vap_in_i
551
+ m_liq_in_i = m_liq_out_i
552
+
553
+ self.L.append(liq_out_i) # add the final two statepoints to the output arrays
554
+ self.V.append(vap_in_i)
555
+
556
+ # for tray in tray_masses:
557
+ # print tray
558
+
559
+ # Plot
560
+ if plotfile != None:
561
+ print('')
562
+ #plt.figure() # make McCabe-Thiele diagram
563
+ #plt.subplot(1, 1, 1)
564
+ #plt.plot(list(range(2)), list(range(2))) # y = x line
565
+ #plt.plot(self.x_sat, self.y_sat) # VLE line
566
+ #plt.plot(self.x_oper, self.y_oper) # operational line
567
+ #plt.plot(trays_x, trays_y) # McCabe Thiele points
568
+ #plt.xlim(0, 1)
569
+ #plt.xlabel('Liquid conc [kg/kg]')
570
+ #plt.ylim(0, 1)
571
+ #plt.ylabel('Vapor conc [kg/kg]')
572
+ #plt.savefig(plotfile)
573
+
574
+ if printmode==True:
575
+ print('\nSUMMARY')
576
+ self.num_trays = num_trays # number of trays discrete trays
577
+ self.feed_tray = feed_tray
578
+ self.Q_feed_to_satliq = Q_feed_to_satliq # Q to bring inlet to saturation
579
+ self.Q_sens = Q_sens # array of Q's to each tray (1st value = top tray)
580
+ self.Q_trays_total = sum(Q_sens) # total Q to trays
581
+ self.Q_trays_heat = sum(Q_sens[(feed_tray-1):]) # total heating requirement
582
+ self.Q_trays_cool = sum(Q_sens[:feed_tray-1]) # total cooling requirement
583
+ self.Q_total = self.Q_trays_total + self.Q_feed_to_satliq # total Q to column
584
+ self.Q_input = self.Q_total
585
+ self.tray_temps = tray_temps # array of temps for each tray (1st value = top tray)
586
+ self.tray_concs = tray_concs # array of concentration arrays for each tray
587
+ self.tray_masses = tray_masses # array of mass flow arrays for each tray
588
+ if printmode==True:
589
+ print('Number of Trays =', num_trays)
590
+ print('Feed at tray ', feed_tray)
591
+ if self.bottoms.c > outlet_liq_c_max:
592
+ print(' NOTE: because of the solving method / operating line constraints, ')
593
+ print(' the outlet liquid vapor concentration is higher than %0.3f' % outlet_liq_c_max)
594
+ print(' Consider adding one more tray to the above number to be safe (%0.0f trays)' % (num_trays + 1))
595
+ print('Vapor outlet conc. =', self.outlet_vap.c)
596
+ print('Liquid outlet conc = %0.3f' % self.bottoms.c)
597
+ print('Q_feed_to_satliq =', self.Q_feed_to_satliq)
598
+ print('Q_trays_heat =', self.Q_trays_heat)
599
+ print('q_trays_cool =', self.Q_trays_cool)
600
+ print('Q_total =', self.Q_total)
601
+ self.trays_x = trays_x
602
+ self.trays_y = trays_y
603
+
604
+ # COM/COE check
605
+ COM = self.outlet_vap.m + self.bottoms.m - self.inlet.m
606
+ COE = (self.outlet_vap.m * self.outlet_vap.h) + (self.bottoms.m * self.bottoms.h) - (self.inlet.m * self.inlet.h) - self.Q_total
607
+ # print 'COM =', COM
608
+ # print 'COE =', COE
609
+
610
+
611
+ def off_design(self, heating_stream, n_trays, h_tray, d_col, htc, plotfile=None):
612
+ """
613
+ This model calculates the performance given both an inlet stream and a heating stream.
614
+ Most of the other models are set up to do design, meaning they output column specifications or heat requirements
615
+ This one, in contrast, just outputs performance based on a specified design parameters and inputs.
616
+
617
+ To do:
618
+
619
+ -It currently is set up for only a square column heating from the perimeter. We should make other options available
620
+ -Improve the solver speed. I think the best ways to do this would be to make the conc/mass convergence criteria adaptive and the heat transfer step size adaptive. A bigger but maybe better change would be to do something more complex than damped direct substitution.
621
+ -LMTD for the top tray is currently not accounting for the fact that the Q/T curve is significantly non-linear because that section has both sensible and latent heating. This should be fixed.
622
+ -Set up plotting again.
623
+ -Make outputs more useful.
624
+
625
+ :param heating_stream: A statepoint instance representing the heating fluid coming into the bottom of the column
626
+ :param n_trays: The number of trays [-]
627
+ :param h_tray: The height of a tray [m]
628
+ :param d_col: Width of the column [m]
629
+ :param htc: Heat transfer coefficient [kW/m^2-C]. I typically assume this is equal the HTC of the heating fluid.
630
+ :param plotfile: [string] name of png file to output a mccabe thiele style diagram. Plotting is not fully enabled right now
631
+ :return: Currently just prints all the outputs
632
+ """
633
+ print("===== Evaluating Problem Boundaries =====")
634
+ sp_all_vap = Statepoint(fluid=self.inlet.fluid, p=self.P_col, c=self.inlet.c, x=1., m=self.inlet.m)
635
+ print('Inlet fluid can absorb: ', self.inlet.m * (sp_all_vap.h - self.inlet.h))
636
+
637
+ sp_max_chilled = Statepoint(fluid=heating_stream.fluid, p=heating_stream.p, t=self.inlet.t, m=heating_stream.m)
638
+ print('Heating stream can supply: ', heating_stream.m * (heating_stream.h - sp_max_chilled.h))
639
+
640
+ print("\n===== Getting VLE =====")
641
+ vle_x = [x/100. for x in range(0, 101, 5)]
642
+ vle_y = [get_C_vap_sat(re.sub(r"\[|\]|[0-9]|\.", "", self.inlet.fluid), x, self.P_col) for x in vle_x]
643
+
644
+ print("\n===== Intializing guesses =====")
645
+ # Initialize heating fluid nodes
646
+ heating_streams = [Statepoint() for n in range(n_trays+1)]
647
+ heating_streams[-1] = heating_stream
648
+ for tray in range(n_trays-1, -1, -1): #step through trays from bottom to top
649
+ heating_streams[tray].set_source(heating_streams[tray+1], ('fluid', 'm', 'p'))
650
+ heating_streams[tray].set_t(self.inlet.t + (heating_stream.t - self.inlet.t)*tray/n_trays) # guess linear temp rise from top to bottom
651
+
652
+ print("\tUsing McCabe Thiele Model to Guess Performance")
653
+ """
654
+ This section uses the minimum exergy destruction mccabe theile model to get a decent starting point for
655
+ how the column will perform before doing physics-based calculations. The target outlet concentration is varied
656
+ until the number of trays is equal to the number of trays specified for this model.
657
+ """
658
+ self.num_trays = 0
659
+ vc_guess = get_C_vap_sat(self.fluid, self.inlet.c, self.P_col) - 0.05
660
+ # print vc_guess, self.num_trays
661
+ c_step = 0.02
662
+ last_step = 'none'
663
+ while self.num_trays != n_trays: # Vary the outlet concentration until the number of trays is correct
664
+ self.outlet_vap = Statepoint(fluid=self.fluid, p=self.P_col, x=1., c=vc_guess)
665
+ self.outlet_liq_c = 0.01
666
+ try:
667
+ self.mccabe_diabatic(printmode=False)
668
+ except:
669
+ self.outlet_vap = Statepoint(fluid=self.fluid, p=self.P_col, x=1., c=vc_guess+0.001) # doesnt always converge when # trays is near changing
670
+ if self.num_trays > n_trays:
671
+ if last_step == 'up': # reduce the step size when its crossed past the solution
672
+ c_step = c_step/2.
673
+ vc_guess = vc_guess - c_step
674
+ last_step = 'down'
675
+ elif self.num_trays < n_trays:
676
+ if last_step == 'down':
677
+ c_step = c_step/2.
678
+ vc_guess = vc_guess + c_step
679
+ last_step = 'up'
680
+ print(vc_guess, self.num_trays)
681
+
682
+ Q = list(self.Q_sens)
683
+ V = list(self.V)
684
+ L = list(self.L)
685
+ for i in range(len(V)):
686
+ V[i].name = i
687
+ L[i].name = i
688
+
689
+ qres = 1.
690
+ first_loop = True
691
+ while qres > 0.001: # Outer loop: solve for the heat transfer from the heating stream to the distilled fluid
692
+ clres = 1.
693
+ cvres = 1.
694
+ mlres = 1.
695
+ mvres = 1.
696
+ step = 1.
697
+ print('\nSolving mass balance: ')
698
+ print('Q: ', ['%.2f' % float(q) for q in Q], sum(Q))
699
+ while abs(clres) + abs(cvres) + abs(mlres) + abs(mvres) > 0.001: # Solve for mass flows and concentrations at Qs
700
+ c_bottoms_last = L[-1].c # save values to calculate residual
701
+ c_tops_last = V[0].c
702
+ m_bottoms_last = L[-1].m
703
+ m_tops_last = V[0].m
704
+
705
+ for v in reversed(V[:-2]): # solve COE and check props to get vapor equilibria from bottom to top
706
+ iv = v.name
707
+ new_c = get_C_vap_sat(self.fluid, L[iv+1].c, self.P_col) # property definition
708
+ h_vap_i1 = Statepoint(fluid=self.fluid, x=1, p=self.P_col, c=V[iv+1].c).h - Statepoint(fluid=self.fluid, x=0, p=self.P_col, c=V[iv+1].c).h #TODO: switch this out of sum(m.h)?
709
+ h_vap_i = Statepoint(fluid=self.fluid, x=1, p=self.P_col, c=new_c).h - Statepoint(fluid=self.fluid, x=0, p=self.P_col, c=new_c).h
710
+ new_m = (Q[iv] + V[iv+1].m * h_vap_i1) / h_vap_i # conservation of energy
711
+ V[iv] = Statepoint(name=v.name, fluid=self.fluid, p=v.p, x=v.x, c=new_c, m=new_m)
712
+
713
+ for l in L[1:]: # solve mass balances to get liquid flow and conc from top to bottom
714
+ il = l.name
715
+ new_m = max(L[il-1].m + V[il].m -V[il-1].m, 1e-6) # conservation of total mass
716
+ new_c = (V[il].m * V[il].c + L[il-1].m * L[il-1].c - V[il-1].m * V[il-1].c) / L[il].m # conservation of FPS mass
717
+ new_c = max(new_c, 0.0001)
718
+ new_c = L[il].c * (1.-step) + new_c * step
719
+ L[il] = Statepoint(name=l.name, fluid=self.fluid, p=l.p, x=l.x, c=new_c, m=new_m)
720
+
721
+ if L[-1].m != 0. and mlres != 0.: # I'm not really sure why but this was crashing sometimes without this check
722
+ if (L[-1].m - m_bottoms_last) / L[-1].m / mlres < 0:
723
+ step = step * 0.8
724
+
725
+ clres = (L[-1].c - c_bottoms_last) / L[-1].c
726
+ cvres = (V[0].c - c_tops_last) / V[0].c
727
+ mlres = (L[-1].m - m_bottoms_last) / L[-1].m
728
+ mvres = (V[0].m - m_tops_last) / V[0].m
729
+ sys.stdout.write('\rConc/mass residual: %f' % (abs(clres) + abs(cvres) + abs(mlres) + abs(mvres)))
730
+ print('\nL conc: ', ['%.3f' % l.c for l in L])
731
+ print('V conc: ', ['%.3f' % v.c for v in V])
732
+
733
+ print('Solving heat transfer: ', end=' ')
734
+ Q_last = list(Q)
735
+ for i in reversed(list(range(n_trays))): # resolve heat transfer from bottom up
736
+ q_tray_res = 1.
737
+ while q_tray_res > 0.001:
738
+ heating_streams[i] = Statepoint(source=heating_streams[i], source_vars=('fluid', 'm', 'p'))
739
+ heating_streams[i].set_h(heating_streams[i+1].h - Q[i] / heating_streams[i+1].m)
740
+
741
+ Q_i_last = Q[i]
742
+ try:
743
+ lmtdx = lmtd(heating_streams[i+1].t, heating_streams[i].t, L[i].t, L[i+1].t)
744
+ except AssertionError: # occurs when some steps have a temperature cross.
745
+ lmtdx = 1.e-3
746
+ A = 4. * h_tray * d_col
747
+ Q[i] = htc * A * lmtdx
748
+
749
+ q_tray_res = abs(Q[i] - Q_i_last) / Q[i]
750
+ print('.', end=' ')
751
+ print()
752
+ for i in range(n_trays): # Property calls crashed because the Q changes were causing extreme swings in the concentrations
753
+ q_damp_factor = 0.9
754
+ Q[i] = Q[i] * (1. - q_damp_factor) + Q_last[i] * q_damp_factor # damp the solver so the conc can keep up
755
+
756
+ qres = abs(sum(Q) - sum(Q_last)) / sum(Q)
757
+ if first_loop: # don't let it converge on the first step because its just duplicating the design calcs
758
+ qres = 1
759
+ first_loop = False
760
+ print('Q residual: ', qres)
761
+
762
+
763
+
764
+ print("\n===== Plotting =====")
765
+ # Plot
766
+ if plotfile != None:
767
+ #plt.figure() # make McCabe-Thiele diagram
768
+ #plt.subplot(1, 1, 1)
769
+ #plt.plot(list(range(2)), list(range(2)), color='gray') # y = x line
770
+ #plt.plot(vle_x, vle_y, color='blue') # VLE line
771
+ print(vle_x)
772
+ print(vle_y)
773
+ print()
774
+ # #plt.plot([tray.c_l_out for tray in trays], [tray.c_v_in for tray in trays]) # operational line
775
+ #plt.plot([l.c for l in L[1:]], [v.c for v in V[1:]], color='green') # operational line
776
+ print([l.c for l in L[1:]])
777
+ print([v.c for v in V[1:]])
778
+ print()
779
+ trays_x = [L[0].c]
780
+ trays_y = [V[0].c, V[0].c]
781
+ for tray in range(n_trays)[1:]:
782
+ trays_x.append(L[tray].c)
783
+ trays_x.append(L[tray].c)
784
+ trays_y.append(V[tray].c)
785
+ trays_y.append(V[tray].c)
786
+ trays_x.append(L[-1].c)
787
+ print(trays_x)
788
+ print(trays_y)
789
+ #plt.plot(trays_x, trays_y, color='red') # McCabe Thiele points
790
+ #plt.xlim(0, 1)
791
+ #plt.xlabel('Liquid conc [kg/kg]')
792
+ #plt.ylim(0, 1)
793
+ #plt.ylabel('Vapor conc [kg/kg]')
794
+ #plt.savefig(plotfile)
795
+
796
+
797
+
798
+ def tray(self,m_vap_in_guess, arg):
799
+ self.tray_outs = [None]*4
800
+ inlet_liq = arg[0]
801
+ outlet_vap = arg[1]
802
+ c_tray = arg[2]
803
+ Fluid = arg[3]
804
+ Q_sens = arg[4]
805
+ assert isinstance(inlet_liq, Statepoint), '\'inlet\' must be set to a Statepoint'
806
+ assert isinstance(outlet_vap, Statepoint), '\'inlet\' must be set to a Statepoint'
807
+
808
+ #this is the vapor coming from the tray below the current tray
809
+ inlet_vap = Statepoint(name='inlet_vap', fluid=Fluid)
810
+ inlet_vap.set_x(1.)
811
+ inlet_vap.set_p(self.P_col)
812
+
813
+ #this is the liquid in the current tray
814
+ outlet_liq = Statepoint(name='outlet_liq', fluid=Fluid)
815
+ outlet_liq.set_c(c_tray)
816
+ outlet_liq.set_x(0.)
817
+ outlet_liq.set_p(self.P_col)
818
+
819
+ count=0
820
+ tol=.002
821
+ stop=0
822
+ while stop <= 1 :
823
+ #update the vapor guess based on the new iteration
824
+ inlet_vap.set_m(m_vap_in_guess)
825
+ outlet_liq.set_m(inlet_vap.m - outlet_vap.m + inlet_liq.m)
826
+ inlet_vap.set_c((outlet_liq.m*outlet_liq.c + outlet_vap.m*outlet_vap.c - inlet_liq.m*inlet_liq.c)/inlet_vap.m)
827
+
828
+ #COE should be 0, so iterate c_guess until it is
829
+ m_vap_in_guess = (outlet_liq.m*outlet_liq.h-inlet_liq.m*inlet_liq.h + outlet_vap.m*outlet_vap.h - Q_sens)/ inlet_vap.h
830
+ m_vap_in_guess_old = m_vap_in_guess
831
+
832
+ COE = abs(float(inlet_liq.m*inlet_liq.h - outlet_liq.m*outlet_liq.h - outlet_vap.m*outlet_vap.h + inlet_vap.m*inlet_vap.h + Q_sens)/(inlet_liq.m*inlet_liq.h+inlet_vap.m*inlet_vap.h))
833
+ COM = abs(float(inlet_liq.m*inlet_liq.c + inlet_vap.m*inlet_vap.c - outlet_liq.m*outlet_liq.c - outlet_vap.m*outlet_vap.c)/(inlet_liq.m*inlet_liq.c + inlet_vap.m*inlet_vap.c)) + abs(float(inlet_liq.m + inlet_vap.m - outlet_liq.m - outlet_vap.m))/(inlet_liq.m + inlet_vap.m)
834
+ res = abs(COE)
835
+
836
+ count=count+1
837
+
838
+ if abs(res)<tol:
839
+ stop=stop+1
840
+ if count > 100:
841
+ warnings.warn('tray did not converge after 100 iteration')
842
+ raise Exception()
843
+
844
+ self.tray_outs = [inlet_vap, outlet_liq, COE, COM]
845
+
846
+ #print
847
+ #print outlet_liq.m, outlet_vap.m, inlet_liq.m, inlet_vap.m
848
+ #print outlet_liq.c, outlet_vap.c, inlet_liq.c, inlet_vap.c
849
+
850
+
851
+ def get_trays(self):
852
+ assert isinstance(self.inlet, Statepoint), '\'inlet\' must be set to a Statepoint'
853
+ assert isinstance(self.outlet_vap, Statepoint), '\'outlet_vap\' must be set to a Statepoint'
854
+
855
+ tray_liq = [None]*1
856
+ tray_vap = [None]*1
857
+ self.tray_outs = [None]*4
858
+
859
+ self.bottoms = Statepoint(name='bottoms', fluid=self.fluid)
860
+ self.bottoms.set_p(self.P_col)
861
+ self.bottoms.set_x(0.)
862
+ self.bottoms.set_c(self.outlet_liq_c)
863
+ self.bottoms.set_m(self.inlet.m-self.outlet_vap.m)
864
+
865
+
866
+ c_first_tray = self.inlet.c
867
+
868
+ if c_first_tray > self.inlet.c:
869
+ warnings.warn('desired outlet vapor concetration is higher than inlet equilibrium vapor')
870
+ raise Exception()
871
+
872
+ tray_liq[0] = self.inlet
873
+ tray_vap[0] = 0.
874
+ tray_vap.append(self.outlet_vap)
875
+
876
+ Q_input_guess = self.outlet_vap.m*self.outlet_vap.h - self.inlet.m*self.inlet.h + self.bottoms.m*self.bottoms.h
877
+ i=1
878
+ liq_c = tray_liq[0].c
879
+ stop = 0
880
+ while stop==0:
881
+ #for these iterations i = the current tray
882
+
883
+ arg=[None]*1
884
+ arg[0] = tray_liq[i-1] #last tray (i-1) liquid statepoint
885
+ arg.append(tray_vap[i]) #outlet vapor statepoint, this is the inlet vapor calculated in the tray above
886
+ arg.append(get_C_liq_sat(self.fluid,tray_vap[i].c,self.P_col)) #current tray (i) concentration
887
+ arg.append(self.fluid) #column fluid
888
+ arg.append(self.Q_sens) # sensible heat addition
889
+ m_guess = float(tray_vap[i].m)
890
+ self.tray(m_vap_in_guess=m_guess, arg=arg)
891
+
892
+ tray_vap.append(self.tray_outs[0])
893
+ tray_liq.append(self.tray_outs[1]) #this creates the i-1 tray's outlet vapor statepoint, IE, the inlet in the i tray
894
+ print('---------Tray %.2G Converged---------'%i)
895
+ print('COM:%.2G'%self.tray_outs[3], 'COE:%.2G'%self.tray_outs[2])
896
+ print('Vap C: %.3G'%tray_vap[i].c, 'Vap M: %.2G'%tray_vap[i].m, 'Vap T: %.3G'%tray_vap[i].t)
897
+ print('Liq C: %.3G'%tray_liq[i].c, 'Liq M: %.2G'%tray_liq[i].m, 'Liq T: %.3G'%tray_liq[i].t)
898
+
899
+ if tray_liq[i].c<self.outlet_liq_c:
900
+ stop = 1
901
+ self.Q_bottoms = tray_vap[i+1].m*(tray_vap[i+1].h-tray_liq[i].h)
902
+ self.bottoms.set_m(tray_liq[i].m - tray_vap[i+1].m)
903
+ self.bottoms.set_c((tray_liq[i].m*tray_liq[i].c - tray_vap[i+1].m*tray_vap[i+1].c)/self.bottoms.m)
904
+
905
+ i=i+1
906
+ if i > 50:
907
+ warnings.warn('column did not produce outlet condition within 50 trays')
908
+ raise Exception()
909
+
910
+ n_trays = i-1
911
+ self.Q_input = self.Q_bottoms + self.Q_sens*n_trays
912
+ self.Per_dia = (self.Q_sens*n_trays)/self.Q_input
913
+ self.COE_total = abs(tray_liq[0].m*tray_liq[0].h - tray_vap[1].m*tray_vap[1].h - self.bottoms.m*self.bottoms.h + self.Q_input)/self.Q_input
914
+
915
+
916
+ # solve flooding equations to get diameter for column
917
+ # pack_f = 693.0 #Cannon packing 0.16" [?m^2/m^3?]
918
+ # HETP = 36.0 #Cannon packing 0.16" [?mm?]
919
+ # pack_f = 92. # Raschig super rings 0.3 (assume same as kempack)
920
+ # HETP = 400. # Raschig super rings 0.3 (http://www.raschig-usa.com/Portals/0/RASCHIG%20SUPER-RING%200_3%20-%20FRI%20Test%20Results.pdf fig 1)
921
+ pack_f = 92. # Kempac 1" Tripack [m^2/m^3]
922
+ HETP = 142. # Kempac 1" Tripack [mm]
923
+ self.d_col = 0.1 #guess diameter
924
+ self.L_col = n_trays*HETP/1000.
925
+ mid_tray = int(round(n_trays/2,1))
926
+
927
+ er=2.
928
+ i=1
929
+ flood_target = 0.3 #flooding target based on Kister p493
930
+ while abs(er)>.01:
931
+ A_cross = self.d_col**2./4.*3.1415
932
+ G_vap = tray_vap[mid_tray].m*3600./A_cross
933
+ G_liq = tray_liq[mid_tray].m*3600./A_cross
934
+ rho_vap = tray_vap[mid_tray].rho
935
+ rho_liq = tray_liq[mid_tray].rho
936
+ X = G_liq/G_vap*(rho_vap/rho_liq)**(0.5)
937
+ Y = ((G_vap*0.205)/3600.)**2.0*pack_f*0.881/(32.2*(rho_vap*0.0624)*((rho_liq*0.0624)-(rho_vap*0.0624)))
938
+ Y_flood = 1.4427*X**2-1.0271*X+0.2312
939
+ per_flood = Y/Y_flood
940
+ er = (flood_target - per_flood)/flood_target
941
+ self.d_col = self.d_col*(1.-er/5.)
942
+ i=i+1
943
+ if i > 50:
944
+ warnings.warn('diameter of column did not converge withing 50 iterations')
945
+ raise Exception()
946
+ self.d_col = Property(float(self.d_col),'m')
947
+
948
+
949
+ #print "Gvap", G_vap, "Gliq", G_liq
950
+
951
+ self.n_trays = n_trays
952
+
953
+
954
+
955
+
956
+
957
+
958
+
959
+
960
+ if __name__=='__main__':
961
+ #FPS = 'REFPROP-MIX:AMMONIA&WATER'
962
+ #FPS = 'REFPROP-MIX:ACETONE&WATER'
963
+ FPS = 'REFPROP-MIX:METHANOL&WATER'
964
+
965
+ P_col = Property(0.2,'bar')
966
+ liq = Statepoint(name='liq', fluid=FPS)
967
+ vap = Statepoint(name='vap', fluid=FPS)
968
+ liq.set_x(0.)
969
+ liq.set_c(.31)
970
+ liq.set_p(P_col)
971
+ liq.set_m(Property(0.074,'kg*s^-1'))
972
+
973
+ vap.set_x(1.)
974
+ vap.set_c(.84)
975
+ vap.set_p(liq.p)
976
+ # vap.set_m(liq.m*liq.c/vap.c) # mass is overdefined, calculated in the functions
977
+
978
+ col = Column()
979
+ col.set_P_col(P_col)
980
+ col.set_inlet(liq)
981
+ col.set_Q_sens(1.2) #1.2
982
+ col.set_outlet_vap(vap)
983
+ col.set_outlet_liq_c(.01)
984
+ col.set_fluid(FPS)
985
+
986
+ col.ME_balance()
987
+ print(col.Q_input)
988
+ print(col.bottoms)
989
+ # print "Solving adiabatic McCabe Thiele"
990
+ # col.mccabe(plotfile='McCabeThieleDiagram')
991
+
992
+ # print "\n\nSolving COE/COM diabatic model"
993
+ # col.set_diabatic(0.1)
994
+ # step = 0.9
995
+ # count = 10
996
+ # col.get_trays()
997
+ # COM = col.bottoms.m + vap.m - liq.m
998
+
999
+ print('\n\nSolving McCabe Thiele diabatic model')
1000
+ # col.set_outlet_vap(Statepoint(fluid=FPS, p=P_col, x=1., c=0.95))
1001
+ col.mccabe_diabatic(shift=0.03, plotfile='McCabeThieleDiabatic')
1002
+
1003
+ # print "Diameter", col.d_col, "total Q", col.Q_input, "Diabatic", col.Per_dia
1004
+ # print "COM:",COM, "bottoms mass flow:",col.bottoms.m