def rate_of_change_product(prod,state,prod_orifice=0,verbose=False): #state is container supplying the product P_N=state.P[-1]*norm.P0 P_tank=prod.Pprod*norm.P0 #compute flows in sN/m3 in_prod_flow=orifice.orifice_flow2(P_N/1000,P_tank/1000,prod_orifice,T=param.Ta)/60 #output from product tank out_prod_flow=orifice.orifice_flow2(P_tank/1000,1e5/1000,param.product_orifice,T=param.Ta)/60 #these are flow rates in Nm^3/second #convert prod_flow to dimensionless flow rates in_prod_flow/=norm.z0**2*norm.v0 out_prod_flow/=norm.z0**2*norm.v0 net_flow=in_prod_flow-out_prod_flow #Product tank dPprod=dg.kprod*net_flow #if dg.kprod > 1e6 or prod.Pprod > 1e6 or state.yA[-1] - prod.yprod) > 1e6 or in_prod_flow>1e6: with warnings.catch_warnings(): warnings.filterwarnings('error') try: dyprod=dg.kprod/prod.Pprod*(state.yA[-1] - prod.yprod)*in_prod_flow except Warning: sys.stdout.write('WARNING:{} {} {} {}\n'.format(prod.Pprod, state.yA, prod.yprod, in_prod_flow)) raise #print('in={} out={}'.format(in_prod_flow,out_prod_flow)) #print(prod.Pprod,prod.yprod,net_flow,dg.kprod) #print('d',dPprod,dyprod) return dPprod, dyprod
def rate_of_change(state, in_P=None, out_P=None, in_orifice=0, out_orifice=0, in_yA=None, in_yB=None, out_yA=None, out_yB=None,verbose=False, prod_orifice=0, modehalf=None): #calculate in and out velocities and call rate_of_change_vel #orifice_flow2(p1, p2, d, C=0.70, T=T_room): P_feed=in_P*norm.P0 #convert to Pa abs P_1=state.P[0]*norm.P0 #denormalize, convert to Pa if verbose: sys.stdout.write('state.P: {}\n'.format(state.P)) P_N=state.P[-1]*norm.P0 P_exit=out_P*norm.P0 #compute flows in sN/m3 if verbose: print('P_feed {} P_1 {} P_N {} P_exit {}'.format(P_feed,P_1,P_N,P_exit)) in_flow=orifice.orifice_flow2(P_feed/1000,P_1/1000,in_orifice,T=param.Ta)/60 out_flow=orifice.orifice_flow2(P_N/1000,P_exit/1000,out_orifice,T=param.Ta)/60 #NOTE: #HERE WE override the flow rates in m/3 #if modehalf is not None and modehalf[0]==1: # #we are doing pressurization, override the inflow rate with a constant # in_flow=9.5/60/1000 # LPM to in Nm^3/sec #these are flow rates in Nm^3/second #now convert to velocity in m/s with warnings.catch_warnings(): warnings.filterwarnings('error') try: in_vel=in_flow*(1e5/P_1)/param.area # [m/s] out_vel=out_flow*(1e5/P_N)/param.area # [m/s] except Warning: sys.stdout.write('WARNING: {} {}\n'.format(in_flow, P_1)) sys.stdout.write('WARNING: {} {}\n'.format(out_flow, P_N)) raise #convert prod_flow to dimensionless flow rates if verbose: print('in_vel={}'.format(in_vel)) print('out_vel={}'.format(out_vel)) print('Pressure: {}'.format(state.P)) #convert to dimensionless in_vel/=norm.v0 out_vel/=norm.v0 #constant_vel=True #if constant_vel and in_vel>0: # in_vel=1.0 # out_vel=0 jplus5,jminus5=difference.gen_j5_functions(param.mode,in_vel>=0 and out_vel>=0) #print('flow rates, m/s {} {}'.format(in_vel, out_vel)) return rate_of_change_vel(state, vin=in_vel, vout=out_vel, in_yA=in_yA, in_yB=in_yB, out_yA=out_yA,out_yB=out_yB,verbose=verbose, jplus5=jplus5,jminus5=jminus5)
def product_flow_rate(P): #given an array of product pressures, compute the corresponding output flow rates #in LPM p1=P*norm.P0 p2=1e5 # 1 atm in [Pa] #Nm3/min f=orifice.orifice_flow2(p1/1000,p2/1000,param.product_orifice) return f*1000 # LPM
def feed_rates(P): #return vin, vout (normalized) #orifice_flow2(p1, p2, d, C=0.70, T=T_room): #input P_feed=param.feed_pressure*1e5 # feed_pressure in bar, convert to Pa abs P_1=P[0]*norm.P0 #denormalize, convert to Pa P_N=P[-1]*norm.P0 P_exit=1e5 #1ATM #compute flows in sN/m3 #print('P_feed {} P_1 {} P_N {} P_exit {}'.format(P_feed,P_1,P_N,P_exit)) in_flow=orifice.orifice_flow2(P_feed/1000,P_1/1000,param.input_orifice,T=param.Ta) out_flow=orifice.orifice_flow2(P_N/1000,P_exit/1000,param.output_orifice,T=param.Ta) #now convert to velocity in m/s in_vel=in_flow*(1e5/P_1)/param.area # [m/s] out_vel=out_flow*(1e5/P_N)/param.area # [m/s] #convert to dimensionless #print('flow rates, m/s {} {}'.format(in_vel, out_vel)) return in_vel/norm.v0, out_vel/norm.v0
def create_param(mods, print=print): #mods is a dict to override the parameter defaults set here #override print function if needed for debug statements to a log file #any keys with value of None are ignored #real_cycle_time and real_vent_time will override cycle_time and vent_time #cycles and time are used to set the simulate end time param = AttrDict() #Physical constants param.R = 8.314 #J/mol-K #Simulation parameters #the mode for the difference scheme param.mode = 1 param.N = 10 #discretization steps in spatial direction param.tstep = 0.20 #time step for ODE (dimensionless) param.adsorb = True #set to True to have adsorption happen #cycle_time, vent_time param.cycle_time = 36 #time for a half-cycle in dimensionless time units param.vent_time = 23.04 #from beginning of cycle in dimensionless time units #Physical system parameters param.Ta = 20 + 273.15 #room temperature in K #Picked these sizes for a volume of about 2000 cm3 param.D = 0.095 #Diameter of cylinder [m] #param.D=0.05 #Diameter of cylinder [m] param.L = .33 #Length of cylinder (m) - 13" #param.L=1.00 #Length of cylinder (m) param.product_tank_volume = 2.0 / 1000 # 2L in m3 #we set an orifice so that we can 5LPM at a certain pressure (3bar abs) and #try to get an output pressure sits around that 3 bar. #param.epsilon=0.36 # void fraction (fraction of space filled with gas vs. spheres) param.epsilon = 0.37 param.epsilonp = 0.35 #particle voidage param.rp = 1e-3 #particle radius [m] param.tauprime = 3.0 # tortuosity #flow params param.input_orifice = 2.0 # dia mm - pressure feed param.output_orifice = 1.40 # dia mm - to output tank #we use an orifice that will provide 5LPM at 3 bar abs product tank param.product_orifice = 0.475 # dia mm - output from product tank to patient #try a smaller for testing #param.product_orifice=0.3 # dia mm - output from product tank to patient param.blowdown_orifice = 2.80 # dia mm param.vent_orifice = 1 # dia mm param.feed_pressure = 3.5 #pressure in bar (1e5 Pa), absolute param.PH = param.feed_pressure * 1e5 #Pa, will be the normalization pressure param.feed_O2 = 0.21 #feed gas fraction O2 param.product_pressure = 2 # bar in output product tank (not used) #Note: random sphere packing is 64%, densest is 74%, void fractions=.36, .26 #param.DL=1.0 #Axial dispersion coefficient (m2/s) #Mol wt O=16, N=14 #air is about 14e-3 kg/mol #These are for my calculation, note different from paper (paper was doing #CO2 and N2 separation, and used a more complex Langmuir dual-site model #param.qAs=5.26e-3 #saturation constant for O2 mol/cm3 (at infinite pressure) #param.qBs=5.26e-3 #saturation constant for N2 mol/cm3 (at infinite pressure) #Langmuir constants. inverse of pressure mol/m3 where we reach 50% of saturation #Estimated these from chart in Yang book at 1 ATM. #param.bA=573.5 # cm3/mol, inverse of 42.5 bar #param.bB=2030 # cm3/mol, inverse of 12 bar #From Mofahari 2013 paper, for Zeolite 13X #param.bA=0.042 # 1/bar @ Room temp #param.bB=0.616 # 1/bar @ Room temp #From Jee paper, same as Mofahari but *0.10 for k3!! #adding correct exponent on Pressure param.bA = 0.0042 # 1/bar @ Room temp param.bB = 0.0616 # 1/bar @ Room temp param.qAs = 0.0025 #saturation constant for O2 mol/g (at infinite pressure) param.qBs = 0.0073 #saturation constant for N2 mol/g (at infinite pressure) param.nA = 1.006 #exponent on bar pressure O2 param.nB = 0.830 #exponent on bar pressure N2 param.kA = 0.620 param.kB = 0.197 #From Sircar ch22 paper @ 25C #param.bA=0.0295 # 1/bar @ Room temp #param.bB=0.107 # 1/bar @ Room temp #param.qAs=0.00312 #saturation constant for O2 mol/g (at infinite pressure) #param.qBs=0.00312 #saturation constant for N2 mol/g (at infinite pressure) #For 5A: #param.bA=0.052 # 1/bar @ Room temp #param.bB=0.165 # 1/bar @ Room temp #param.qAs=0.0019 #saturation constant for O2 mol/g (at infinite pressure) #param.qBs=0.0025 #saturation constant for N2 mol/g (at infinite pressure) #param.kA=.15 #param.kB=.05 #From the new paper param.rho_s = 1130 #adsorbent density kg/m3 param.rho_w = 7800 #wall density kg/m3 param.rho_pellet = 1.160 * 1000 # [kg/m3] param.Cpg = 30.7 #specific heat capacity of gas phase [J/mol/K] param.Cpa = 30.7 #specific heat capacity of adsorbed phase [J/mol/K] param.Cps = 1070 #specific heat capacity of adsorbent [J/kg/K] param.Cpw = 502 #specific heat capacity of wall [J/kg/K] param.mu = 1.72e-5 #fluid viscocity param.Dm = 1.6e-5 #Molecular diffusivity [FOR CO2/N2 paper] param.gamma = 1.4 #adiabatic constant param.Kz = 0.09 #Effective gas thermal conductivity [J/m/K/s] param.Kw = 16 #Effective thermal cond of wall param.hin = 8.6 #inside heat transfer coeff param.hout = 2.5 #outside heat transfer coeff param.R = 8.314 #Gas constant [m3Pa/mol/K] param.eta = .72 #compression/evacuation efficiency #not doing Temp and Tw simulation at the moment param.HA = 0 #heat of adsorption of component A [J/mol] param.HB = 0 #heat of adsorption of component B [J/mol] ##### #Apply overrides, ignoring None values. But see cycles/time logic below for k in param.keys(): if k in mods and mods[k] is not None: param[k] = mods[k] print('params overriding {}:{}'.format(k, param[k])) ##### #Things we have to calculate, need to do this after the overriding param.area = (param.D / 2)**2 * math.pi #[m2] param.volume = param.area * param.L print('canister volume is {} L'.format(param.volume * 1000)) param.rin = param.D / 2 param.rout = param.rin + 0.25 param.epst = param.epsilon / (1 - param.epsilon) param.epsb = (1 - param.epsilon) / param.epsilon param.cellsize = param.L / param.N param.deltaZ = 1 / param.N # nondimensional deltaZ param.container_vol = param.area * param.L #volume of reactor (m3) #We compute the initial flow rate and use that for velocity normalization in_flow = orifice.orifice_flow2( param.feed_pressure * 1e5 / 1000, 100, param.input_orifice, T=param.Ta) / 60 in_vel = in_flow / param.area # [m/s] param.norm_v0 = in_vel # [m/s] feed velocity, which varies param.norm_t0 = param.L / param.norm_v0 # so t=time/norm.t0 param.feed_N2 = 1.0 - param.feed_O2 # feed gas fraction N2 #molecular weight of air is 29 g/mol or 29e-03 kg/mol param.rho_g = 100000 / param.R / param.Ta * 29e-3 # Needs to be adjusted for pressure/Temp param.Dp = param.Dm / param.tauprime #NOTE: norm.v0 is computed after this, will be set to param.v0 #Axial dispersion coefficient m2/sec #param.DL=0.7*param.Dm+0.5*param.norm_v0*param.rp*2.0 param.DL = 4.9e-4 # m2/sec from another paper print('param.DL={}'.format(param.DL)) #compute param.end_time for the simulation #want exactly 1 to be not None and 1 None #assert (mod.cycles is None) + (mod.time is None) == 1 #total time to simulate (dimensionless). This is handled differently because #we allow 2 ways to override (cycles takes precedence over time) #First we check for real_cycle_time and real_vent_time rct = mods.get('real_cycle_time', None) rvt = mods.get('real_vent_time', None) #note: param.real_vent_time will only exist if this method of setting was used. #simulation should not care about real cycle time, this is just for reporting #in log files if rct: param.cycle_time = rct / param.norm_t0 param.real_cycle_time = rct print('using a real cycle time of {:7.2f} -> {:7.2f}'.format( rct, param.cycle_time)) if rvt: param.vent_time = rvt / param.norm_t0 param.real_vent_time = rvt print('using a real vent time of {:7.2f} -> {:7.2f}'.format( rvt, param.vent_time)) param.end_time = 2 * param.cycle_time #default will be 2 cycles if mods.get('cycles', None) is not None: param.end_time = param.cycle_time * mods['cycles'] else: if mods.get('time', None) is not None: param.end_time = mods['time'] print('running for end_time of {}'.format(param.end_time)) #this might not be an integer param.cycles = param.end_time / param.cycle_time #set real times param.real_cycle_time = param.cycle_time * param.norm_t0 param.real_vent_time = param.vent_time * param.norm_t0 return param