def setup(self, T0, P0, mdot_fuel, mdot_ox): self.gas = ct.Solution('gri30.xml') # fuel inlet self.gas.TPX = T0, P0, "CH4:1.0" self.fuel_in = ct.Reservoir(self.gas) # oxidizer inlet self.gas.TPX = T0, P0, "N2:3.76, O2:1.0" self.oxidizer_in = ct.Reservoir(self.gas) # reactor, initially filled with N2 self.gas.TPX = T0, P0, "N2:1.0" self.combustor = ct.IdealGasReactor(self.gas) self.combustor.volume = 1.0 # outlet self.exhaust = ct.Reservoir(self.gas) # connect the reactor to the reservoirs self.fuel_mfc = ct.MassFlowController(self.fuel_in, self.combustor) self.fuel_mfc.set_mass_flow_rate(mdot_fuel) self.oxidizer_mfc = ct.MassFlowController(self.oxidizer_in, self.combustor) self.oxidizer_mfc.set_mass_flow_rate(mdot_ox) self.valve = ct.Valve(self.combustor, self.exhaust) self.valve.set_valve_coeff(1.0) self.net = ct.ReactorNet() self.net.add_reactor(self.combustor) self.net.max_err_test_fails = 10
def connectReactors(self): """Use this function to connect exhaust reactors.""" #Connecting fuel res -> combustor def inflow( t ): # inflow is a variable mass flow rate base on residence time return self.reactors[0].mass / self.residenceTime(t) self.inflow = inflow self.controllers = ct.MassFlowController( self.fuelReservoir, self.reactors[0], mdot=self.inflow), #Fuel Air Mixture MFC #Connecting combustor -> exhaust self.controllers += ct.MassFlowController( self.reactors[0], self.reactors[1], mdot=self.inflow), #Exhaust MFC #Connecting exhausts -> adj exhaust exStart = 1 #starting index of exhaust reactors for f, row in enumerate(self.connects[:-1], exStart): sink = np.sum( row ) #number of places the mass flow goes from one reactor to another if sink != 0: #Connects non-terminal exhaust reactors for t, value in enumerate(row[:-1], start=exStart): if value: #Using a closure to create mass flow function def mdot(t, fcn=None): return (self.reactors[fcn.ridx].mass / self.residenceTime(t)) / fcn.sink mdot.__defaults__ = (mdot, ) mdot.sink = sink #number of places the mass flow goes from one reactor to another mdot.ridx = np.copy(f) #Create controller self.controllers += ct.MassFlowController( self.reactors[f], self.reactors[t], mdot=mdot), #Exhaust MFCS else: #This else statement connects all terminal exhaust reactors to exhaust reservoir #Using a closure to create mass flow function def mdot(t, fcn=None): return (self.reactors[fcn.ridx].mass / self.residenceTime(t)) mdot.__defaults__ = (mdot, ) mdot.sink = sink #number of places the mass flow goes from one reactor to another mdot.ridx = np.copy(f) #Create controller self.controllers += ct.MassFlowController( self.reactors[f], self.exhaustReservoir, mdot=mdot), #Exhaust MFCS #entrainment controllers for t, value in enumerate(self.connects[-1], exStart): if value: self.controllers += ct.MassFlowController( self.atmosReservoir, self.reactors[t], mdot=self.entrainment), #Exhaust MFCS
def create_reactors(self, add_Q=False, add_mdot=False, add_surf=False): self.gas = ct.Solution('gri30.xml') self.gas.TPX = 900, 25*ct.one_atm, 'CO:0.5, H2O:0.2' self.gas1 = ct.Solution('gri30.xml') self.gas1.ID = 'gas' self.gas2 = ct.Solution('gri30.xml') self.gas2.ID = 'gas' resGas = ct.Solution('gri30.xml') solid = ct.Solution('diamond.xml', 'diamond') T0 = 1200 P0 = 25*ct.one_atm X0 = 'CH4:0.5, H2O:0.2, CO:0.3' self.gas1.TPX = T0, P0, X0 self.gas2.TPX = T0, P0, X0 self.r1 = ct.IdealGasReactor(self.gas1) self.r2 = self.reactorClass(self.gas2) self.r1.volume = 0.2 self.r2.volume = 0.2 resGas.TP = T0 - 300, P0 env = ct.Reservoir(resGas) U = 300 if add_Q else 0 self.w1 = ct.Wall(self.r1, env, K=1e3, A=0.1, U=U) self.w2 = ct.Wall(self.r2, env, A=0.1, U=U) if add_mdot: mfc1 = ct.MassFlowController(env, self.r1, mdot=0.05) mfc2 = ct.MassFlowController(env, self.r2, mdot=0.05) if add_surf: self.interface1 = ct.Interface('diamond.xml', 'diamond_100', (self.gas1, solid)) self.interface2 = ct.Interface('diamond.xml', 'diamond_100', (self.gas2, solid)) C = np.zeros(self.interface1.n_species) C[0] = 0.3 C[4] = 0.7 self.surf1 = ct.ReactorSurface(self.interface1, A=0.2) self.surf2 = ct.ReactorSurface(self.interface2, A=0.2) self.surf1.coverages = C self.surf2.coverages = C self.surf1.install(self.r1) self.surf2.install(self.r2) self.net1 = ct.ReactorNet([self.r1]) self.net2 = ct.ReactorNet([self.r2]) self.net1.set_max_time_step(0.05) self.net2.set_max_time_step(0.05) self.net2.max_err_test_fails = 10
def setUp(self): self.gas = ct.Solution('h2o2.xml') # create a reservoir for the fuel inlet, and set to pure methane. self.gas.TPX = 300.0, ct.one_atm, 'H2:1.0' fuel_in = ct.Reservoir(self.gas) fuel_mw = self.gas.mean_molecular_weight # Oxidizer inlet self.gas.TPX = 300.0, ct.one_atm, 'O2:1.0, AR:3.0' oxidizer_in = ct.Reservoir(self.gas) oxidizer_mw = self.gas.mean_molecular_weight # to ignite the fuel/air mixture, we'll introduce a pulse of radicals. # The steady-state behavior is independent of how we do this, so we'll # just use a stream of pure atomic hydrogen. self.gas.TPX = 300.0, ct.one_atm, 'H:1.0' self.igniter = ct.Reservoir(self.gas) # create the combustor, and fill it in initially with a diluent self.gas.TPX = 300.0, ct.one_atm, 'AR:1.0' self.combustor = ct.IdealGasReactor(self.gas) # create a reservoir for the exhaust self.exhaust = ct.Reservoir(self.gas) # compute fuel and air mass flow rates factor = 0.1 oxidizer_mdot = 4 * factor * oxidizer_mw fuel_mdot = factor * fuel_mw # The igniter will use a time-dependent igniter mass flow rate. def igniter_mdot(t, t0=0.1, fwhm=0.05, amplitude=0.1): return amplitude * math.exp( -(t - t0)**2 * 4 * math.log(2) / fwhm**2) # create and install the mass flow controllers. Controllers # m1 and m2 provide constant mass flow rates, and m3 provides # a short Gaussian pulse only to ignite the mixture m1 = ct.MassFlowController(fuel_in, self.combustor, mdot=fuel_mdot) m2 = ct.MassFlowController(oxidizer_in, self.combustor, mdot=oxidizer_mdot) m3 = ct.MassFlowController(self.igniter, self.combustor, mdot=igniter_mdot) # put a valve on the exhaust line to regulate the pressure self.v = ct.Valve(self.combustor, self.exhaust, K=1.0) # the simulation only contains one reactor self.sim = ct.ReactorNet([self.combustor])
def run_single(self): gas=self.processor.solution reactorPressure=gas.P self.reactorPressure=self.processor.solution.P pressureValveCoefficient=self.pvalveCoefficient maxPressureRiseAllowed=self.maxPrise print(maxPressureRiseAllowed,self.reactorPressure,pressureValveCoefficient) #Build the system components for JSR fuelAirMixtureTank=ct.Reservoir(self.processor.solution) exhaust=ct.Reservoir(self.processor.solution) stirredReactor=ct.IdealGasReactor(self.processor.solution,energy=self.energycon,volume=self.reactor_volume) massFlowController=ct.MassFlowController(upstream=fuelAirMixtureTank, downstream=stirredReactor,mdot=stirredReactor.mass/self.residence_time) pressureRegulator=ct.Valve(upstream=stirredReactor,downstream=exhaust,K=pressureValveCoefficient) reactorNetwork=ct.ReactorNet([stirredReactor]) if bool(self.observables) and self.kineticSens==1: for i in range(gas.n_reactions): stirredReactor.add_sensitivity_reaction(i) if self.kineticSens and bool(self.observables)==False: #except: print('Please supply a non-empty list of observables for sensitivity analysis or set kinetic_sens=0') if self.physicalSens==1 and bool(self.observables)==False:
def setup_reactor(self): self.gas = ct.Solution(self.rxnmech) self.xinit = {"O2": 0.21, "N2": 0.79} self.gas.TPX = self.T0, self.p0, self.xinit self.injection_gas, _ = setup_injection_gas(self.rxnmech, self.fuel, pure_fuel=True) self.injection_gas.TP = self.T0, self.p0 fuel_res = ct.Reservoir(self.injection_gas) # Create the reactor object self.reactor = ct.Reactor(self.gas) self.rempty = ct.Reactor(self.gas) # Set the initial states of the reactor self.reactor.chemistry_enabled = True self.reactor.volume = self.history["V"][0] # Add in a fuel injector self.injector = ct.MassFlowController(fuel_res, self.reactor) # Add in a wall that moves according to piston velocity self.piston = ct.Wall( left=self.reactor, right=self.rempty, A=np.pi / 4.0 * self.bore**2, U=0.0, velocity=self.history["piston_velocity"][0], ) # Create the network object self.sim = ct.ReactorNet([self.reactor])
def solve(gas, t): # Set the temperature and pressure for gas # t is different from t0 gas.TPX = t, pressure, composition surf.TP = t, pressure TDY = gas.TDY cov = surf.coverages # create a new reactor gas.TDY = TDY r = ct.IdealGasReactor(gas, energy='on') r.volume = rvol upstream = ct.Reservoir(gas, name='upstream') downstream = ct.Reservoir(gas, name='downstream') rsurf = ct.ReactorSurface(surf, r, A=cat_area) m = ct.MassFlowController(upstream, r, mdot=mass_flow_rate) v = ct.PressureController(r, downstream, master=m, K=1e-5) sim = ct.ReactorNet([r]) sim.max_err_test_fails = 20 sim.rtol = 1.0e-9 sim.atol = 1.0e-21 # define time, space, and other information vectors z2 = (np.arange(NReactors)) * rlen * 1e3 t_r2 = np.zeros_like(z2) # residence time in each reactor t2 = np.zeros_like(z2) states2 = ct.SolutionArray(gas) for n in range(NReactors): # Set the state of the reservoir to match that of the previous reactor gas.TDY = r.thermo.TDY upstream.syncState() sim.reinitialize() sim.advance_to_steady_state() dist = n * rlen * 1.0e3 # distance in mm t_r2[n] = r.mass / mass_flow_rate # residence time in this reactor t2[n] = np.sum(t_r2) states2.append(gas.state) print('Temperature of Gas :', t, ' residence time :', t2[-1]) MolFrac_CH4 = states2('CH4').X MolFrac_CO2 = states2('CO2').X MolFrac_CO = states2('CO').X MolFrac_H2 = states2('H2').X MolFrac_H2O = states2('H2O').X MolFrac_O2 = states2('O2').X kq = np.zeros(6) kq[0] = MolFrac_CH4[-1] * 100 kq[1] = MolFrac_CO2[-1] * 100 kq[2] = MolFrac_CO[-1] * 100 kq[3] = MolFrac_H2[-1] * 100 kq[4] = MolFrac_H2O[-1] * 100 kq[5] = MolFrac_O2[-1] * 100 return kq
def test_mass_flow_controller(self): self.makeReactors(nReactors=1) gas2 = ct.Solution('h2o2.xml') gas2.TPX = 300, 10*101325, 'H2:1.0' reservoir = ct.Reservoir(gas2) mfc = ct.MassFlowController(reservoir, self.r1) mfc.setMassFlowRate(lambda t: 0.1 if 0.2 <= t < 1.2 else 0.0) self.assertEqual(len(reservoir.inlets), 0) self.assertEqual(len(reservoir.outlets), 1) self.assertEqual(reservoir.outlets[0], mfc) self.assertEqual(len(self.r1.outlets), 0) self.assertEqual(len(self.r1.inlets), 1) self.assertEqual(self.r1.inlets[0], mfc) ma = self.r1.volume * self.r1.density Ya = self.r1.Y self.net.advance(2.5) mb = self.r1.volume * self.r1.density Yb = self.r1.Y self.assertNear(ma + 0.1, mb) self.assertArrayNear(ma * Ya + 0.1 * gas2.Y, mb * Yb)
def runSinglePSR(T, P, moleFraction, factor, reactionIndex, targetSpc): start = time.time() # create the gas mixture gas = ct.Solution('HXD15_Battin_mech.xml') gas.TPX = T, P, moleFraction # create an upstream reservoir that will supply the reactor. The temperature, # pressure, and composition of the upstream reservoir are set to those of the # 'gas' object at the time the reservoir is created. upstream = ct.Reservoir(gas) # Now create the reactor object with the same initial state cstr = ct.IdealGasReactor(gas) # Set its volume to 80 cm^3. In this problem, the reactor volume is fixed, so # the initial volume is the volume at all later times. cstr.volume = 80.0*1.0e-6 # Connect the upstream reservoir to the reactor with a mass flow controller # (constant mdot). Set the mass flow rate. vdot = 40* 1.0e-6 # m^3/s mdot = gas.density * vdot # kg/s mfc = ct.MassFlowController(upstream, cstr, mdot=mdot) # now create a downstream reservoir to exhaust into. downstream = ct.Reservoir(gas) # connect the reactor to the downstream reservoir with a valve, and set the # coefficient sufficiently large to keep the reactor pressure close to the # downstream pressure. v = ct.Valve(cstr, downstream, K=1.0e-9) # create the network network = ct.ReactorNet([cstr]) # modify the A factor of the given reaction gas.set_multiplier(factor,reactionIndex-1) # now integrate in time t = 0.0 dt = 0.1 print "\n\n\n**************************\n***** Solving case %d *****\n"%(reactionIndex) while t < 30.0: print t t += dt network.advance(t) results = [] for spc in targetSpc: results.append(cstr.thermo[spc].X[0]*1.0e6) end = time.time() print 'Execution time is:' print end - start return [gas.reaction_equation(reactionIndex-1)] + results
def cstr0(self, F, P, V, T=None, dt=2.0, t=None, disp=0): # mix streams if there is more than one self, F = mixer(self, F) # set initial conditions self.TP = T, P # create a new reactor if T == None or T == 0: r = ct.IdealGasReactor(self, energy='on') else: r = ct.IdealGasReactor(self, energy='off') r.volume = V # create up and downstream reservoirs upstream = ct.Reservoir(self) downstream = ct.Reservoir(self) # set mass flow into reactor m = ct.MassFlowController(upstream, r, mdot=F) # set valve to hold pressure constant ct.PressureController(r, downstream, master=m, K=1e-5) # create reactor network s = ct.ReactorNet([r]) s.rtol, s.atol, s.max_err_test_fails = rel_tol, abs_tol, test_fails # set parameters time = 0 residual = 1 all_done = False # forces reinitialization s.set_initial_time(0) while not all_done: Yo = r.thermo.Y To = r.thermo.T try: time += dt s.advance(time) except: dt *= 0.1 time += dt s.advance(time) if t != None and time >= t: all_done = False if time > 10 * dt: all_done = True residual = slope(Yo, r.thermo.Y, dt) if T == None: residual = np.max(np.append(residual,slope(To,r.thermo.T,dt))) if residual > rel_tol: all_done = False break if disp == True: print 'Residual: %1.3e' %(residual) return self, time, residual
def psr_ss(soln_in, soln_out, p, T0, T, X0, X, tau): #sys.stdout.flush() """ find the steady state of a PSR :param mech: mechanism :param p: pressure (Pa) :param T0: temperature (K) of the inlet flow :param T: initial temperature (K) of the reactor :param tau: residence time (s) of the reactor :return reactor: """ vol = 1.0 # unit: m3 K = 1.0 t_end = tau * 100.0 #print 't_end = '+str(t_end) soln_out.TPX = T, p, X soln_in.TPX = T0, p, X0 inlet = ct.Reservoir(soln_in) reactor = ct.IdealGasReactor(soln_out) reactor.volume = vol vdot = vol/tau mdot = soln_out.density * vdot mfc = ct.MassFlowController(inlet, reactor, mdot=mdot) exhaust = ct.Reservoir(soln_out) valve = ct.Valve(reactor, exhaust, K=K) network = ct.ReactorNet([reactor]) try: network.advance(t_end) except RuntimeError as e: print '@'*10+'\nct.exceptions = \n'+str(e) #sys.exit() return None return soln_out
def test_pressure_controller_errors(self): self.make_reactors() res = ct.Reservoir(self.gas1) mfc = ct.MassFlowController(res, self.r1, mdot=0.6) p = ct.PressureController(self.r1, self.r2, master=mfc, K=0.5) with self.assertRaises(ct.CanteraError): p = ct.PressureController(self.r1, self.r2, K=0.5) p.mdot(0.0) with self.assertRaises(ct.CanteraError): p = ct.PressureController(self.r1, self.r2, master=mfc) p.mdot(0.0) with self.assertRaises(ct.CanteraError): p = ct.PressureController(self.r1, self.r2) p.mdot(0.0)
def test_pressure_controller(self): self.makeReactors(nReactors=1) g = ct.Solution('h2o2.xml') g.TPX = 500, 2*101325, 'H2:1.0' inletReservoir = ct.Reservoir(g) g.TP = 300, 101325 outletReservoir = ct.Reservoir(g) mfc = ct.MassFlowController(inletReservoir, self.r1) mdot = lambda t: np.exp(-100*(t-0.5)**2) mfc.setMassFlowRate(mdot) pc = ct.PressureController(self.r1, outletReservoir) pc.setMaster(mfc) pc.setPressureCoeff(1e-5) t = 0 while t < 1.0: t = self.net.step(1.0) self.assertNear(mdot(t), mfc.mdot(t)) dP = self.r1.thermo.P - outletReservoir.thermo.P self.assertNear(mdot(t) + 1e-5 * dP, pc.mdot(t))
def run(self,): gas=processor.solution.P reactorPressure=gas pressureValveCoefficient=pvalveCoefficient maxPressureRiseAllowed=maxPrise #Build the system components for JSR fuelAirMixtureTank=ct.Reservoir(gas) exhaust=ct.Reservoir(gas) stirredReactor=ct.IdealGasReactor(gas,energy=energycon,volume=volume) massFlowController=ct.MassFlowController(upstream=fuelAirMixtureTank, downstream=stirredReactor,mdot=stirredReactor.mass/restime) pressureRegulator=ct.Valve(upstream=stirredReactor,downstream=exhaust,K=pressureValveCoefficient) reactorNetwork=ct.ReactorNet([stirredReactor]) if bool(self.moleFractionObservables) and self.kineticSens==1: for i in range(gas.n_reactions): stirredReactor.add_sensitivity_reaction(i) if self.kineticSens and bool(self.moleFractionObservables)==False: except:
# temperature. env = ct.Reservoir(gas) # Create a heat-conducting wall between the reactor and the environment. Set its # area, and its overall heat transfer coefficient. Larger U causes the reactor # to be closer to isothermal. If U is too small, the gas ignites, and the # temperature spikes and stays high. w = ct.Wall(cstr, env, A=1.0, U=0.02) # Connect the upstream reservoir to the reactor with a mass flow controller # (constant mdot). Set the mass flow rate to 1.25 sccm. sccm = 1.25 vdot = sccm * 1.0e-6 / 60.0 * ( (ct.one_atm / gas.P) * (gas.T / 273.15)) # m^3/s mdot = gas.density * vdot # kg/s mfc = ct.MassFlowController(upstream, cstr, mdot=mdot) # now create a downstream reservoir to exhaust into. downstream = ct.Reservoir(gas) # connect the reactor to the downstream reservoir with a valve, and set the # coefficient sufficiently large to keep the reactor pressure close to the # downstream pressure of 60 Torr. v = ct.Valve(cstr, downstream, K=1.0e-9) # create the network network = ct.ReactorNet([cstr]) # In[3]: # now integrate in time
def runSinglePFR(T_0, pressure, composition_0, length, v_0, d, tempProfile, factor, reactionIndex): start = time.time() # input file containing the reaction mechanism reaction_mechanism = 'HXD15_Battin_mech.xml' # import the gas model and set the initial conditions gas2 = ct.Solution(reaction_mechanism) gas2.TPX = T_0, pressure, composition_0 mass_flow_rate2 = v_0 * gas2.density * (1.01325e5 / pressure) * (T_0 / 298.15) # Resolution: The PFR will be simulated by 'n_steps' time steps or by a chain # of 'n_steps' stirred reactors. n_steps = 100 area = math.pi * d * d / 4 dz = length / n_steps r_vol = area * dz # create a new reactor r2 = ct.IdealGasReactor(gas2) r2.volume = r_vol # create a reservoir to represent the reactor immediately upstream. Note # that the gas object is set already to the state of the upstream reactor upstream = ct.Reservoir(gas2, name='upstream') # create a reservoir for the reactor to exhaust into. The composition of # this reservoir is irrelevant. downstream = ct.Reservoir(gas2, name='downstream') # The mass flow rate into the reactor will be fixed by using a # MassFlowController object. m = ct.MassFlowController(upstream, r2, mdot=mass_flow_rate2) # We need an outlet to the downstream reservoir. This will determine the # pressure in the reactor. The value of K will only affect the transient # pressure difference. v = ct.PressureController(r2, downstream, master=m, K=1.0e-9) sim2 = ct.ReactorNet([r2]) from scipy.interpolate import UnivariateSpline [zz, tem] = readTemperatureProfile(tempProfile) para = UnivariateSpline(zz, tem) # modify the A factor of the given reaction gas2.set_multiplier(factor, reactionIndex - 1) # iterate through the PFR cells # define time, space, and other information vectors # z2 = (np.arange(n_steps+1) + 1) * dz # t_r2 = np.zeros_like(z2) # residence time in each reactor # u2 = np.zeros_like(z2) # t2 = np.zeros_like(z2) for n in range(n_steps + 1): print n position = dz * float(n) # Set the state of the reservoir to match that of the previous reactor gas2.TDY = para(position), r2.thermo.density, r2.thermo.Y upstream.syncState() sim2.reinitialize() # integrate the reactor forward in time until steady state is reached sim2.advance_to_steady_state() # compute velocity and transform into time # u2[n] = mass_flow_rate2 / area / r2.thermo.density # t_r2[n] = r2.mass / mass_flow_rate2 # residence time in this reactor # t2[n] = np.sum(t_r2) # write output data # print position, r2.thermo.T, r2.thermo.P, u2[n], t2[n], r2.thermo['C3H6'].X[0] # moleFractionC3H6.append([dz*n, u2[n], t2[n], r2.thermo.T, r2.thermo['C3H6'].X[0]]) end = time.time() print 'Done! Execution time is:' print end - start return [ gas2.reaction_equation(reactionIndex - 1), r2.thermo['C3H6'].X[0] * 1.0e6 ]
r1.Y residence_time = 0.8e-4 def mdot_out(t): return r1.mass / residence_time #Poner una válvula a la entrada del reactor r1 para mantener P=cte y evitar que se #devuelva el flujo. Véase help(ct.Valve) #Nótese el uso del objeto upstream que es un tipo Reservoir devinido arriba inlet = ct.Valve(upstream, r1, K=100) #Pone un objeto MassFlowController a la salidad de r1 que mantiene el flujo másico #constante, véase help(ct.MassFlowController) #Nótese el uso del objeto downstream que es un tipo Reservoir devinido arriba outlet = ct.MassFlowController(r1, downstream) #fija el flujo másico a la salida del reactor outlet.set_mass_flow_rate(mdot_out) net = ct.ReactorNet([r1]) #Creo los "pasos de tiempo" en los que quiero ver el progreso de la solución #El solver de cantera resuelve de forma automática y escoge su paso de tiempo de #forma automática así t sea 2, 20 o 2000 puntos t = np.linspace(0, residence_time, 2) # #¿Por qué el código original de Ray multiplica por 5 el tiempo de residencia? #t = np.linspace(0, 5*residence_time, 2) # #Vector donde guardo las temperaturas T = np.zeros(t.shape)
gas.set_equivalence_ratio(0.30, 'c12h26', 'n2:3.76, o2:1.0') gas.equilibrate('TP') r = ct.IdealGasReactor(gas) r.volume = 0.001 # 1 liter def fuel_mdot(t): """Create an inlet for the fuel, supplied as a Gaussian pulse""" total = 3.0e-3 # mass of fuel [kg] width = 0.5 # width of the pulse [s] t0 = 2.0 # time of fuel pulse peak [s] amplitude = total / (width * np.sqrt(2 * np.pi)) return amplitude * np.exp(-(t - t0)**2 / (2 * width**2)) mfc = ct.MassFlowController(inlet, r, mdot=fuel_mdot) # Create the reactor network sim = ct.ReactorNet([r]) # Integrate for 10 seconds, storing the results for later plotting tfinal = 10.0 tnow = 0.0 i = 0 tprev = tnow states = ct.SolutionArray(gas, extra=['t']) while tnow < tfinal: tnow = sim.step() i += 1 # Storing results after every step can be excessive. Instead, store results
def simulatePFRorTPRwithCantera(model_name, canteraGasPhaseObject, canteraSurfacePhaseObject, simulation_settings_module): #simulation_settings_module_name must be a string if it is provided. The module itself should not be passed as an argument. This is intentional. canteraPhases ={} gas = canteraGasPhaseObject surf = canteraSurfacePhaseObject canteraPhases['gas'] = gas canteraPhases['surf'] = surf '''Now the code that handles *either* isothermal PFR or surface TPR. In future, will allow PFR TPR as well''' #We are going to model things as a flow reactor made of CSTRs, with no flow for the surface TPR case, following this example: https://cantera.org/examples/python/reactors/surf_pfr.py.html #it could probably also have been done as a flow reactor with no flow: https://cantera.org/examples/python/surface_chemistry/catalytic_combustion.py.html '''START OF settings that users should change.''' flow_type = simulation_settings_module.flow_type T_gas_feed = simulation_settings_module.T_gas_feed T_surf = simulation_settings_module.T_surf velocity = simulation_settings_module.velocity reactor_cross_area = simulation_settings_module.reactor_cross_area cat_area_per_vol = simulation_settings_module.cat_area_per_vol porosity = simulation_settings_module.porosity length = simulation_settings_module.length P_gas = simulation_settings_module.P_gas gas_composition = simulation_settings_module.gas_composition t_step_size = simulation_settings_module.t_step_size t_final = simulation_settings_module.t_final NReactors = simulation_settings_module.NReactors print_frequency = simulation_settings_module.print_frequency heating_rate = simulation_settings_module.heating_rate surface_coverages = simulation_settings_module.surface_coverages rtol = simulation_settings_module.rtol atol = simulation_settings_module.atol exportOutputs = simulation_settings_module.exportOutputs '''END OF settings that users should change.''' #Initiate concentrations output file and headers. if exportOutputs == True: concentrations_output_filename = model_name + "_output_concentrations.csv" outfile = open(concentrations_output_filename,'w') writer = csv.writer(outfile) concentrationsArrayHeaderList = ['Distance (m)', 'time(s)', 'T_gas (K)', 'T_surf (K)', 'P (atm)'] + \ gas.species_names + surf.species_names concentrationsArrayHeader = str(concentrationsArrayHeaderList)[1:-1] #The species names were imported when "surf" and "gas" objects were created. if exportOutputs == True: writer.writerow(concentrationsArrayHeaderList) if flow_type == "Static": velocity = 0 NReactors = 2 #In the surf_pfr example. For static, we only need 1 reactor, but the rlen formula below has a minimum value of 2. #FIXME num_t_steps = ceil(t_final / t_step_size) #rounds up. rlen = length/(NReactors-1) #This is each individual CSTR's length. #FIXME: Why is this not just Nreactors? Is it because of reservoirs?... #rvol is the reactor volume. We're modeling this as as a single CSTR with no flow. rvol = reactor_cross_area * rlen * porosity #Each individual CSTR gas volume reduced by porosity. # catalyst area in one reactor cat_area = cat_area_per_vol * rvol #Set the initial conditions for the gas and the surface. gas.TPX = T_gas_feed, P_gas, gas_composition surf.TP = T_surf, P_gas surf.X = surface_coverages mass_flow_rate = velocity * reactor_cross_area * gas.density #linear velocity times area is volume/s, times kg/vol becomes kg/s. I think that for this example we neglect effects of surface adsorption regarding how much mass is in the gas phase? TDY = gas.TDY #Get/Set temperature [K] and density [kg/m^3 or kmol/m^3], and mass fractions. From: https://cantera.org/documentation/docs-2.4/sphinx/html/cython/thermo.html cov = surf.coverages #This is normalized coverages, built in from InerfacePhase class: https://cantera.github.io/docs/sphinx/html/cython/thermo.html#cantera.InterfacePhase #It would also be possible to type surface.site_density, giving [kmol/m^2] for surface phases. Also possible to use surf.set_unnormalized_coverages(self, cov) for cases when don't want to use normalized coverages. # create a new reactor gas.TDY = TDY #<-- If TDY was changing, and if original gas wanted to be kept, this could have been gas = copy.deepcopy(gas)? reactor = ct.IdealGasReactor(gas, energy='off') reactor.volume = rvol # create a reservoir to represent the reactor immediately upstream. Note # that the gas object is set already to the state of the upstream reactor upstream_of_CSTR = ct.Reservoir(gas, name='upstream') #A cantera reservoir never changes no matter what goes in/out: https://cantera.org/documentation/docs-2.4/sphinx/html/cython/zerodim.html#cantera.Reservoir # create a reservoir for the reactor to exhaust into. The composition of # this reservoir is irrelevant. downstream_of_CSTR = ct.Reservoir(gas, name='downstream') #A cantera reservoir never changes no matter what goes in/out: https://cantera.org/documentation/docs-2.4/sphinx/html/cython/zerodim.html#cantera.Reservoir #Note: these are reservoirs for individual CSTRs. It's used in a clever way in this example, as will be seen later. #I would prefer to call these reservoirs "feed" and "exhaust", and would consider to fill the downstream/exhaust with inert to make it clear that it is different. Or maybe make deep copies of gas called "feed_gas" and "exhaust_gas". # Add the reacting surface to the reactor. The area is set to the desired # catalyst area in the reactor. rsurf = ct.ReactorSurface(surf, reactor, A=cat_area) #Here is where the catalyst site density gets used. Note that cat_area is actually in meters now, even though site_density is defined as mol/cm^2 in the cti file. # The mass flow rate into the reactor will be fixed by using a # MassFlowController object. f_mfr = ct.MassFlowController(upstream_of_CSTR, reactor, mdot=mass_flow_rate) #mass flow controller makes flow that goes from the first argument to the second argument with mdot providing "The mass flow rate [kg/s] through this device at time t [s]."s # We need an outlet to the downstream reservoir. This will determine the # pressure in the reactor. The value of K will only affect the transient # pressure difference. e_mfr = ct.PressureController(reactor, downstream_of_CSTR, master=f_mfr, K=1e-5) #This makes flow that goes from first argument to second argument with the mass flow controller "f_mfr" controlling the pressure here. K has units of kg/s/Pa times the pressure difference. this "v" that comes out is in same units as the mass flow rate, it's in kg/s. https://cantera.org/documentation/docs-2.4/sphinx/html/cython/zerodim.html#flowdevice e_mfr is the exhaust mass flow rate. sim = ct.ReactorNet([reactor]) #This is normally a list of reactors that are coupled. sim.max_err_test_fails = 12 #Even after looking at docs, I'm not sure what this does. I think this is how many relative and absolute tolerance failures there can be in a single time step before the resolution of the ODE integrator does that step again with finer resolution? https://cantera.org/documentation/docs-2.4/sphinx/html/cython/zerodim.html#reactor-networks #Static versus PFR flag # set relative and absolute tolerances on the simulation sim.rtol = rtol sim.atol = atol gas_rates = [] #NOTE: These are just the rates from the surface phase. These are *not* including any homogeneous rates. surface_rates = [] #NOTE: These are just the rates from the surface phase. These are *not* including any homogeneous rates. sim_times = [] #NOTE: This is less useful if one uses advance_to_steady_state sim_dist = [] #This is the distance in the reactor. If one is using flow and advance_to_steady_state, then this is representative of the kinetics. concentrationsArray = [] #Print some things out for before the simulation, these are basically headers. if print_frequency != None: print(concentrationsArrayHeader) if flow_type == "PFR": for n in range(NReactors): #iterate across the CSTRs. # Set the state of the reservoir to match that of the previous reactor gas.TDY = reactor.thermo.TDY #<-- setting gas to *most recent* state of the reactor. upstream_of_CSTR.syncState() #<-- this is an interesting trick. Once one CSTR has been simulated, we set the current values (the one from the just simulated CSTR) to be upstream value. This works because we're doing implicit ODE down a reactor length, and probably only gives the right answer for steady state kinetics. It also only works because we don't care what's happening downstream. We have a fixed K for the e_mfr, and also f_mfr. sim.reinitialize() sim.advance_to_steady_state() #<-- we advance the current CSTR to steady state before going to the next one. For a tpr, we'd probably use advance(self, double t) instead. The syntax would be something like sim.advance(10.0) for 10 seconds. https://cantera.github.io/docs/sphinx/html/cython/examples/reactors_reactor1.html#py-example-reactor1-py dist = n * rlen # distance in m <-- this could have been defined above, and I would have... but it's probably good to have it down here to make it clear that only 'n" is being used rather than this value, to simulate the reactor. sim_dist.append(dist) sim_times.append(sim.time) gas_rates.append(surf.get_net_production_rates('gas')) surface_rates.append(surf.get_net_production_rates('surf')) # write the gas mole fractions and surface coverages vs. distance rowListOfStrings = [dist, sim.time, gas.T, surf.T, reactor.thermo.P/ct.one_atm] + \ list(gas.concentrations) + list(surf.coverages) if exportOutputs == True: writer.writerow(rowListOfStrings) concentrationsArray.append(np.array(rowListOfStrings)) if print_frequency != None: if not n % print_frequency: #This only prints every specified number of steps. try: with np.printoptions(precision=3): print(np.array(rowListOfStrings)) except: print(np.array(rowListOfStrings)) if flow_type == "Static": T_surf_0 = T_surf #sim.set_max_time_step(t_step_size) #Cantera's sim.step normally advances in a variable way. for i_step in range(num_t_steps): #iterate across the CSTRs. time = i_step*t_step_size T_surf = T_surf_0 + time*heating_rate surf.TP = T_surf, P_gas if simulation_settings_module.piecewise_coverage_dependence == True: modified_reactions_parameters_array = canteraKineticsParametersParser.calculatePiecewiseCoverageDependentModifiedParametersArray(simulation_settings_module, surf.species_names, surf.coverages) #This feature requires the piecewise coverage dependence settings AND the reactions_parameters_array to already be inside the surf object **in advance** canteraKineticsParametersParser.modifyReactionsInOnePhase(surf, modified_reactions_parameters_array, ArrheniusOnly=True) #TODO: Right now this is constrainted to Arrhenius only because cantera does not yet allow modifyReactionsInOnePhase to do more (but it's planned to change in developers roadmap) surf.advance_coverages(t_step_size) #sim.advance(time) would not work. Changing T with time is not officially supported but happens to work with surf.advance_coverages. Supported way to change temperature during simulation for arbitrary reactors is to use custom integrator: https://cantera.org/examples/python/reactors/custom.py.html dist = 0.0 sim_dist.append(dist) sim_times.append(time) gas_rates.append(surf.get_net_production_rates('gas')) surface_rates.append(surf.get_net_production_rates('surf')) rowListOfStrings = [dist, time, gas.T, surf.T, reactor.thermo.P/ct.one_atm] + \ list(gas.X) + list(surf.coverages) if exportOutputs == True: writer.writerow(rowListOfStrings) concentrationsArray.append(np.array(rowListOfStrings)) if print_frequency != None: if not i_step % print_frequency: #This only prints every specified number of steps. try: with np.printoptions(precision=3): print(np.array(rowListOfStrings)) except: print(np.array(rowListOfStrings)) #Need to get things into a stackable state. Figured out from numpy shape that I needed to do at_least2D and transpose. sim_dist = np.atleast_2d(sim_dist).transpose() sim_times = np.atleast_2d(sim_times).transpose() gas_rates = np.array(gas_rates) surface_rates = np.array(surface_rates) gasRatesArray = np.hstack((sim_dist,sim_times, gas_rates)) surfaceRatesArray = np.hstack((sim_dist,sim_times, surface_rates)) rates_all_array = np.hstack((sim_dist,sim_times, gas_rates, surface_rates)) concentrationsArray = np.array(concentrationsArray) concentrationsArrayHeader = concentrationsArrayHeader gasRatesArrayHeader = 'dist(m), time(s),'+str(gas.species_names).replace("'","")[1:-1] surfaceRatesArrayHeader = 'dist(m),time(s),'+str(surf.species_names).replace("'","")[1:-1] rates_all_array_header = 'dist(m),time(s),'+str(gas.species_names).replace("'","")[1:-1]+"," + str(surf.species_names).replace("'","")[1:-1] canteraSimulationsObject = sim cantera_phase_rates = {"gas":gasRatesArray, "surf":surfaceRatesArray} cantera_phase_rates_headers = {"gas":gasRatesArrayHeader, "surf":surfaceRatesArrayHeader} if exportOutputs == True: np.savetxt(model_name + "_output_rates_all.csv", rates_all_array, delimiter=",", comments = '', header = rates_all_array_header) np.savetxt(model_name + "_output_rates_gas.csv", gasRatesArray, delimiter=",", comments = '', header = gasRatesArrayHeader ) np.savetxt(model_name + "_output_rates_surf.csv", surfaceRatesArray, delimiter=",", comments = '', header = surfaceRatesArrayHeader) if exportOutputs == True: outfile.close() return concentrationsArray, concentrationsArrayHeader, rates_all_array, rates_all_array_header, cantera_phase_rates, canteraPhases, cantera_phase_rates_headers, canteraSimulationsObject
#mixer: filled initially with nitrogen fuel2.TPX= 700.0,10.3e5,'N2:1' mixer2=ct.IdealGasReactor(fuel2, energy='on') mixer2.volume=10e-6 # Volume Computed as per experimental dimensions in m3 combustor=ct.IdealGasReactor(fuel2,energy='on') combustor.volume=71.136e-6 # Experimental Dimensions in m3 exhaust2=ct.Reservoir(fuel2) # Exhaust Reservoir eq_ratio2=1 # Equivalence Ratio # mdot of oxidiser - 1.4 kg/s according to the experiment fuel_pipe2= ct.MassFlowController(NG_inlet2, mixer2, mdot=1.4*eq_ratio2) oxy_pipe2=ct.MassFlowController(oxy_inlet2,mixer2,mdot=1.4) # Igniter modeled as pulsed radicals of Hydrogen fwhm=0.2 amp=0.1 t0=1 igniter_mdot= lambda t:amp*math.exp(-(t-t0)**2*4*math.log(2)/fwhm**2) ignite_pipe=ct.MassFlowController(igniter,combustor,mdot=igniter_mdot) # The premixed reactants have to enter the combustor mixer2combustor=ct.MassFlowController(mixer2,combustor) #exhaust valve
combustor2.volume = 1.0 # Create a reservoir for the exhaust exhaust1 = ct.Reservoir(gas1) #gas1 exhaust2 = ct.Reservoir(gas2) #gas2 # Use a variable mass flow rate to keep the residence time in the reactor # constant (residence_time = mass / mass_flow_rate). The mass flow rate function # can access variables defined in the calling scope, including state variables # of the Reactor object (combustor) itself. #gas1 def mdot(t): return combustor1.mass / residence_time1 inlet1_mfc = ct.MassFlowController(inlet1, combustor1, mdot=mdot) #gas2 def mdot(t): return combustor2.mass / residence_time2 inlet2_mfc = ct.MassFlowController(inlet2, combustor2, mdot=mdot) # A PressureController has a baseline mass flow rate matching the 'master' # MassFlowController, with an additional pressure-dependent term. By explicitly # including the upstream mass flow rate, the pressure is kept constant without # needing to use a large value for 'K', which can introduce undesired stiffness. outlet1_mfc = ct.PressureController(combustor1, exhaust1, master=inlet1_mfc, K=0.01) #gas1 outlet2_mfc = ct.PressureController(combustor2, exhaust2, master=inlet2_mfc, K=0.01) #gas2 # the simulation
def execute(): #============================================================================== # set up the cantera gases and reactors #============================================================================== drop = droplet(50e-6, 'A2', 300, 1) #drop is defined only to use the drop properties #environment air gas_env = ct.Solution('a2.cti') gas_env.TPX = 300, ct.one_atm, 'O2:29,N2:71' gas_fuel = ct.Solution('a2.cti') gas_fuel.TPX = drop.ABP, ct.one_atm, 'POSF10325:1' #gaseous fuel at average boiling temperature gas_kernel = ct.Solution('a2.cti') gas_kernel.TPX = 4000, ct.one_atm, 'O2:29,N2:71' gas_kernel.equilibrate('HP') res_air = ct.Reservoir(gas_env) res_fuel = ct.Reservoir(gas_fuel) kernel = ct.IdealGasConstPressureReactor(gas_kernel) kernel.volume = 2.4e-8 #m^3 mfc1 = ct.MassFlowController(res_air, kernel) #connect air reservoir to kernel mfc2 = ct.MassFlowController(res_fuel, kernel) #connect fuel reservoir to kernel mfc1.set_mass_flow_rate(6e-5) mfc2.set_mass_flow_rate(0) w = ct.Wall(kernel, res_fuel) w.set_heat_flux(0) w.expansion_rate_coeff = 1e8 net = ct.ReactorNet({kernel}) net.atol = 1e-10 #absolute tolerance net.rtol = 1e-10 #relative tolerance #============================================================================== # endtime, time steps, and data to be stored #============================================================================== dt = 1e-6 #change time step for advancing the reactor endtime = 1000e-6 mdot_fuel = 1.98e-6 #this number should be calculated based on equivalence ratio, kg/s droplets = [] T = [] time = [] m = [] m_dot = [] q_dot = [] droplet_count = [] X_fuel = [] X_O = [] X_CO2 = [] num_d = [] #============================================================================== # advance reaction #============================================================================== print("Running simulation") for i in range(1,int(endtime/dt)): print (int(endtime/dt) - i) #entrain droplets every 1 microsecond droplets.extend(entrainer(400, 5.*mdot_fuel)) mdot, qdot = vaporize(gas_kernel.T, droplets, dt) # print(gas_kernel.T, mdot, qdot, len(droplets)) # print(str(mdot) + str(qdot) + str(gas_kernel.T)+' '+str(len(droplets))) mfc2.set_mass_flow_rate(mdot) w.set_heat_flux(qdot) #heat required to heat up the droplets during that time step net.advance(i*dt) num_d.append(len(droplets)) #storing variables T.append(gas_kernel.T) time.append(net.time) m.append(kernel.mass) m_dot.append(mdot) q_dot.append(qdot) droplet_count.append(len(droplets)) if gas_kernel.X[0] < 0: gas_kernel.X[0] = 0 X_fuel.append(gas_kernel.X[gas_kernel.species_index("POSF10325")]) X_O.append(gas_kernel.X[gas_kernel.species_index("O")]) X_CO2.append(gas_kernel.X[gas_kernel.species_index("CO2")]) #============================================================================== # plotting important information #============================================================================== plt.plot([t*1e3 for t in time],T, linewidth=3) plt.xlabel('time(ms)', FontSize=15) plt.ylabel('Temperature (K)', FontSize=15) plt.show() fig, ax1 = plt.subplots() ax2 = ax1.twinx() ax1.plot([t*1e3 for t in time], X_fuel, color = 'red', linewidth = 3) ax1.set_xlabel('time (ms)', FontSize=15) ax1.set_ylabel('X_fuel', color='red', FontSize=15) # ax2.plot([t*1e3 for t in time], X_O, color = 'green', linewidth = 3) ax2.set_ylabel('X_CO2', color='green', FontSize=15) ax2.plot([t*1e3 for t in time], X_CO2, color = 'blue', linewidth = 3) plt.show() fig, ax1 = plt.subplots() ax2 = ax1.twinx() ax1.plot([t*1e3 for t in time], m_dot, color = 'red', linewidth = 3) ax1.set_xlabel('time (ms)', FontSize=15) ax1.set_ylabel('m_dot', color='red', FontSize=15) ax2.plot([t*1e3 for t in time], q_dot, color = 'green', linewidth = 3) ax2.set_ylabel('q_dot', color='green', FontSize=15) plt.show() fig,ax1 = plt.subplots() ax1.plot([t*1e3 for t in time], num_d, linewidth=3) ax1.set_xlabel('time(ms)', FontSize=15) ax1.set_ylabel('#droplets', FontSize=15) plt.show()
def __init__(self): """Constructor for the SimEnv class. Call SimEnv(...) to create a SimEnv object. Arguments: None """ # super(SimEnv) returns the superclass, in this case gym.Env. Construct a gym.Env instance # gym.Env.__init__(self) # EzPickle.__init__(self) self.dt = DT self.age = 0 self.steps_taken = 0 self.reward = 0 # Create main burner objects self.main_burner_gas = ct.Solution(MECH) self.main_burner_gas.TPX = main_burner_reactor.thermo.TPX self.main_burner_reactor = ct.ConstPressureReactor( self.main_burner_gas) self.main_burner_reservoir = ct.Reservoir( contents=self.main_burner_gas) self.remaining_main_burner_mass = M_air_main + M_fuel_main # Create secondary stage objects self.sec_fuel = ct.Solution(MECH) self.sec_fuel.TPX = T_FUEL, P, {'CH4': 1.0} self.sec_fuel_reservoir = ct.Reservoir(contents=self.sec_fuel) self.sec_fuel_remaining = M_fuel_sec self.sec_air = ct.Solution(MECH) self.sec_air.TPX = T_AIR, P, {'N2': 0.79, 'O2': 0.21} self.sec_air_reservoir = ct.Reservoir(contents=self.sec_air) self.sec_air_remaining = M_air_sec # Create simulation network model self.sec_stage_gas = ct.Solution(MECH) self.sec_stage_gas.TPX = 300, P, {'AR': 1.0} self.sec_stage_reactor = ct.ConstPressureReactor(self.sec_stage_gas) self.sec_stage_reactor.volume = 1e-8 # second stage starts off small, and grows as mass is entrained self.network = ct.ReactorNet( [self.main_burner_reactor, self.sec_stage_reactor]) # Create main and secondary mass flow controllers and connect them to the secondary stage self.mfc_main = ct.MassFlowController(self.main_burner_reservoir, self.sec_stage_reactor) self.mfc_main.set_mass_flow_rate(0) # zero initial mass flow rate self.mfc_air_sec = ct.MassFlowController(self.sec_air_reservoir, self.sec_stage_reactor) self.mfc_air_sec.set_mass_flow_rate(0) self.mfc_fuel_sec = ct.MassFlowController(self.sec_fuel_reservoir, self.sec_stage_reactor) self.mfc_fuel_sec.set_mass_flow_rate(0) # Define action and observation spaces; must be gym.spaces # we're controlling three things: mdot_main, mdot_fuel_sec, mdot_air_sec #TODO: check to see if chosen tau_ent and self.action_space.high definition allow for complete entrainment of mass self.action_space = spaces.Box( low=np.array([0, 0, 0]), high=np.array([ self.remaining_main_burner_mass / tau_ent_main, self.sec_fuel_remaining / tau_ent_sec, self.sec_air_remaining / tau_ent_sec ]), dtype=np.float32) low = np.array([0] * 55 + [-np.finfo(np.float32).max] * 53) high = np.array([3000, 10] + [1] * 53 + [np.finfo(np.float64).max] * 53) num_cols = len(self.sec_stage_gas.state) + \ len(self.sec_stage_gas.net_production_rates) self.observation_space = spaces.Box(low=np.tile(low, (10, 1)), high=np.tile(high, (10, 1)), dtype=np.float64) self.observation_array = self._next_observation(init=True) # Reward variables for T, NO, and CO self.reward_T = 0 self.reward_NO = 0 self.reward_CO = 0
# create a reservoir for the exhaust exhaust = ct.Reservoir(gas) # lean combustion, phi = 0.5 equiv_ratio = 0.5 # compute fuel and air mass flow rates factor = 0.1 air_mdot = factor * 9.52 * air_mw fuel_mdot = factor * equiv_ratio * fuel_mw # create and install the mass flow controllers. Controllers m1 and m2 provide # constant mass flow rates, and m3 provides a short Gaussian pulse only to # ignite the mixture m1 = ct.MassFlowController(fuel_in, combustor, mdot=fuel_mdot) # note that this connects two reactors with different reaction mechanisms and # different numbers of species. Downstream and upstream species are matched by # name. m2 = ct.MassFlowController(air_in, combustor, mdot=air_mdot) # The igniter will use a Gaussian time-dependent mass flow rate. fwhm = 0.2 amplitude = 0.1 t0 = 1.0 igniter_mdot = lambda t: amplitude * math.exp(-(t - t0)**2 * 4 * math.log(2) / fwhm**2) m3 = ct.MassFlowController(igniter, combustor, mdot=igniter_mdot) # put a valve on the exhaust line to regulate the pressure
# create a new reactor r2 = ct.IdealGasReactor(gas2) r2.volume = r_vol # create a reservoir to represent the reactor immediately upstream. Note # that the gas object is set already to the state of the upstream reactor upstream = ct.Reservoir(gas2, name='upstream') # create a reservoir for the reactor to exhaust into. The composition of # this reservoir is irrelevant. downstream = ct.Reservoir(gas2, name='downstream') # The mass flow rate into the reactor will be fixed by using a # MassFlowController object. m = ct.MassFlowController(upstream, r2, mdot=mass_flow_rate2) # We need an outlet to the downstream reservoir. This will determine the # pressure in the reactor. The value of K will only affect the transient # pressure difference. v = ct.PressureController(r2, downstream, master=m, K=1e-5) sim2 = ct.ReactorNet([r2]) # define time, space, and other information vectors z2 = (np.arange(n_steps) + 1) * dz t_r2 = np.zeros_like(z2) # residence time in each reactor u2 = np.zeros_like(z2) t2 = np.zeros_like(z2) states2 = ct.SolutionArray(r2.thermo) # iterate through the PFR cells
def monolith_simulation(path_to_cti, temp, mol_in, verbose=False, sens=False): """ Set up and solve the monolith reactor simulation. Verbose prints out values as you go along Sens is for sensitivity, in the form [perturbation, reaction #] Args: path_to_cti: full path to the cti file temp (float): The temperature in Kelvin mol_in (3-tuple or iterable): the inlet molar ratios of (CH4, O2, Ar) verbose (Boolean): whether to print intermediate results sens (False or 2-tuple/list): if not False, then should be a 2-tuple or list [dk, rxn] in which dk = relative change (eg. 0.01) and rxn = the index of the surface reaction rate to change Returns: gas_out, # gas molar flow rate in moles/minute surf_out, # surface mole fractions gas_names, # gas species names surf_names, # surface species names dist_array, # distances (in mm) T_array # temperatures (in K) """ sols_dict = setup_ct_solution(path_to_cti) gas, surf, i_ar, n_surf_reactions = sols_dict['gas'], sols_dict[ 'surf'], sols_dict['i_ar'], sols_dict['n_surf_reactions'] print( f"Running monolith simulation with CH4 and O2 concs {mol_in[0], mol_in[1]} on thread {threading.get_ident()}" ) ch4, o2, ar = mol_in ratio = ch4 / (2 * o2) X = f"CH4(2):{ch4}, O2(3):{o2}, Ar:{ar}" gas.TPX = 273.15, ct.one_atm, X # need to initialize mass flow rate at STP # mass_flow_rate = velocity * gas.density_mass * area # kg/s mass_flow_rate = flow_rate * gas.density_mass gas.TPX = temp, ct.one_atm, X temp_cat = temp surf.TP = temp_cat, ct.one_atm surf.coverages = 'X(1):1.0' gas.set_multiplier(1.0) TDY = gas.TDY cov = surf.coverages if verbose is True: print( ' distance(mm) X_CH4 X_O2 X_H2 X_CO X_H2O X_CO2' ) # create a new reactor gas.TDY = TDY r = ct.IdealGasReactor(gas) r.volume = rvol # create a reservoir to represent the reactor immediately upstream. Note # that the gas object is set already to the state of the upstream reactor upstream = ct.Reservoir(gas, name='upstream') # create a reservoir for the reactor to exhaust into. The composition of # this reservoir is irrelevant. downstream = ct.Reservoir(gas, name='downstream') # Add the reacting surface to the reactor. The area is set to the desired # catalyst area in the reactor. rsurf = ct.ReactorSurface(surf, r, A=cat_area) # The mass flow rate into the reactor will be fixed by using a # MassFlowController object. # mass_flow_rate = velocity * gas.density_mass * area # kg/s # mass_flow_rate = flow_rate * gas.density_mass m = ct.MassFlowController(upstream, r, mdot=mass_flow_rate) # We need an outlet to the downstream reservoir. This will determine the # pressure in the reactor. The value of K will only affect the transient # pressure difference. v = ct.PressureController(r, downstream, master=m, K=1e-5) sim = ct.ReactorNet([r]) sim.max_err_test_fails = 12 # set relative and absolute tolerances on the simulation sim.rtol = 1.0e-10 sim.atol = 1.0e-20 gas_names = gas.species_names surf_names = surf.species_names gas_out = [] surf_out = [] dist_array = [] T_array = [] surf.set_multiplier(0.0) # no surface reactions until the gauze for n in range(N_reactors): # Set the state of the reservoir to match that of the previous reactor gas.TDY = r.thermo.TDY upstream.syncState() if n == on_catalyst: surf.set_multiplier(1.0) if sens is not False: surf.set_multiplier(1.0 + sens[0], sens[1]) if n == off_catalyst: surf.set_multiplier(0.0) sim.reinitialize() sim.advance_to_steady_state() dist = n * reactor_len * 1.0e3 # distance in mm dist_array.append(dist) T_array.append(surf.T) kmole_flow_rate = mass_flow_rate / gas.mean_molecular_weight # kmol/s gas_out.append(1000 * 60 * kmole_flow_rate * gas.X.copy()) # molar flow rate in moles/minute surf_out.append(surf.X.copy()) # stop simulation when things are done changing, to avoid getting so many COVDES errors if n >= 1001: if np.max(abs(np.subtract(gas_out[-2], gas_out[-1]))) < 1e-15: break # make reaction diagrams # out_dir = 'rxnpath' # os.path.exists(out_dir) or os.makedirs(out_dir) # elements = ['H', 'O'] # locations_of_interest = [1000, 1200, 1400, 1600, 1800, 1999] # if sens is False: # if n in locations_of_interest: # location = str(int(n / 100)) # diagram = ct.ReactionPathDiagram(surf, 'X') # diagram.title = 'rxn path' # diagram.label_threshold = 1e-9 # dot_file = f"{out_dir}/rxnpath-{ratio:.1f}-x-{location}mm.dot" # img_file = f"{out_dir}/rxnpath-{ratio:.1f}-x-{location}mm.pdf" # diagram.write_dot(dot_file) # os.system('dot {0} -Tpng -o{1} -Gdpi=200'.format(dot_file, img_file)) # # for element in elements: # diagram = ct.ReactionPathDiagram(surf, element) # diagram.title = element + 'rxn path' # diagram.label_threshold = 1e-9 # dot_file = f"{out_dir}/rxnpath-{ratio:.1f}-x-{location}mm-{element}.dot" # img_file = f"{out_dir}/rxnpath-{ratio:.1f}-x-{location}mm-{element}.pdf" # diagram.write_dot(dot_file) # os.system('dot {0} -Tpng -o{1} -Gdpi=200'.format(dot_file, img_file)) # else: # pass if verbose is True: if not n % 100: print( ' {0:10f} {1:10f} {2:10f} {3:10f} {4:10f} {5:10f} {6:10f}' .format( dist, *gas['CH4(2)', 'O2(3)', 'H2(6)', 'CO(7)', 'H2O(5)', 'CO2(4)'].X * 1000 * 60 * kmole_flow_rate)) gas_out = np.array(gas_out) surf_out = np.array(surf_out) gas_names = np.array(gas_names) surf_names = np.array(surf_names) data_out = gas_out, surf_out, gas_names, surf_names, dist_array, T_array, i_ar, n_surf_reactions print( f"Finished monolith simulation for CH4 and O2 concs {mol_in[0], mol_in[1]} on thread {threading.get_ident()}" ) return data_out
def compute(X): global combustor global residence_time x1, x2, x3, x4, x5 = X inject_parameters(x1, x2, x3, x4, x5) #导入network计算 win32api.ShellExecute(0, 'open', 'C:\\Users\\86183\\network_flow.exe', '', '', 1) #停止程序执行,等待network计算结果 time.sleep(8) #识别result文件中某几行的数据之和 path = os.getcwd()+ "\\result.dat" f=open(path,'r') data=f.readlines() f.close() for i in range(len(data)): line=data[i].split()[1] data[i]=float(line) # print(data) result=np.sum(data[1:2]+data[2:3]) # print(result) # print(result/3) #CRN 化学反应网络法 # 设置反应器气体 gas = ct.Solution('gri30.yaml') # 确定油气等效比、燃气温度、燃气压强 equiv_ratio =result/3 gas.TP = 300.0, 101325 gas.set_equivalence_ratio(equiv_ratio, 'CH4:1.0', 'O2:1.0, N2:3.76') inlet = ct.Reservoir(gas) # 让反应器达到平衡状态 gas.equilibrate('HP') combustor = ct.IdealGasReactor(gas) combustor.volume = 1.0 # 设置出口排放 exhaust = ct.Reservoir(gas) # 计算进出口质量流量 inlet_mfc = ct.MassFlowController(inlet, combustor, mdot=mdot) outlet_mfc = ct.PressureController(combustor, exhaust, master=inlet_mfc, K=0.01) sim = ct.ReactorNet([combustor]) # 设置驻留时间,计算驻留时间逐渐减小所对应的反应器温度 states = ct.SolutionArray(gas, extra=['tres']) temperatureList=[] while combustor.T > 500: sim.set_initial_time(0.0) sim.advance_to_steady_state() # print('tres = {:.2e}; T = {:.1f}'.format(residence_time, combustor.T)) states.append(combustor.thermo.state, tres=residence_time) residence_time *= 0.9 temperatureList.append(combustor.T) # print(temperatureList) maxT=max(temperatureList) return (-1)*maxT
# create the combustor, and fill it in initially with N2 gas.TPX = T, P, 'N2:1.0' przestrzen_obliczeniowa = ct.IdealGasReactor(gas) przestrzen_obliczeniowa.volume = 1.0 # outlet from reactor exhaust = ct.Reservoir(gas) # mass streams of methane and air - setting A/F ratio factor = 0.1 air_mdot = factor * 9.52 * air_mw fuel_mdot = factor * fi * fuel_mw # mass flow stabilizer for methane m1 = ct.MassFlowController(fuel_in, przestrzen_obliczeniowa, mdot=fuel_mdot) # mass flow stabilizer for air m2 = ct.MassFlowController(air_in, przestrzen_obliczeniowa, mdot=air_mdot) # ignition modelled by Gaussian pulse fwhm = 0.2 amplitude = 0.1 t0 = 1.0 igniter_mdot = lambda t: amplitude * math.exp(-(t - t0)**2 * 4 * math. log(2) / fwhm**2) m3 = ct.MassFlowController(igniter, przestrzen_obliczeniowa,
combustor.volume = 1.0 # Create a reservoir for the exhaust exhaust = ct.Reservoir(gas) # Use a variable mass flow rate to keep the residence time in the reactor # constant (residence_time = mass / mass_flow_rate). The mass flow rate function # can access variables defined in the calling scope, including state variables # of the Reactor object (combustor) itself. def mdot(t): return combustor.mass / residence_time inlet_mfc = ct.MassFlowController(inlet, combustor, mdot=mdot) # A PressureController has a baseline mass flow rate matching the 'master' # MassFlowController, with an additional pressure-dependent term. By explicitly # including the upstream mass flow rate, the pressure is kept constant without # needing to use a large value for 'K', which can introduce undesired stiffness. outlet_mfc = ct.PressureController(combustor, exhaust, master=inlet_mfc, K=0.01) # the simulation only contains one reactor sim = ct.ReactorNet([combustor]) # Run a loop over decreasing residence times, until the reactor is extinguished, # saving the state after each iteration.
# define inlet state gas.TPX = T_inlet, p_inlet, comp_inlet inlet = ct.Reservoir(gas) # define injector state (gaseous!) gas.TPX = T_injector, p_injector, comp_injector injector = ct.Reservoir(gas) # define outlet pressure (temperature and composition don't matter) gas.TPX = T_ambient, p_outlet, comp_ambient outlet = ct.Reservoir(gas) # define ambient pressure (temperature and composition don't matter) gas.TPX = T_ambient, p_ambient, comp_ambient ambient_air = ct.Reservoir(gas) # set up connecting devices inlet_valve = ct.Valve(inlet, r) injector_mfc = ct.MassFlowController(injector, r) outlet_valve = ct.Valve(r, outlet) piston = ct.Wall(ambient_air, r) # convert time to crank angle def crank_angle(t): return np.remainder(2 * np.pi * f * t, 4 * np.pi) # set up IC engine parameters V_oT = V_H / (epsilon - 1.) A_piston = .25 * np.pi * d_piston ** 2 stroke = V_H / A_piston r.volume = V_oT piston.area = A_piston def piston_speed(t): return - stroke / 2 * 2 * np.pi * f * np.sin(crank_angle(t))