def __init__(self, cti_file, init_X, inlet_X, inlet_F, volume, n_cstr=0, P=ct.one_atm, T=298, area=None, K=1e-5, kin_param_to_set=None): ''' parameters: cti_file: str, the cti file must contain a gas phase named 'gas' and optionally a reactive surface named 'surface' init_X: dict, array or list of them initial composition of the reactors ''' if not HAS_CANTERA: raise SpectroChemPyException( 'Cantera is not available : please install it before continuing: \n' 'conda install -c cantera cantera') if area is None: add_surface = False else: add_surface = True # copy inlet parameters (for copy) self._cti = cti_file self._init_X = init_X self._inlet_X = inlet_X self._inlet_F = inlet_F self._volume = volume self.T = T self.P = P self._area = area self._K = K self._kin_param_to_set = kin_param_to_set self.cstr = [] # list of cstrs self.surface = [] # reactor surfaces of cstr's self._mfc = [] # mass flow self.inlet = [] # reservoirs self.event = None self._pc = [] # pressure controllers if isinstance(self._volume, (float, int)): self._volume = self._volume * np.ones((n_cstr)) / n_cstr if add_surface and isinstance(area, (float, int)): self._area = self.area * np.ones((n_cstr)) / n_cstr self.n_cstr = len(volume) # first cstr initial_gas = ct.Solution(self._cti, 'gas') initial_gas.TPX = self.T, self.P, init_X self.n_gas_species = len(initial_gas.X) self.cstr.append( ct.IdealGasReactor(initial_gas, name="R_0", energy='off')) self.cstr[0].volume = volume[0] if add_surface: surface = ct.Interface(self._cti, phaseid='surface', phases=[initial_gas]) if kin_param_to_set is not None: modify_surface_kinetics(surface, kin_param_to_set) self.n_surface_species = len(surface.X) self.surface.append( ct.ReactorSurface(kin=surface, r=self.cstr[0], A=area[0])) # create and connect inlets to R_0 if not isinstance(inlet_X, Iterable): inlet_X = [inlet_X] inlet_F = [inlet_F] self._inlet_F = inlet_F for i, (X, F) in enumerate(zip(inlet_X, self._inlet_F)): inlet_gas = ct.Solution(self._cti, 'gas') inlet_gas.TPX = self.T, self.P, X self.inlet.append( ct.Reservoir(contents=inlet_gas, name=f'inlet_{i}')) self._mfc.append( ct.MassFlowController(self.inlet[-1], self.cstr[0], name=f'MFC_{i}')) if not callable(F): self._mfc[-1].set_mass_flow_rate(F * inlet_gas.density) else: # it is tricky to pass non explicit lambda functions to MassFlowControllers # the following works while use of self._inlet_F[i](t) generate an error # when using reactorNet.advance() if i == 0: self._mfc[-1].set_mass_flow_rate(lambda t: self._inlet_F[0] (t) * inlet_gas.density) elif i == 1: self._mfc[-1].set_mass_flow_rate(lambda t: self._inlet_F[1] (t) * inlet_gas.density) elif i == 2: self._mfc[-1].set_mass_flow_rate(lambda t: self._inlet_F[2] (t) * inlet_gas.density) elif i == 3: self._mfc[-1].set_mass_flow_rate(lambda t: self._inlet_F[3] (t) * inlet_gas.density) elif i == 4: self._mfc[-1].set_mass_flow_rate(lambda t: self._inlet_F[4] (t) * inlet_gas.density) else: raise ValueError( "variable flow rate(s) must be associated within the first" "five MFC(s)") # create other cstrs and link them with the previous one through a pressure controller for i in range(1, len(volume)): initial_gas = ct.Solution(self._cti, 'gas') initial_gas.TPX = self.T, self.P, init_X self.cstr.append( ct.IdealGasReactor(initial_gas, name="R_0", energy='off')) self.cstr[i].volume = volume[i] if add_surface: surface = ct.Interface(self._cti, phaseid='surface', phases=[initial_gas]) self.n_surface_species = len(surface.X) if kin_param_to_set is not None: modify_surface_kinetics(surface, kin_param_to_set) self.surface.append( ct.ReactorSurface(kin=surface, r=self.cstr[i], A=area[i])) self._pc.append( ct.PressureController(self.cstr[i - 1], self.cstr[i], master=self._mfc[-1], K=K)) # create the event event_gas = ct.Solution(self._cti, 'gas') event_gas.TPX = self.T, self.P, init_X self.event = ct.Reservoir(contents=event_gas, name=f'event') self._pc.append( ct.PressureController(self.cstr[-1], self.event, master=self._mfc[-1], K=K)) self.X = np.ones((self.n_cstr, self.n_gas_species)) self.coverages = np.ones((self.n_cstr, self.n_surface_species)) for i, (r, s) in enumerate(zip(self.cstr, self.surface)): self.X[i, :] = r.thermo.X self.coverages[i, :] = s.coverages self.net = ct.ReactorNet(self.cstr)
def simulate(self): """ Run all the conditions as a cantera simulation. Returns the data as a list of tuples containing: (time, [list of temperature, pressure, and species data]) for each reactor condition """ # Get all the cantera names for the species speciesNamesList = [ getSpeciesIdentifier(species) for species in self.speciesList ] inertIndexList = [ self.speciesList.index(species) for species in self.speciesList if species.index == -1 ] allData = [] for condition in self.conditions: # First translate the molFrac from species objects to species names newMolFrac = {} for key, value in condition.molFrac.iteritems(): newkey = getSpeciesIdentifier(key) newMolFrac[newkey] = value # Set Cantera simulation conditions if condition.V0 is None: self.model.TPX = condition.T0.value_si, condition.P0.value_si, newMolFrac elif condition.P0 is None: self.model.TDX = condition.T0.value_si, 1.0 / condition.V0.value_si, newMolFrac else: raise Exception( "Cantera conditions in which T0 and P0 or T0 and V0 are not the specified state variables are not yet implemented." ) # Choose reactor if condition.reactorType == 'IdealGasReactor': canteraReactor = ct.IdealGasReactor(self.model) elif condition.reactorType == 'IdealGasConstPressureReactor': canteraReactor = ct.IdealGasConstPressureReactor( contents=self.model) elif condition.reactorType == 'IdealGasConstPressureTemperatureReactor': canteraReactor = ct.IdealGasConstPressureReactor( contents=self.model, energy='off') else: raise Exception( 'Other types of reactor conditions are currently not supported' ) # Run this individual condition as a simulation canteraSimulation = ct.ReactorNet([canteraReactor]) numCtReactions = len(self.model.reactions()) if self.sensitiveSpecies: if ct.__version__ == '2.2.1': print 'Warning: Cantera version 2.2.1 may not support sensitivity analysis unless SUNDIALS was used during compilation.' print 'Warning: Upgrade to newer of Cantera in anaconda using the command "conda update -c rmg cantera"' # Add all the reactions as part of the analysis for i in range(numCtReactions): canteraReactor.add_sensitivity_reaction(i) # Set the tolerances for the sensitivity coefficients canteraSimulation.rtol_sensitivity = 1e-4 canteraSimulation.atol_sensitivity = 1e-6 # Initialize the variables to be saved times = [] temperature = [] pressure = [] speciesData = [] sensitivityData = [] # Begin integration time = 0.0 # Run the simulation over 100 time points while canteraSimulation.time < condition.reactionTime.value_si: # Advance the state of the reactor network in time from the current time to time t [s], taking as many integrator timesteps as necessary. canteraSimulation.step(condition.reactionTime.value_si) times.append(canteraSimulation.time) temperature.append(canteraReactor.T) pressure.append(canteraReactor.thermo.P) speciesData.append(canteraReactor.thermo[speciesNamesList].X) if self.sensitiveSpecies: # Cantera returns mass-based sensitivities rather than molar concentration or mole fraction based sensitivities. # The equation for converting between them is: # # d ln xi = d ln wi - sum_(species i) (dln wi) (xi) # # where xi is the mole fraction of species i and wi is the mass fraction of species i massFracSensitivityArray = canteraSimulation.sensitivities( ) if condition.reactorType == 'IdealGasReactor': # Row 0: mass, Row 1: volume, Row 2: internal energy or temperature, Row 3+: mass fractions of species massFracSensitivityArray = massFracSensitivityArray[ 3:, :] elif condition.reactorType == 'IdealGasConstPressureReactor' or condition.reactorType == 'IdealGasConstPressureTemperatureReactor': # Row 0: mass, Row 1: enthalpy or temperature, Row 2+: mass fractions of the species massFracSensitivityArray = massFracSensitivityArray[ 2:, :] else: raise Exception( 'Other types of reactor conditions are currently not supported' ) for i in range(len(massFracSensitivityArray)): massFracSensitivityArray[i] *= speciesData[-1][i] sensitivityArray = np.zeros( len(self.sensitiveSpecies) * len(self.model.reactions())) for index, species in enumerate(self.sensitiveSpecies): for j in range(numCtReactions): sensitivityArray[ numCtReactions * index + j] = canteraSimulation.sensitivity( species.toChemkin(), j) for i in range(len(massFracSensitivityArray)): if i not in inertIndexList: # massFracSensitivity for inerts are returned as nan in Cantera, so we must not include them here sensitivityArray[ numCtReactions * index + j] -= massFracSensitivityArray[i][j] sensitivityData.append(sensitivityArray) # Convert speciesData and sensitivityData to a numpy array speciesData = np.array(speciesData) sensitivityData = np.array(sensitivityData) # Resave data into generic data objects time = GenericData(label='Time', data=times, units='s') temperature = GenericData(label='Temperature', data=temperature, units='K') pressure = GenericData(label='Pressure', data=pressure, units='Pa') conditionData = [] conditionData.append(temperature) conditionData.append(pressure) for index, species in enumerate(self.speciesList): # Create generic data object that saves the species object into the species object. To allow easier manipulate later. speciesGenericData = GenericData(label=speciesNamesList[index], species=species, data=speciesData[:, index], index=species.index) conditionData.append(speciesGenericData) reactionSensitivityData = [] for index, species in enumerate(self.sensitiveSpecies): for j in range(numCtReactions): reactionSensitivityGenericData = GenericData( label='dln[{0}]/dln[k{1}]: {2}'.format( species.toChemkin(), j + 1, self.model.reactions()[j]), species=species, reaction=self.model.reactions()[j], data=sensitivityData[:, numCtReactions * index + j], index=j + 1, ) reactionSensitivityData.append( reactionSensitivityGenericData) allData.append((time, conditionData, reactionSensitivityData)) return allData
def ignUVTimeScales(gas, dt): ''' Function ignUVAll ====================================================================== This function returns the ignition delay and excitation time for the reactor gas: cantera phase object dt: initial guess for the timestep return: (tign,texcite) ''' def findTimescales(t, qdot): ''' Function findTimescales ====================================================================== This function determines the ignition delay and the excitation time (using 5% of qdotMax). t: time qdot: volumetric chemical energy release return: (tIgn,tExcite,resolution): resolution is given in number of timesteps between tIgn and the start of tExcitement ''' qdotMax = np.max(qdot) k = len(qdot) - 1 kMax = 0 kExcite = 0 while k >= 0: if qdot[k] == qdotMax: kMax = k if kMax != 0 and qdot[k] <= qdotMax * 0.05: kExcite = k break k -= 1 if kMax == 0: Exception("Failed to find the excitation time") tExcite = t[kMax] - t[kExcite] tIgn = t[kMax] return tIgn, tExcite, kMax - kExcite, kExcite #find the equilibrium (T, p, X) = gas.TPX gas.equilibrate('UV') try: iH2O = gas.species_index('H2O') H2O = 'H2O' except: iH2O = gas.species_index('h2o') H2O = 'h2o' H2Oeq = gas.Y[iH2O] gas.TPX = (T, p, X) alpha = 0.95 #arbitrary percentage #initiate the reactor network T0, p0, Y0 = gas.TPY qdot0 = chemicalPower(gas, 'uv') #advance simulation with guessed timestep it, itMin, itMax = 0, 64, 1028 while True: if it == 0: gas.TPY = T0, p0, Y0 r = ct.IdealGasReactor(gas) sim = ct.ReactorNet([r]) t0 = 0.0 time = t0 t = [t0] T = [T0] p = [p0] Y = [Y0] qdot = [qdot0] it += 1 elif (r.thermo[H2O].Y >= (alpha * H2Oeq)) and it >= itMin: break elif (r.thermo[H2O].Y >= (alpha * H2Oeq)) and it < itMin: #not enough data points; resolve more it = 0 dt /= 2.0 elif it > itMax: #too resolved; increase time step it = 0 dt *= 2.0 else: time += dt sim.advance(time) t.append(time) T.append(gas.T) p.append(gas.P) Y.append(gas.Y) qdot.append(chemicalPower(gas, 'uv')) it += 1 #find the ignition delay and the excitement time tIgn, tExcite, resolution, exciteIndex = findTimescales(t, qdot) N = 50 while resolution < 50: #zoom in on excitement time N *= 2 #enforced resolution kStart = max(exciteIndex - resolution, 0) gas.TPY = T[kStart], p[kStart], Y[kStart] r = ct.IdealGasReactor(gas) sim = ct.ReactorNet([r]) time = 0.0 tInitial = t[kStart] tFinal = tIgn + 2 * tExcite #factor of two to make this symmetrical dt = (tFinal - tInitial) / float(N) t = [tInitial] T = [gas.T] p = [gas.P] Y = [gas.Y] qdot = [chemicalPower(gas, 'uv')] while time < (tFinal - tInitial): time += dt sim.advance(time) t.append(time + tInitial) T.append(gas.T) p.append(gas.P) Y.append(gas.Y) qdot.append(chemicalPower(gas, 'uv')) tIgn, tExcite, resolution, exciteIndex = findTimescales(t, qdot) return tIgn, tExcite
def run(self, initialTime: float = -1.0, finalTime: float = -1.0): if initialTime == -1.0: initialTime = self.initialTime if finalTime == -1.0: finalTime = self.finalTime self.timeHistory = None self.kineticSensitivities = None #3D numpy array, columns are reactions with timehistories, depth gives the observable for those histories conditions = self.settingShockTubeConditions() mechanicalBoundary = conditions[1] #same solution for both cp and cv sims if mechanicalBoundary == 'constant pressure': shockTube = ct.IdealGasConstPressureReactor( self.processor.solution, name='R1', energy=conditions[0]) else: shockTube = ct.IdealGasReactor(self.processor.solution, name='R1', energy=conditions[0]) sim = ct.ReactorNet([shockTube]) columnNames = [ shockTube.component_name(item) for item in range(shockTube.n_vars) ] columnNames = ['time'] + ['pressure'] + columnNames self.timeHistory = pd.DataFrame(columns=columnNames) if self.kineticSens == 1: for i in range(self.processor.solution.n_reactions): shockTube.add_sensitivity_reaction(i) dfs = [pd.DataFrame() for x in range(len(self.observables))] tempArray = [ np.zeros(self.processor.solution.n_reactions) for x in range(len(self.observables)) ] t = self.initialTime counter = 0 while t < self.finalTime: t = sim.step() if mechanicalBoundary == 'constant volume': state = np.hstack([ t, shockTube.thermo.P, shockTube.mass, shockTube.volume, shockTube.T, shockTube.thermo.X ]) else: state = np.hstack([ t, shockTube.thermo.P, shockTube.mass, shockTube.T, shockTube.thermo.X ]) self.timeHistory.loc[counter] = state if self.kineticSens == 1: counter_1 = 0 for observable, reaction in itertools.product( self.observables, range(self.processor.solution.n_reactions)): tempArray[self.observables.index( observable)][reaction] = sim.sensitivity( observable, reaction) counter_1 += 1 if counter_1 % self.processor.solution.n_reactions == 0: dfs[self.observables.index(observable)] = dfs[ self.observables.index(observable)].append( ((pd.DataFrame( tempArray[self.observables.index( observable)])).transpose()), ignore_index=True) counter += 1 if self.timeHistories != None: self.timeHistories.append(self.timeHistory) if self.kineticSens == 1: numpyMatrixsksens = [ dfs[dataframe].values for dataframe in range(len(dfs)) ] self.kineticSensitivities = np.dstack(numpyMatrixsksens) return self.timeHistory, self.kineticSensitivities else: return self.timeHistory
def JSR_steadystate2(gas,resTime,volume,kinetic_sens=0,physical_sens=0,observables=[],physical_params=['T','P'],energycon='off',pressureValveCoefficient=0.01,maxsimulationTime=1000,initial_conditions_gas=0,tempsens=0): # Inlet gas conditions are passed into function in the "gas" object, which is a cantera object # Reactor parameters passed into function as resTime and volume. Residence time and volume of JSR #kinetic sens and physical sens are optional parameters which are set to zero by default. Set them to 1 to #calculate sensitivity based on kinetic or physical parameters. If these are set to 1 you must pass #an array of all observables to calculate sensitivities for simtype='jsr' reactorPressure=gas.P # This is the "conductance" of the pressure valve and will determine its efficiency in # holding the reactor pressure to the desired conditions. It is an optional parameter pressureValveCoefficient=pressureValveCoefficient # This parameter will allow you to decide if the valve's conductance is acceptable. If there # is a pressure rise in the reactor beyond this tolerance, you will get a warning maxPressureRiseAllowed = 0.001 # Simulation termination criterion #maxSimulationTime = maxsimulationTime # seconds. An optional parameter fuelAirMixtureTank = ct.Reservoir(gas) exhaust = ct.Reservoir(gas) if initial_conditions_gas==0: stirredReactor = ct.IdealGasReactor(gas, energy=energycon, volume=volume) mdot=stirredReactor.mass/resTime else: stirredReactor = ct.IdealGasReactor(initial_conditions_gas,energy=energycon,volume=volume) dummyReactor = ct.IdealGasReactor(gas,energy=energycon,volume=volume) mdot=dummyReactor.mass/resTime massFlowController = ct.MassFlowController(upstream=fuelAirMixtureTank,downstream=stirredReactor,mdot=mdot) pressureRegulator = ct.Valve(upstream=stirredReactor,downstream=exhaust,K=pressureValveCoefficient) reactorNetwork = ct.ReactorNet([stirredReactor]) #This block adds kinetic sensitivity parameters for all reactions if desired. if kinetic_sens==1 and bool(observables): for i in range(gas.n_reactions): stirredReactor.add_sensitivity_reaction(i) if kinetic_sens==1 and bool(observables)==False: print('Please supply a non-empty list of observables for sensitivity analysis or set kinetic_sens=0') #if physical_sens==1 and bool(observables): #print('Placeholder') if physical_sens==1 and bool(observables)==False: print('Please supply a non-empty list of observables for sensitivity analysis or set physical_sens=0') # now compile a list of all variables for which we will store data columnNames = [stirredReactor.component_name(item) for item in range(stirredReactor.n_vars)] columnNames = ['pressure'] + columnNames # use the above list to create a DataFrame timeHistory = pd.DataFrame(columns=columnNames) # Start the stopwatch tic = time.time() #Names=[] #for l in np.arange(stirredReactor.n_vars): #Names.append(stirredReactor.component_name(l)) #global b #Names = [stirredReactor.component_name(item) for item in range(stirredReactor.n_vars)] #state = np.hstack([stirredReactor.mass, #stirredReactor.volume, stirredReactor.T, stirredReactor.thermo.X]) #print(state) #b=pd.DataFrame(data=state).transpose() #b.columns=Names #Establish a matrix to hold sensitivities for kinetic parameters, along with tolerances if kinetic_sens==1 and bool(observables): #senscolumnNames = ['Reaction']+observables senscolumnNames = observables #sensArray = pd.DataFrame(columns=senscolumnNames) senstempArray = np.zeros((gas.n_reactions,len(observables))) reactorNetwork.rtol_sensitivity = 1.0e-6 reactorNetwork.atol_sensitivity = 1.0e-6 dk=0.01 if physical_sens==1 and bool(observables): #psenscolumnNames = ['Parameter'] + observables #psensArray = pd.DataFrame(columns=senscolumnNames) pIndex=[[gas.T],physical_params,observables] psenstempArray = np.zeros((len(observables),len(physical_params))) tempSol=[] conditions=gas.TPX for i in np.arange(len(physical_params)): if physical_params[i]=='T': gas.TPX=conditions[0]+dk,conditions[1],conditions[2] if physical_params[i]=='P': gas.TPX=conditions[0],conditions[1]+dk,conditions[2] tempMixTank=ct.Reservoir(gas) tempExhaust = ct.Reservoir(gas) tempReactor=ct.IdealGasReactor(gas,energy=energycon,volume=volume) tempMassFlowCt=ct.MassFlowController(upstream=tempMixTank,downstream=tempReactor,mdot=tempReactor.mass/resTime) tempPresReg=ct.Valve(upstream=tempReactor,downstream=tempExhaust,K=pressureValveCoefficient) tempNetwork=ct.ReactorNet([tempReactor]) tempNetwork.advance_to_steady_state() tempSol.append(tempReactor.get_state()) gas.TPX=conditions reactorNetwork.rtol=1e-10 resid=reactorNetwork.advance_to_steady_state(return_residuals=True) #print(resid) #print(reactorNetwork.rtol) final_pressure=stirredReactor.thermo.P global b b=reactorNetwork.sensitivities() if kinetic_sens==1 and bool(observables): for k in np.arange(len(observables)): for j in np.arange(gas.n_reactions): try: senstempArray[j,k]=reactorNetwork.sensitivity(observables[k],j) except: senstempArray[j,k]=-1 #sensArray['Reaction']=gas.reaction_equations() #sensArray[observables]=senstempArray.T #temp = sensArray.as_matrix() kIndex = [[gas.T],gas.reaction_equations(),senscolumnNames] if physical_sens==1 and bool(observables): for k in np.arange(len(observables)): for j in np.arange(len(['T','P'])): psenstempArray[j,k]=np.log10(stirredReactor.get_state()[stirredReactor.component_index(observables[k])])-np.log10(tempSol[j][stirredReactor.component_index(observables[k])]) psenstempArray[j,k]=np.divide(psenstempArray[j,k],np.log10(dk)) #state = np.hstack([stirredReactor.thermo.P, stirredReactor.mass, #stirredReactor.volume, stirredReactor.T, stirredReactor.thermo.X]) # Stop the stopwatch toc = time.time() print('Simulation Took {:3.2f}s to compute'.format(toc-tic)) columnNames = [] #Store solution to a solution array #for l in np.arange(stirredReactor.n_vars): #columnNames.append(stirredReactor.component_name(l)) columnNames=[stirredReactor.component_name(item) for item in range(stirredReactor.n_vars)] #state=stirredReactor.get_state() state=np.hstack([stirredReactor.mass, stirredReactor.volume, stirredReactor.T, stirredReactor.thermo.X]) data=pd.DataFrame(state).transpose() data.columns=columnNames # We now check to see if the pressure rise during the simulation, a.k.a the pressure valve # was okay pressureDifferential = timeHistory['pressure'].max()-timeHistory['pressure'].min() if(abs(pressureDifferential/reactorPressure) > maxPressureRiseAllowed): print("WARNING: Non-trivial pressure rise in the reactor. Adjust K value in valve") if kinetic_sens==1 and bool(observables) and physical_sens!=1: modelData=model_data(simtype,kinetic_sens=np.expand_dims(senstempArray,axis=0),Solution=data,Index=kIndex) modelData.add_final_pressure(final_pressure) modelData.add_solver_time(toc-tic) return modelData if physical_sens==1 and bool(observables) and kinetic_sens!=1: modelData=model_data(simtype,physical_sens=np.expand_dims(psenstempArray,axis=0),Solution=data,pIndex=pIndex) modelData.add_final_pressure(final_pressure) modelData.add_solver_time(toc-tic) return modelData if physical_sens==1 and bool(observables) and kinetic_sens==1: modelData=model_data(simtype,physical_sens=np.expand_dims(psenstempArray,axis=0),kinetic_sens=np.expand_dims(senstempArray,axis=0),Solution=data,Index=kIndex,pIndex=pIndex) modelData.add_final_pressure(final_pressure) modelData.add_solver_time(toc-tic) return modelData else: modelData=model_data(simtype,Solution=data) modelData.add_final_pressure(final_pressure) modelData.add_solver_time(toc-tic) return modelData
def define_reactor(feed_temp, feed_pressure, feed_velocity, feed_composition, cat_area_by_rctr_vol, cat_composition, wall_temp, wall_area_by_rctr_vol, length, rctr_type, nodes=None): """ Define Continous Stirred Tank Reactor with a catalyst reacting surface by adding appropriate components to IdealGasReactor class defined in cantera. Due to the nature of underlying class definitions in cantera, CSTR is defined as a collection of reactor, catlayst surface, inlets, outlets, and the corresponding reservoirs. Plug Flow Reactor is defined as a concatenation of CSTRs. :param feed_temp: Temperature of the reactant feed :param feed_pressure: Pressure of the reactant feed :param feed_velocity: Velocity of the reactant feed :param feed_composition: composition of the reactant feed. Use gas phase :param cat_area_by_rctr_vol: Area of catalyst given in inverse length :param cat_composition: Composition of catalyst. Use surface phase :param wall_temp: External temperature outside outer wall of reactor :param wall_area_by_rctr_vol: Area of outer wall given in inverse length :param length: Length of the reactor along which feed flows :param rctr_type: Type of the reactor. Options are 'CSTR' or 'PFR' :param nodes: If reactor type is 'PFR', it is simulated as N CSTRS, where N is given by nodes :return: IdealGasRector or ReactorNet with appropriate components """ gas = feed_composition gas.TP = feed_temp, feed_pressure surf = cat_composition surf.TP = gas.TP if rctr_type == 'CSTR': rctr_len = length else: rctr_len = length / (nodes - 1) width = 1.0 # Arbitrary value surf_area = rctr_len * width rctr_vol = surf_area / cat_area_by_rctr_vol feed_mass_flow_rate = feed_velocity * gas.density * width / \ cat_area_by_rctr_vol """ In the python code, the reactor is input to the defintions of reactor components. Despite the weird way of specifying reactor components in python, they get added to the reactor object in the C++ code """ upstream = ct.Reservoir(gas, name='upstream') downstream = ct.Reservoir(gas, name='downstream') if rctr_type == 'CSTR': reactor = ct.IdealGasReactor(gas, energy='off') reactor.volume = rctr_vol rctr_surf = ct.ReactorSurface(surf, reactor, A=surf_area) inlet = ct.MassFlowController(upstream, reactor, mdot=feed_mass_flow_rate) outlet = ct.PressureController(reactor, downstream, master=inlet, K=1e-5) #K is small number return reactor, upstream, gas elif rctr_type == 'PFR': reactors = [] for i in range(nodes): r = ct.IdealGasReactor(gas, energy='off') r.volume = rctr_vol r_srf = ct.ReactorSurface(surf, r, A=surf_area) if not i: inlet = ct.MassFlowController(upstream, r, mdot=feed_mass_flow_rate) else: inlet = ct.MassFlowController(reactors[i - 1], r, mdot=feed_mass_flow_rate) outlet = ct.PressureController(r, downstream, master=inlet, K=1e-5) reactors.append(r) reactor_net = ct.ReactorNet(reactors) return reactor_net, upstream, gas
gas = ct.Solution(os.getcwd()+'\\Mechanisms\\nheptane\\nheptane.cti') # Define the reactor temperature and pressure reactorTemperature = 1000 #Kelvin reactorPressure = 101325.0 #Pascals gas.TP = reactorTemperature, reactorPressure # Define the fuel, oxidizer and set the stoichiometry gas.set_equivalence_ratio(phi=1.0, fuel='NC7H16', oxidizer={'O2':1.0, 'N2':3.76}) # Create a batch reactor object and add it to a reactor network # In this example, the batch reactor will be the only reactor # in the network r = ct.IdealGasReactor(contents=gas, name='Batch Reactor') reactorNetwork = ct.ReactorNet([r]) # now compile a list of all variables for which we will store data stateVariableNames = [r.component_name(item) for item in range(r.n_vars)] # use the above list to create a DataFrame timeHistory = pd.DataFrame(columns=stateVariableNames) def ignitionDelay(df, species): """ This function computes the ignition delay from the occurence of the peak in species' concentration. """ return df[species].argmax()
def compute_ignition_time_hist_Tb(dict_ZCT, dict_Yk, idx_Z, idx_C, ctml_file, pressure=ct.one_atm): """ Compute the ignition time for a given Z, C and T from table """ print(idx_Z, idx_C) T_ref = dict_ZCT['T'][idx_Z, idx_C] gas = get_cantera_mixture_nodes(dict_ZCT, dict_Yk, idx_Z, idx_C, ctml_file, pressure=pressure) r = ct.IdealGasReactor(contents=gas, name='Batch Reactor') reactor_network = ct.ReactorNet([r]) reactor_network.set_max_time_step(1e-3) # This is a starting estimate. If you do not get an ignition within this time, increase it # estimatedIgnitionDelayTime = 10e-3 # estimatedIgnitionDelayTime = 5 estimatedIgnitionDelayTime = 1. t = 0 counter = 0 dict_data = {'t': [], 'T': [], 'OH': []} while (t < estimatedIgnitionDelayTime): t = reactor_network.step() if not counter % 20: dict_data['t'].append(t) dict_data['T'].append(reactor_network.get_state()[2]) dict_data['OH'].append(reactor_network.get_state()[8]) if counter > 10000: print( "\tmaximum number of iterations reached (%s) for idx : (%s,%s)" % (counter, idx_Z, idx_C)) return np.nan, np.nan, np.nan counter += 1 # df_T = pd.DataFrame.from_dict(dict_data) T_burnt = df_T['T'].max() try: # dT_dt = np.gradient(df_T['T'], df_T['t']) # idx = np.argmax(dT_dt) idx = np.argmax(df_T['OH']) ignition_time = df_T['t'][idx] except IndexError: # plt.plot(df_T['t'], df_T['T']) # plt.savefig("Figures/indexErrorMaxOH_idx_z_%s_idx_c_%s.png" % (idx_Z, idx_Z)) # plt.close() ignition_time = np.nan if T_burnt <= 1.05 * T_ref: print("\tT_burnt < T_ref") ignition_time = np.nan if ignition_time > 0.9 * estimatedIgnitionDelayTime: # ignition_time = estimatedIgnitionDelayTime # plt.figure() # plt.plot(df_T['t'], df_T['T']) # plt.savefig('Figures/tau_ign_hugeTime_%s_%s.png' % (idx_Z, idx_C)) # plt.close() ignition_time = np.nan alpha_tmp = ((T_burnt + 1e-6) - T_ref) / T_burnt return float(ignition_time), float(T_burnt), float(alpha_tmp)
def ShockTube(ctiFile ,pressure,temperature,conditions,initialTime,finalTime,thermalBoundary,observables=[],physical_params=[], kinetic_sens=0,physical_sens=0): #gas = ct.Solution('AramcoMech2.0.cti') # 'GRI30-1999.cti' gas = ct.Solution(ctiFile) gas.TPX = temperature, pressure*101325, conditions if thermalBoundary == 'adiabatic': shockTube = ct.IdealGasReactor(gas,name = 'R1',energy= 'on') elif thermalBoundary == 'isothermal': shockTube = ct.IdealGasReactor(gas,name = 'R1', energy= 'off') else: raise Exception('Please enter adiabatic or isothermal for the thermal boundary layer') sim=ct.ReactorNet([shockTube]) if kinetic_sens==1 and bool(observables)==False: raise Exception('Please supply a non-empty list of observables for sensitivity analysis or set kinetic_sens=0') if physical_sens==1 and bool(observables)==False: raise Exception('Please supply a non-empty list of observables for sensitivity analysis or set physical_sens=0') if kinetic_sens==1 and bool(observables): [shockTube.add_sensitivity_reaction(i) for i in xrange(gas.n_reactions)] dfs = [pd.DataFrame() for x in xrange(len(observables))] tempArray = [np.zeros(gas.n_reactions) for x in xrange(len(observables))] if physical_sens==1 and bool(observables): baseConditions = gas.TPX columnNames = [shockTube.component_name(item) for item in range(shockTube.n_vars)] columnNames = ['time']+['pressure'] + columnNames timeHistory = pd.DataFrame(columns = columnNames) timeHistorytest= pd.DataFrame(columns = columnNames) timeHistorytest2= pd.DataFrame(columns = columnNames) timeHistorytest3 = pd.DataFrame(columns = columnNames) t=initialTime counter = 0 while t < finalTime: t = sim.step() state = np.hstack([t, shockTube.thermo.P, shockTube.mass, shockTube.volume, shockTube.T, shockTube.thermo.X]) timeHistory.loc[counter] = state if kinetic_sens == 1 and bool(observables): newcounter = 0 for observable,reaction in itertools.product(observables,xrange(gas.n_reactions)): tempArray[observables.index(observable)][reaction] = sim.sensitivity(observable, reaction) newcounter +=1 #print('this is new counter' , newcounter) if(newcounter%gas.n_reactions == 0): print(observable , newcounter) dfs[observables.index(observable)] = dfs[observables.index(observable)].append(((pd.DataFrame(tempArray[observables.index(observable)])).transpose()),ignore_index=True) counter +=1 ksensIndex = [timeHistory['time'].as_matrix(),gas.reaction_equations(),observables] numpyMatrixsksens = [dfs[dataframe].as_matrix() for dataframe in xrange(len(dfs))] S = np.dstack(numpyMatrixsksens) print(np.shape(S)) if physical_sens == 1 and bool(observables): dk=.01 originalPsens = (timeHistory[observables]).applymap(np.log10) for numOfPhyParams in xrange(len(physical_params)): if physical_params[numOfPhyParams] == 'T': gas2 = ct.Solution(ctiFile) gas2.TPX = baseConditions[0]+dk,baseConditions[1],baseConditions[2] if thermalBoundary == 'adiabatic': shockTube2 = ct.IdealGasReactor(gas2,name = 'R1',energy= 'on') sim2=ct.ReactorNet([shockTube2]) if thermalBoundary == 'isothermal': shockTube2 = ct.IdealGasReactor(gas2,name = 'R1',energy= 'off') sim2=ct.ReactorNet([shockTube2]) for idx, newTime in enumerate(timeHistory['time'].as_matrix()): sim2.advance(newTime) state = np.hstack([newTime, shockTube2.thermo.P, shockTube2.mass, shockTube2.volume, shockTube2.T, shockTube2.thermo.X]) timeHistorytest.loc[idx] = state tempT = (timeHistorytest[observables]).applymap(np.log10) tempT = (originalPsens.subtract(tempT))/np.log10(dk) print(tempT) tempTlst = [tempT.ix[:,idx] for idx in xrange(tempT.shape[1])] if physical_params[numOfPhyParams] == 'P': gas3 = ct.Solution(ctiFile) gas3.TPX = baseConditions[0],baseConditions[1]+dk,baseConditions[2] if thermalBoundary == 'adiabatic': shockTube3 = ct.IdealGasReactor(gas3,name = 'R1',energy = 'on') sim3 = ct.ReactorNet([shockTube3]) if thermalBoundary =='isothermal': shockTube3 = ct.IdealGasReactor(gas3,name = 'R1', energy = 'off') sim3 = ct.ReactorNet([shockTube3]) for idx2, newTime2 in enumerate(timeHistory['time'].as_matrix()): sim3.advance(newTime) state = np.hstack([newTime2, shockTube3.thermo.P, shockTube3.mass, shockTube3.volume, shockTube3.T, shockTube3.thermo.X]) timeHistorytest2.loc[idx2] = state tempP = (timeHistorytest2[observables]).applymap(np.log10) tempP = (originalPsens.subtract(tempP))/np.log10(dk) tempPlst = [tempP.ix[:,idx] for idx in xrange(tempP.shape[1])] if physical_params[numOfPhyParams] == 'X': gas4 = ct.Solution(ctiFile) gas4.TPX = baseConditions[0],baseConditions[1],baseConditions[2]+dk if thermalBoundary =='adiabatic': shockTube4 = ct.IdealGasReactor(gas4,name = 'R1',energy= 'on') sim4 = ct.ReactorNet([shockTube4]) if thermalBoundary == 'isothermal': shockTube4 = ct.IdealGasReactor(gas4 ,name = 'R1', energy = 'off') sim4 = ct.ReactorNet([shockTube4]) for idx3, newTime3 in enumerate(timeHistory['time'].as_matrix()): sim4.advance(newTime3) state = np.hstack([newTime3, shockTube4.thermo.P, shockTube4.mass, shockTube4.volume, shockTube4.T, shockTube4.thermo.X]) timeHistorytest3.loc[idx3] = state tempX = (timeHistorytest3[observables]).applymap(np.log10) tempX = (originalPsens.subtract(tempX))/np.log10(dk) tempXlst = [tempX.ix[:,idx] for idx in xrange(tempX.shape[1])] if "T" and "P" and "X" in physical_params: t = [tempTlst,tempPlst,tempXlst] psensIndex = [timeHistory['time'].as_matrix(),['T','P','X'],observables] psensdfs = [pd.concat([t[0][x],t[1][x],t[2][x]],ignore_index = True , axis = 1) for x in xrange(len(tempXlst))] numpyMatrixspsens = [psensdfs[dataframe].as_matrix() for dataframe in xrange(len(psensdfs))] pS = np.dstack(numpyMatrixspsens) elif "T" and "P" in physical_params: t = [tempTlst,tempPlst] psensIndex = [timeHistory['time'].as_matrix(),['T','P'],observables] psensdfs = [pd.concat([t[0][x],t[1][x]],ignore_index=True,axis = 1) for x in xrange(len(tempTlst))] numpyMatrixspsens = [psensdfs[dataframe].as_matrix() for dataframe in xrange(len(psensdfs))] pS = np.dstack(numpyMatrixspsens) print(np.shape(pS)) elif "T" and "X" in physical_params: t = [tempTlst,tempXlst] psensIndex = [timeHistory['time'].as_matrix(),['T','X'],observables] psensdfs = [pd.concat([t[0][x],t[1][x]], ignore_index = True, axis = 1) for x in xrange(len(tempTlst))] numpyMatrixspsens = [psensdfs[dataframe].as_matrix() for dataframe in xrange(len(psensdfs))] pS = np.dstack(numpyMatrixspsens) elif "P" and "X" in physical_params: t = [tempPlst,tempXlst] psensIndex = [timeHistory['time'].as_matrix(),['P','X'],observables] psensdfs = [pd.concat([t[0][x],t[1][x]], ignore_index = True, axis = 1) for x in xrange(len(tempPlst))] numpyMatrixspsens = [psensdfs[dataframe].as_matrix() for dataframe in xrange(len(psensdfs))] pS = np.dstack(numpyMatrixspsens) elif "T" in physical_params: t = [tempTlst] psensIndex = [timeHistory['time'].as_matrix(),['T'],observables] numpyMatrixspsens = [tempTlst[dataframe].as_matrix() for dataframe in xrange(len(tempTlst))] pS = np.dstack(numpyMatrixspsens) elif "P" in physical_params: t = [tempPlst] psensIndex = [timeHistory['time'].as_matrix(),['P'],observables] numpyMatrixspsens = [tempPlst[dataframe].as_matrix() for dataframe in xrange(len(tempPlst))] pS = np.dstack(numpyMatrixspsens) elif "X" in physical_params: t = [tempXlst] psensIndex = [timeHistory['time'].as_matrix(),['X'],observables] numpyMatrixspsens = [tempXlst[dataframe].as_matrix() for dataframe in xrange(len(tempPlst))] pS = np.dstack(numpyMatrixspsens) if kinetic_sens==1 and bool(observables) and physical_sens==0: results = shock_tube_model_data(kinetic_sens = S, Solution = timeHistory, Index = ksensIndex) return results if kinetic_sens==0 and bool(physical_params) and physical_sens==1: results = shock_tube_model_data(physical_sens = pS, solution = timeHistory, pIndex = psensIndex) return results if physical_sens==1 and bool(observables)and kinetic_sens==1: results = shock_tube_model_data(kinetic_sens = S, physical_sens = pS, Solution = timeHistory, Index = ksensIndex, pIndex = psensIndex) return results if physical_sens == 0 and kinetic_sens == 0: results = shock_tube_model_data(Solution = timeHistory) return results
###################################################################################################################### # Set the initial condition of the mixture P = 1.1017E5 # pressure unit: Pa T = 299.05 # temperature unit: K mix_comp = 'IC8H18:1, O2:12.5, AR:75' # mixture composition. Molar ratio. # Input the ic8 model mixture = ct.Solution('ic8mech.xml') # Set the state of the reactive mixture mixture.TPX = T, P, mix_comp air = ct.Solution('air.xml') mixture_unr = ct.Solution('ic8mech_unr.xml') # Set the state of the unreactive mixture mixture_unr.TPX = T, P, mix_comp r1 = ct.IdealGasReactor(mixture) # Set core zone r2 = ct.IdealGasReactor(mixture_unr) # Set boundary layer zone r3 = ct.IdealGasReactor(mixture_unr) # Set crevice zone v1 = ct.Valve(r1,r2) # Add a valve between the main combustion chamber and the boundary layer v2 = ct.Valve(r2,r3) # Add a valve between the boundary layer and the crevice Env = ct.Reservoir(air) # Set the environment w1 = ct.Wall(Env, r1) w2 = ct.Wall(Env, r2) w3 = ct.Wall(Env, r3) ###################################################################################################################### # v1.set_valve_coeff(5E-6) # Set mass flow rate for valve1
def Engine(fname): import sys import numpy as np import cantera as ct import matplotlib.pyplot as plt import csv import time # from scipy.interpolate import interp1d import math from openpyxl import load_workbook def volume(t): theta = t * omega + IVC * np.pi / 180 #theta in radian; TDC corresponds to theta=0 v = V_min * (1 + ((r_c - 1) / 2) * (L / a + 1 - np.cos(theta) - np.sqrt(pow(L / a, 2) - pow(np.sin(theta), 2)))) return v def surfA(t): area = np.pi * math.pow(D, 2) / 4 SA = 2 * area + np.pi * D * volume(t) / area return SA def vel(t): #Note - the only input to the passed through function is time theta = t * omega + IVC * np.pi / 180 #TDC corresponds to theta=0 z = np.sqrt(pow(L / a, 2) - pow(np.sin(theta), 2)) v = omega * (V_min / A_p) * ( (r_c - 1) / 2) * np.sin(theta) * (1 + np.cos(theta) / z) return v def qfluxB( t): #Note - the only input to the passed through function is time rho = m_t / volume(t) T = (unb.mass * unb.thermo.T + bur.mass * bur.thermo.T) / (unb.mass + bur.mass) k = k_0 * pow(T, n_k) #W/m-K, for air mu = mu_0 * pow(T, n_mu) #kg/m-s, for air Re = rho * MPS * D / mu Nu = Nu_0 * pow(Re, n_Nu) h = Nu * k / D area = surfA(t) * bur.volume / volume(t) q = h * area * (bur.thermo.T - T_w) * HeatOn return q def qfluxU( t): #Note - the only input to the passed through function is time rho = m_t / volume(t) T = (unb.mass * unb.thermo.T + bur.mass * bur.thermo.T) / (unb.mass + bur.mass) k = k_0 * pow(T, n_k) #W/m-K, for air mu = mu_0 * pow(T, n_mu) #kg/m-s, for air Re = rho * MPS * D / mu Nu = Nu_0 * pow(Re, n_Nu) h = Nu * k / D area = surfA(t) * unb.volume / volume(t) q = h * area * (unb.thermo.T - T_w) / A_p * HeatOn return q def mflow( t): #Note - the only input to the passed through function is time theta = t * omega + IVC * np.pi / 180 #TDC corresponds to theta=0 if (theta >= theta_0r) and (theta <= theta_99r): # dxb=1/Deltathetar dxb = b * (m + 1) / Deltathetar * pow( ((theta - theta_0r) / Deltathetar), m) * math.exp(-b * pow( ((theta - theta_0r) / Deltathetar), m + 1)) else: dxb = 0 mf = burnflag * m_t * omega * dxb #mass flow unb to bur return mf ## MAIN - read inputs wb = load_workbook(fname) ws = wb.active r_c = ws['A1'].value # compression ratio L = ws['A2'].value # con rod length [m] D = ws['A3'].value # bore [m] stroke = ws['A4'].value # stroke [m] IVC = ws['A5'].value # IVC crank angle where TDC = 0 EVO = ws['A6'].value # EVO crank angle where TDC = 0 CrevOn = ws['A7'].value # Flag to determine whether to use crevice model HeatOn = ws[ 'A8'].value # Flag to determine whether to use heat transfer model crevfrac = ws['A9'].value # crevice volume fraction of minimum volume RPM = ws['A10'].value # engine speed b = ws['A11'].value # Wiebe b parameter m = ws['A12'].value # Wiebe m parameter theta_0 = ws['A13'].value # Wiebe combustion start parameter Deltatheta = ws['A14'].value # Wiebe combustion duration parameter eta_c = ws['A15'].value # combustion efficiency T_IVC = ws['A16'].value # IVC temperature [K] P_IVC = ws['A17'].value # IVC pressure [bar] T_w = ws['A18'].value # wall temperature [K] RON = ws['A19'].value # fuel RON MON = ws['A20'].value # fuel MON Phi = ws['A21'].value # equivalence ratio burnflag = ws[ 'A22'].value # fraction of mass transferred out of unburned zone k_0 = ws['A23'].value # thermal conductivity [W/m-K] n_k = ws['A24'].value # thermal conductivity temperature exponent mu_0 = ws['A25'].value # dynamic viscosity [kg/m-s] n_mu = ws['A26'].value # viscosity temperature exponent Nu_0 = ws['A27'].value # Nusselt number correlation scaling constant n_Nu = ws[ 'A28'].value # Nusselt number correlation Reynolds number exponent Chemflag = ws['A29'].value # End gas chemistry flag; 1=on Y_EGR = ws['A30'].value # EGR mass fraction Y_f_ref = ws['A31'].value # mass fraction of FUEL to the reformer Phi_ref = ws['A32'].value # reformer equivalence ratio Ref_comp = ws[ 'A33'].value # reformer composition: 0=PCI; 1=equilibriu; 2=ideal mechanism = ws['A34'].value # kinetic mechanism ## Engine geometry calculations a = stroke / 2 # crank radius A_p = np.pi * D * D / 4 # piston area [m^2] V_disp = A_p * stroke # displacement volume [m^3] V_min = V_disp / (r_c - 1) # TDC volume [m^3] V_max = V_min + V_disp # BDC volume [m^3] MPS = 2 * stroke * RPM / 60 #mean piston speed [m/s] ## Engine operating conditions theta_0r = theta_0 * np.pi / 180 #start of combustion in radians Deltathetar = Deltatheta * np.pi / 180 #combustion duration in radians theta_99r = theta_0r + Deltathetar * math.pow( -math.log(0.01) / b, 1 / m) #99% of mass burn for Wiebe omega = RPM * 2 * np.pi / 60 #rotation rate in radian per second based on RPM T_0 = T_IVC # K P_0 = 1e5 * P_IVC # Pa ct.suppress_thermo_warnings() env = ct.Solution('air.xml') env.TP = T_w, P_0 gas = ct.Solution(mechanism) x_init = np.zeros(gas.X.shape) c4 = ws['C41'].value #0.01 #n-butane c5 = ws['C42'].value #0.0 #iso-pentane c5_2 = ws['C43'].value #0.04 #n-pentane ic8 = ws['C44'].value #0.93 #Iso-octane c6 = ws['C45'].value #0.0 #added for 1-hexene nc7 = ws['C46'].value #0.0 #n-heptane c7_2 = ws['C47'].value #0.0 #tolune tmb = ws['C48'].value #0.02 #1,2,4 Trimethyl benzene eth = ws['C49'].value #0 #Ethanol x_init[gas.species_index('C4H10')] = c4 x_init[gas.species_index('IC5H12')] = c5 x_init[gas.species_index('NC5H12')] = c5_2 x_init[gas.species_index('C6H12-1')] = c6 x_init[gas.species_index('NC7H16')] = nc7 x_init[gas.species_index('C6H5CH3')] = c7_2 x_init[gas.species_index('IC8')] = ic8 x_init[gas.species_index('T124MBZ')] = tmb x_init[gas.species_index('C2H5OH')] = eth # x_init[gas.species_index('NO')]=150e-6 #150 ppm NO ## Determine global composition nC = (4 * c4) + (5 * c5) + (5 * c5_2) + (6 * c6) + (7 * nc7) + ( 8 * ic8) + (9 * tmb) + (2 * eth) + (7 * c7_2) nH = (10 * c4) + (12 * c5) + (12 * c5_2) + (12 * c6) + (16 * nc7) + ( 18 * ic8) + (12 * tmb) + (6 * eth) + (8 * c7_2) nO = 1 * eth x_init[gas.species_index('O2')] = (nC + nH / 4 - nO / 2) / Phi x_init[gas.species_index('N2')] = 3.76 * (nC + nH / 4 - nO / 2) / Phi gas.TPX = 300, 1e5, x_init y_global = gas.Y ## Determine EGR composition keep = np.zeros(gas.X.shape) keep[gas.species_index('N2')] = 1 keep[gas.species_index('O2')] = 1 keep[gas.species_index('CO2')] = 1 keep[gas.species_index('CO')] = 1 keep[gas.species_index('H2O')] = 1 keep[gas.species_index('H2')] = 1 gas.TPY = 300, 1e5, y_global gas.equilibrate('HP') #find equilibrium concentration y_EGR = gas.Y * keep # y_EGR[7:86]=0; #set minor species to zero; based on MECHANISM gas.Y = y_EGR #use Cantera to renormalize mass fraction y_EGR = gas.Y y_eng = 1 / (1 + Y_EGR) * y_global + +Y_EGR / (1 + Y_EGR) * y_EGR gas.TPX = 298, 1e5, x_init delta_uf = gas.standard_int_energies_RT * (ct.gas_constant * 298) / gas.molecular_weights tmax = (EVO - IVC) / 6 / RPM # time for one revolution step = 8 * (EVO - IVC) tim = np.linspace(tmax / step, tmax, step) theta = IVC + omega * tim * 180 / np.pi ## Cantera reactor setup gas.TPY = T_0, P_0, y_eng #reactants # gas.set_multiplier(1) #option to make reactants inert unb = ct.IdealGasReactor(gas) if Chemflag == 0: unb.chemistry_enabled = False gas.TPY = T_0, P_0, y_eng #products gas.equilibrate( 'HP' ) #make the products hot so that mass will burn when it moves into products bur = ct.IdealGasReactor(gas) r3 = ct.Reservoir(env) #outside world gas.TPY = T_w, P_0, y_eng crev = ct.IdealGasReactor(gas) #crevices crev.chemistry_enabled = False #no reaction in crevices piston = ct.Wall(unb, r3, velocity=vel, A=A_p, Q=qfluxU) flame = ct.Wall( unb, bur, K=0.01, A=A_p) #expansion rate K set to lowest value possible to have const P topland = ct.Wall( crev, r3, U=1e3) #heat xfer rate set to keep close to wall temperature head = ct.Wall(bur, r3, A=1, Q=qfluxB) mfc = ct.MassFlowController(unb, bur, mdot=mflow) V1 = ct.Valve(unb, crev) V1.set_valve_coeff(1e-6 * CrevOn) V2 = ct.Valve(crev, bur) V2.set_valve_coeff(1e-6 * CrevOn) sim = ct.ReactorNet([unb, bur, crev]) sim.atol = 1e-12 sim.rtol = 1e-6 initvol = 1e-3 unb.volume = (1 - initvol) * volume(0) bur.volume = initvol * volume(0) crev.volume = crevfrac * V_min m_t = unb.mass + bur.mass + crev.mass vol = np.zeros(step) T = np.zeros(step) vol2 = np.zeros(step) P = np.zeros(step) outdat = np.zeros((step, 13)) SpecMatrix = np.zeros((step, gas.X.shape[0])) #burnflag=0.82 #flag to stop combustion if unburned mass gets too low # y_old=unb.thermo.Y for i in range(step): #step): if math.fmod(i, 32) == 0: print(i / step) sim.advance(tim[i]) # burnflag=unb.mass/m_t # if unb.mass/m_t < 1-eta_c: # burnflag=0 outdat[i, 0] = IVC + tim[i] * RPM / 60 * 360 outdat[i, 1] = bur.thermo.P / 1e5 outdat[i, 2] = bur.thermo.T outdat[i, 3] = bur.mass / m_t outdat[i, 4] = qfluxB(tim[i]) outdat[i, 5] = unb.thermo.T outdat[i, 6] = qfluxU(tim[i]) * A_p outdat[i, 7] = unb.mass / m_t outdat[i, 8] = crev.mass / m_t outdat[i, 9] = volume(sim.time) outdat[i, 10] = -(unb.kinetics.net_production_rates * unb.thermo.molecular_weights ).dot(delta_uf) * unb.volume * 60 / (RPM * 360) outdat[i, 11] = m_t outdat[i, 12] = ct.gas_constant / gas.mean_molecular_weight outdat[i, 13] = volume(tim[i]) SpecMatrix[i] = gas.X # outdat[i,10]=-(unb.thermo.Y-y_old).dot(delta_uf)/(tim[2]-tim[1])*unb.mass # y_old=unb.thermo.Y return outdat, SpecMatrix
def find_ignition_delay( solution, conditions=None, condition_type='adiabatic-constant-volume', output_profile=False, output_species=True, output_reactions=True, output_directional_reactions=False, output_rop_roc=False, temp_final=965, time_final=1000, skip_data=150, ): """ This method finds the ignition delay of a cantera solution object with an option to return all species and reactions as a pandas.DataFrame object which can be stored. The method calculates ignition delay by going until the temperature is near `temp_final`, and then it locates the maximum change in temperature with time, $\frac{\delta T}{\delta t}$. The time value corresponding with the max is the ignition delay This method returns a tuple with ignition delay and the species and reaction data, and the rate of production and consumption (or `None` if not specified). `solution` = Cantera.Solution object `conditions` = tuple of temperature, pressure, and mole fraction initial species `condition_type` = string describing the run type, currently only 'adiabatic-constant-volume' supported `output_profile` = should the program save simulation results and output them (True), or should it just give the ignition delay (False) `output_species` = output a Series of species' concentrations `output_reactions` = output a Series of net reaction rates `output_directional_reactions` = output a Series of directional reaction rates `output_rop_roc` = output a DataFrame of species rates of consumption & production `temp_final` = the temperature which the ignition is reported `time_final` = the time to cut off the simulation if the temperature never reaches `temp_final` `skip_data` = an integer which reduces storing each point of data. storage space scales as 1/`skip_data` """ if conditions is not None: solution.TPX = conditions if condition_type == 'adiabatic-constant-volume': reactor = ct.IdealGasReactor(solution) simulator = ct.ReactorNet([reactor]) solution = reactor.kinetics else: raise NotImplementedError( 'only adiabatic constant volume is supported') # setup data storage outputs = {} if output_profile: outputs['conditions'] = pd.DataFrame() if output_species: outputs['species'] = pd.DataFrame() if output_reactions: outputs['net_reactions'] = pd.DataFrame() if output_directional_reactions: outputs['directional_reactions'] = pd.DataFrame() if output_rop_roc: outputs['rop'] = pd.DataFrame() # run simulation max_time = time_final old_time = -1 old_temp = reactor.T max_dTdt = 0 max_dTdt_time = 0 data_storage = 1e8 # large number to ensure first data point taken while simulator.time < time_final: simulator.step() if data_storage > skip_data: data_storage = 1 if time_final == max_time and reactor.T > temp_final: time_final = simulator.time * 1.01 # go just beyond the final temperature if output_profile: outputs['conditions'] = outputs['conditions'].append( get_conditions_series(simulator, reactor, solution), ignore_index=True) if output_species: outputs['species'] = outputs['species'].append( get_species_series(solution), ignore_index=True) if output_reactions: outputs['net_reactions'] = outputs['net_reactions'].append( get_reaction_series(solution), ignore_index=True) if output_directional_reactions: outputs['directional_reactions'] = outputs[ 'directional_reactions'].append( get_forward_and_reverse_reactions_series(solution), ignore_index=True) if output_rop_roc: outputs['rop'] = outputs['rop'].append( get_rop_and_roc_series(solution), ignore_index=True) # find ignition delay dTdt = (reactor.T - old_temp) / (simulator.time - old_time) if dTdt > max_dTdt: max_dTdt = dTdt max_dTdt_time = simulator.time old_temp = reactor.T old_time = simulator.time data_storage += 1 # set indexes as time if output_profile: time_vector = outputs['conditions']['time (s)'] for output in outputs.values(): output.set_index(time_vector, inplace=True) # save ignition_delay outputs['ignition_delay'] = max_dTdt_time return outputs
def run_simulation_till_conversion( solution, species, conversion, conditions=None, condition_type='adiabatic-constant-volume', output_species=True, output_reactions=True, output_directional_reactions=False, output_rop_roc=False, skip_data=150, atol=1e-15, rtol=1e-9, ): """ This method iterates through the cantera solution object and outputs information about the simulation as a pandas.DataFrame object. This method returns a dictionary with the reaction conditions data, species data, net reaction data, forward/reverse reaction data, and the rate of production and consumption (or `None` if a variable not specified) at the specified conversion value. `solution` = Cantera.Solution object `conditions` = tuple of temperature, pressure, and mole fraction initial species `species` = a string of the species label (or list of strings) to be used in conversion calculations `conversion` = a float of the fraction conversion to stop the simulation at `condition_type` = string describing the run type, currently supports 'adiabatic-constant-volume' and 'constant-temperature-and-pressure' `output_species` = output a Series of species' concentrations `output_reactions` = output a Series of net reaction rates `output_directional_reactions` = output a Series of directional reaction rates `output_rop_roc` = output a DataFrame of species rates of consumption & production `skip_data` = an integer which reduces storing each point of data. storage space scales as 1/`skip_data` """ if conditions is not None: solution.TPX = conditions if condition_type == 'adiabatic-constant-volume': reactor = ct.IdealGasReactor(solution) if condition_type == 'constant-temperature-and-pressure': reactor = ct.IdealGasConstPressureReactor(solution, energy='off') else: raise NotImplementedError( 'only adiabatic constant volume is supported') simulator = ct.ReactorNet([reactor]) solution = reactor.kinetics simulator.atol = atol simulator.rtol = rtol # setup data storage outputs = {} outputs['conditions'] = pd.DataFrame() if output_species: outputs['species'] = pd.DataFrame() if output_reactions: outputs['net_reactions'] = pd.DataFrame() if output_directional_reactions: outputs['directional_reactions'] = pd.DataFrame() if output_rop_roc: outputs['rop'] = pd.DataFrame() if isinstance(species, str): target_species_indexes = [solution.species_index(species)] else: # must be a list or tuple target_species_indexes = [solution.species_index(s) for s in species] starting_concentration = sum([ solution.concentrations[target_species_index] for target_species_index in target_species_indexes ]) proper_conversion = False new_conversion = 0 skip_count = 1e8 while not proper_conversion: error_count = 0 while error_count >= 0: try: simulator.step() error_count = -1 except: error_count += 1 if error_count > 10: print( 'Might not be possible to achieve conversion at T={0}, P={1}, with concentrations of {2} obtaining a conversion of {3} at time {4} s.' .format(solution.T, solution.P, zip(solution.species_names, solution.X), new_conversion, simulator.time)) raise new_conversion = 1 - sum([ solution.concentrations[target_species_index] for target_species_index in target_species_indexes ]) / starting_concentration if new_conversion > conversion: proper_conversion = True # save data if skip_count > skip_data or proper_conversion: skip_count = 0 outputs['conditions'] = outputs['conditions'].append( get_conditions_series(simulator, reactor, solution), ignore_index=True) if output_species: outputs['species'] = outputs['species'].append( get_species_series(solution), ignore_index=True) if output_reactions: outputs['net_reactions'] = outputs['net_reactions'].append( get_reaction_series(solution), ignore_index=True) if output_directional_reactions: outputs['directional_reactions'] = outputs[ 'directional_reactions'].append( get_forward_and_reverse_reactions_series(solution), ignore_index=True) if output_rop_roc: outputs['rop'] = outputs['rop'].append( get_rop_and_roc_series(solution), ignore_index=True) skip_count += 1 # set indexes as time time_vector = outputs['conditions']['time (s)'] for output in outputs.values(): output.set_index(time_vector, inplace=True) return outputs
def run_simulation(solution, times, conditions=None, condition_type='adiabatic-constant-volume', output_species=True, output_reactions=True, output_directional_reactions=False, output_rop_roc=False, atol=1e-15, rtol=1e-9, temperature_values=None): """ This method iterates through the cantera solution object and outputs information about the simulation as a pandas.DataFrame object. This method returns a dictionary with the reaction conditions data, species data, net reaction data, forward/reverse reaction data, and the rate of production and consumption (or `None` if a variable not specified). `solution` = Cantera.Solution object `conditions` = tuple of temperature, pressure, and mole fraction initial species (will be deprecated. Set parameters before running) `times` = an iterable of times which you would like to store information in `condition_type` = string describing the run type `output_species` = output a DataFrame of species' concentrations `output_reactions` = output a DataFrame of net reaction rates `output_directional_reactions` = output a DataFrame of directional reaction rates `output_rop_roc` = output a DataFrame of species rates of consumption & production condition_types supported ######################### 'adiabatic-constant-volume' - assumes no heat transfer and no volume change 'constant-temperature-and-pressure' - no solving energy equation or changing rate constants 'constant-temperature-and-volume' - no solving energy equation but allows for pressure to change with reactions 'specified-temperature-constant-volume' - the temperature profile specified `temperature_values`, which corresponds to the input `times`, alters the temperature right before the next time step is taken. Constant volume is assumed. """ if conditions is not None: solution.TPX = conditions if condition_type == 'adiabatic-constant-volume': reactor = ct.IdealGasReactor(solution) elif condition_type == 'constant-temperature-and-pressure': reactor = ct.IdealGasConstPressureReactor(solution, energy='off') elif condition_type == 'constant-temperature-and-volume': reactor = ct.IdealGasReactor(solution, energy='off') elif condition_type == 'specified-temperature-constant-volume': reactor = ct.IdealGasReactor(solution, energy='off') if temperature_values is None: raise AttributeError( 'Must specify temperature with `temperature_values` parameter') elif len(times) != len(temperature_values): raise AttributeError( '`times` (len {0}) and `temperature_values` (len {1}) must have the same length.' .format(len(times), len(temperature_values))) else: supported_types = [ 'adiabatic-constant-volume', 'constant-temperature-and-pressure', 'constant-temperature-and-volume', 'specified-temperature-constant-volume' ] raise NotImplementedError('only {0} are supported. {1} input'.format( supported_types, condition_type)) simulator = ct.ReactorNet([reactor]) solution = reactor.kinetics simulator.atol = atol simulator.rtol = rtol # setup data storage outputs = {} outputs['conditions'] = pd.DataFrame() if output_species: outputs['species'] = pd.DataFrame() if output_reactions: outputs['net_reactions'] = pd.DataFrame() if output_directional_reactions: outputs['directional_reactions'] = pd.DataFrame() if output_rop_roc: outputs['rop'] = pd.DataFrame() for time_index, time in enumerate(times): if condition_type == 'specified-temperature-constant-volume': solution.TD = temperature_values[time_index], solution.density reactor = ct.IdealGasReactor(solution, energy='off') simulator = ct.ReactorNet([reactor]) solution = reactor.kinetics simulator.atol = atol simulator.rtol = rtol if time_index > 0: simulator.set_initial_time(times[time_index - 1]) simulator.advance(time) # save data outputs['conditions'] = outputs['conditions'].append( get_conditions_series(simulator, reactor, solution), ignore_index=True) if output_species: outputs['species'] = outputs['species'].append( get_species_series(solution), ignore_index=True) if output_reactions: outputs['net_reactions'] = outputs['net_reactions'].append( get_reaction_series(solution), ignore_index=True) if output_directional_reactions: outputs['directional_reactions'] = outputs[ 'directional_reactions'].append( get_forward_and_reverse_reactions_series(solution), ignore_index=True) if output_rop_roc: outputs['rop'] = outputs['rop'].append( get_rop_and_roc_series(solution), ignore_index=True) # set indexes as time time_vector = outputs['conditions']['time (s)'] for output in outputs.values(): output.set_index(time_vector, inplace=True) return outputs
#oxidizer tank oxidizer_P = 40 * ct.one_atm oxidizer_T = 293.0 oxidizier_X = 'O2:1.0' gas.TPX = oxidizer_T, oxidizer_P, oxidizier_X #oxigen oxidizer = ct.Reservoir(gas) oxidizer_mw = gas.mean_molecular_weight oxidizer_k = gas.cp / gas.cv #zrodlo zaplonu gas.TPX = 300.0, ct.one_atm, 'H:1.0' igniter = ct.Reservoir(gas) #komora spalania silnika gas.TPX = 300.0, 1.1 * ct.one_atm, 'O2:1.0' #komora spalania jest poczatkowo wypelniona azotem combustion_chamber = ct.IdealGasReactor(gas) combustion_chamber.volume = 0.0005 #wylot spalin gas.TPX = 300.0, 1 * ct.one_atm, 'N2:1.0' exhaust = ct.Reservoir(gas) exaustP = ct.one_atm #defining of necessary funciotns def kappa(gas): return gas.cp / gas.cv def critical_flow(gasin, gasinP, gasinT, gasinmw, k, gasoutP, area): R = ct.gas_constant / gasinmw
""" Constant-pressure, adiabatic kinetics simulation with sensitivity analysis """ import sys import numpy as np import cantera as ct gri3 = ct.Solution('gri30.xml') temp = 1500.0 pres = ct.one_atm gri3.TPX = temp, pres, 'CH4:0.1, O2:2, N2:7.52' r = ct.IdealGasReactor(gri3) air = ct.Solution('air.xml') air.TP = temp, pres env = ct.Reservoir(air) # Define a wall between the reactor and the environment, and make it flexible, # so that the pressure in the reactor is held at the environment pressure. w = ct.Wall(r, env) w.expansion_rate_coeff = 1.0e6 # set expansion parameter. dV/dt = KA(P_1 - P_2) w.area = 1.0 sim = ct.ReactorNet([r]) # enable sensitivity with respect to the rates of the first 10 # reactions (reactions 0 through 9) for i in range(10):
def simulate_isobar_adiabatic(input_file, initial_mole_fractions, *args, record_period=1, rtol=None): """ Constant-pressure, adiabatic kinetics simulation with Cantera: Simulation of chemical kinetics in an ideally stirred, isobar and adiabatic reactor. It takes an Cantera input file which defines a phase, chemical species and chemical reactions and runs an isobar and adiabatic time dependent kinetics simulation with the defined reaction system. The initial concentrations of the chemical species are defined as mole fractions. The given mole fractions are taken as relative values and are normalized by Cantera. The absolute concentrations are calculated from the thermodynamic state (temperature, pressure etc.) and the thermodynamic model of the phase in the reactor. Typically an ideal gas is specified by the input file. The initial mole fractions are set by ``initial_mole_fractions`` which is a string in the format expected by Cantera as mole fraction initialization. The format is a comma (``,``) separated list of substance identifiers with mole fraction values separated by a colon (``:``). For example, if a simulation defines a simulation with water ``H2O``, nitrogen ``N2`` and protnated water ions ``H3O+``, a valid concentration initalization string would be: .. code-block:: shell 'H2O:2.5e+14, N2:2.54e+17, H3O+:1e+10' .. note:: Species can be omitted in the initialization. Omitted species are initalized with no concentration. Call signatures: .. code-block:: python simulate_isobar_adiabatic(input_file, initial_mole_fractions, n_steps, dt, pressure, record_period=1, rtol=None) simulate_isobar_adiabatic(input_file, initial_mole_fractions, custom_steps, pressure, record_period=1, rtol=None) :param input_file: Path to a configuration (.cti) file :type input_file: path :param initial_mole_fractions: Inital mole fraction configuration :type initial_mole_fractions: str :param n_steps: Number of time steps to simulate :type n_steps: int :param dt: Length of a time step :type dt: float :param custom_steps: explicit time steps. The simulation will calculate the concentrations for the list of explicit time steps :type dt: list / array of floats :param pressure: Background pressure in the reaction vessel :type pressure: float :param record_period: The period with which calculated samples are written to the resulting trajectory Example: If this parameter is 10 only every 10th sample is written to the trajectory. This parameter is intended to keep trajectory sizes controllable for simulations with very fine grained time steps. :type record_period: :param rtol: Relative tolerance passed to cantera solver :type rtol: float :return: :class:`kineticsPy.base.trajectory.Trajectory` (a kinetic trajectory object) """ # Parse / Process arguments: # check if input file exists: if not os.path.isfile(input_file): raise ValueError('The given cantea file input file is not existing') if len(args) == 3: n_steps = args[0] custom_steps = None dt = args[1] pressure = args[2] elif len(args) == 2: custom_steps = args[0] n_steps = len(custom_steps) pressure = args[1] else: raise ValueError('Wrong number of arguments') sol = ct.Solution(input_file) air = ct.Solution('air.xml') sol.TPX = sol.T, pressure, initial_mole_fractions species_names = sol.species_names n_species = len(species_names) reac = ct.IdealGasReactor(sol) env = ct.Reservoir(air) # Define a wall between the reactor and the environment, and # make it flexible, so that the pressure in the reactor is held # at the environment pressure. wall = ct.Wall(reac, env) wall.expansion_rate_coeff = 1.0e0 # set expansion parameter. dV/dt = KA(P_1 - P_2) wall.area = 0.0 # Initialize simulation. sim = ct.ReactorNet([reac]) if rtol: sim.rtol = rtol n_rec_steps = int(np.ceil(n_steps / record_period)) times = np.zeros(n_rec_steps) data = np.zeros((n_rec_steps, n_species)) if custom_steps is None: time = 0.0 else: time = custom_steps[0] n_recorded = 0 for n in range(n_steps): sim.advance(time) if n % record_period == 0: times[n_recorded] = time # time in s # .concentrations of a ThermoPhase returns concentrations in [kmol/m^3], # we want to use molecules / cm^3 and have to convert: data[n_recorded, :] = reac.thermo[ species_names].concentrations * 6.022E20 n_recorded += 1 if n % 30000 == 0: print('%5d %10.3e %10.3f %10.3f %14.6e' % (n, sim.time, reac.T, reac.thermo.P, reac.thermo.u)) if custom_steps is None: time += dt elif n < n_steps - 1: time = custom_steps[n + 1] sim_attributes = {'pressure': pressure} result = Trajectory(species_names, times, data, sim_attributes) return result
air.TP = 800, 1.4e+06 #typical temperature and pressure behind HPC air_in=ct.Reservoir(air) air_mdot=ct.Quantity(air, mass=20) fuel = ct.Solution('Dagaut_Ori.cti') fuel.TPY = 300, 3e+05, 'NC10H22:0.74,PHC3H7:0.15,CYC9H18:0.11' fuel_in=ct.Reservoir(fuel) fuel_mdot = ct.Quantity(fuel, mass=1) fuel_mdot.mass=air_mdot.mass/lambda_air/14.9 #igniter (like in "combustor.py") fuel.TPX = 1500, 2e+06, 'H:1.0' igniter = ct.Reservoir(fuel) fuel.TPX = 1100, 1.2e+6, 'N2:1.0' #combustion chamber already hot, otherwise some mechanims doesn't integrate properly when combustor temperature is below 1000 K combustor = ct.IdealGasReactor(fuel, energy='on') combustor.volume = 0.2 #exhaust reservoir fuel.TPX = 300, ct.one_atm, 'N2:1.0' exhaust = ct.Reservoir(fuel) m1 = ct.MassFlowController(fuel_in, combustor, mdot=fuel_mdot.mass) m2 = ct.MassFlowController(air_in, combustor, mdot=air_mdot.mass) fwhm = 0.01 amplitude = 0.1 t0 = 0.2 igniter_mdot = lambda t: amplitude * math.exp(-(t-t0)**2 * 4 * math.log(2) / fwhm**2) m3 = ct.MassFlowController(igniter, combustor, mdot=igniter_mdot)
gas = ct.Solution('gri30.xml') # Create a Reservoir for the inlet, set to a methane/air mixture at a specified # equivalence ratio equiv_ratio = 0.5 # lean combustion gas.TP = 300.0, ct.one_atm gas.set_equivalence_ratio(equiv_ratio, 'CH4:1.0', 'O2:1.0, N2:3.76') inlet = ct.Reservoir(gas) # Create the combustor, and fill it initially with a mixture consisting of the # equilibrium products of the inlet mixture. This state corresponds to the state # the reactor would reach with infinite residence time, and thus provides a good # initial condition from which to reach a steady-state solution on the reacting # branch. gas.equilibrate('HP') combustor = ct.IdealGasReactor(gas) 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
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 pretic=time.time() if bool(self.observables) and self.kineticSens==1: ################################################################### #Block to create temp reactor network to pre-solve JSR without kinetic sens ct.suppress_thermo_warnings() tempgas=ct.Solution(self.processor.cti_path) tempgas.TPX=self.processor.solution.TPX tempfuelAirMixtureTank=ct.Reservoir(tempgas) tempexhaust=ct.Reservoir(tempgas) tempstirredReactor=ct.IdealGasReactor(tempgas,energy=self.energycon, volume=self.reactor_volume) tempmassFlowController=ct.MassFlowController(upstream=tempfuelAirMixtureTank, downstream=tempstirredReactor, mdot=tempstirredReactor.mass/self.residence_time) tempPressureRegulator=ct.Valve(upstream=tempstirredReactor,downstream=tempexhaust, K=pressureValveCoefficient) tempreactorNetwork=ct.ReactorNet([tempstirredReactor]) tempreactorNetwork.rtol = self.rtol tempreactorNetwork.atol = self.atol print(self.rtol,self.atol) tempreactorNetwork.advance_to_steady_state() ################################################################### #reactorNetwork.advance_to_steady_state() #reactorNetwork.reinitialize() elif 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') pretoc=time.time() print('Presolving Took {:3.2f}s to compute'.format(pretoc-pretic)) fuelAirMixtureTank=ct.Reservoir(self.processor.solution) exhaust=ct.Reservoir(self.processor.solution) if bool(self.observables) and self.kineticSens==1: stirredReactor=ct.IdealGasReactor(tempgas,energy=self.energycon, volume=self.reactor_volume) else: stirredReactor=ct.IdealGasReactor(self.processor.solution,energy=self.energycon, volume=self.reactor_volume) #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) reactorNetwork.rtol_sensitivity=0.0000001 reactorNetwork.atol_sensitivity=0.00000001 print('Sens tols:'+str(reactorNetwork.atol_sensitivity)+', '+str(reactorNetwork.rtol_sensitivity)) # now compile a list of all variables for which we will store data columnNames = [stirredReactor.component_name(item) for item in range(stirredReactor.n_vars)] columnNames = ['pressure'] + columnNames # use the above list to create a DataFrame timeHistory = pd.DataFrame(columns=columnNames) # Start the stopwatch tic = time.time() reactorNetwork.rtol = self.rtol reactorNetwork.atol = self.atol #reactorNetwork.max_err_test_fails= 10000 #print(reactorNetwork.max_err_test_fails) if self.physicalSens==1 and bool(self.observables)==False: #except: print('Please supply a non-empty list of observables for sensitivity analysis or set physical_sens=0') #Establish a matrix to hold sensitivities for kinetic parameters, along with tolerances if self.kineticSens==1 and bool(self.observables): #senscolumnNames = ['Reaction']+observables senscolumnNames = self.observables #sensArray = pd.DataFrame(columns=senscolumnNames) #senstempArray = np.zeros((gas.n_reactions,len(observables))) dfs = [pd.DataFrame() for x in range(len(self.observables))] #tempArray = [np.zeros(self.processor.solution.n_reactions) for x in range(len(self.observables))] #stirredReactor.thermo.X=tempstirredReactor.thermo.X posttic=time.time() # for steps in range(10): # reactorNetwork.step() reactorNetwork.advance_to_steady_state() posttoc=time.time() print('Main Solver Took {:3.2f}s to compute'.format(posttoc-posttic)) final_pressure=stirredReactor.thermo.P sens=reactorNetwork.sensitivities() #print(sens[:,787]) #print(gas.species_names) #print(self.observables) #print(sens) if self.kineticSens==1 and bool(self.observables): #print((pd.DataFrame(sens[0,:])).transpose()) #test=pd.concat([pd.DataFrame(),pd.DataFrame(sens[0,:]).transpose()]) #print(test) for k in range(len(self.observables)): index=gas.species_names.index(self.observables[k]) dfs[k] = dfs[k].append(((pd.DataFrame(sens[index+3,:])).transpose()),ignore_index=True) #dfs[k]=pd.concat([dfs[k],pd.DataFrame(sens[k,:]).transpose()]) #dfs[k]=pd.DataFrame(sens[k,:]).transpose() #print(dfs) toc = time.time() print('Simulation Took {:3.2f}s to compute'.format(toc-tic)+' at T = '+str(stirredReactor.T)) #print(dfs[0]) columnNames = [] #Store solution to a solution array #for l in np.arange(stirredReactor.n_vars): #columnNames.append(stirredReactor.component_name(l)) columnNames=[stirredReactor.component_name(item) for item in range(stirredReactor.n_vars)] #state=stirredReactor.get_state() state=np.hstack([stirredReactor.mass, stirredReactor.volume, stirredReactor.T, stirredReactor.thermo.X]) data=pd.DataFrame(state).transpose() data.columns=columnNames pressureDifferential = timeHistory['pressure'].max()-timeHistory['pressure'].min() if(abs(pressureDifferential/self.reactorPressure) > maxPressureRiseAllowed): #except: print("WARNING: Non-trivial pressure rise in the reactor. Adjust K value in valve") if self.kineticSens==1: numpyMatrixsksens = [dfs[dataframe].values for dataframe in range(len(dfs))] self.kineticSensitivities = np.dstack(numpyMatrixsksens) #print(np.shape(self.kineticSensitivities)) self.solution=data return (self.solution,self.kineticSensitivities) else: self.solution=data return (self.solution,[])
def combustor(): # use reaction mechanism GRI-Mech 3.0 gas = ct.Solution('gri30.xml') # create a reservoir for the fuel inlet, and set to pure methane. fuelX = 'CH4:' + eCH4.get() + ',C2H6:' + eC2H6.get() + ',N2:' + eN2.get() i = 0 while i < len(newFuelParts): name = newFuelParts[i].entryName.get() x = newFuelParts[i].entryX.get() if float(x) > 0: fuelX += ',' + name + ':' + x i += 1 gas.TPX = float(eFuelT.get()), ct.one_atm, fuelX fuel_in = ct.Reservoir(gas) fuel_mw = gas.mean_molecular_weight # use predefined function Air() for the air inlet air = ct.Solution('air.xml') air_in = ct.Reservoir(air) air_mw = air.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. gas.TPX = float(eCombustorT.get()), float(eCombustorP.get()) * ct.one_atm, 'H:1.0' igniter = ct.Reservoir(gas) # create the combustor, and fill it in initially with N2 gas.TPX = float(eCombustorT.get()), float(eCombustorP.get()) * ct.one_atm, 'N2:1.0' combustor = ct.IdealGasReactor(gas) combustor.volume = 1.0 # create a reservoir for the exhaust exhaust = ct.Reservoir(gas) # lean combustion, phi = 0.5 equiv_ratio = float(eRatio.get()) # 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 v = ct.Valve(combustor, exhaust, K=1.0) # the simulation only contains one reactor sim = ct.ReactorNet([combustor]) # take single steps to 2 s, writing the results to a CSV file for later # plotting tfinal = 2.0 tnow = 0.0 Tprev = combustor.T tprev = tnow states = ct.SolutionArray(gas, extra=['t','tres']) while tnow < tfinal: tnow = sim.step() tres = combustor.mass/v.mdot(tnow) Tnow = combustor.T if abs(Tnow - Tprev) > 1.0 or tnow-tprev > 2e-2: tprev = tnow Tprev = Tnow states.append(gas.state, t=tnow, tres=tres) states.write_csv('combustor.csv', cols=('t','T','tres','X')) #matplotlib plt.figure() plt.plot(states.t, states.T) plt.xlabel('Time [s]') plt.ylabel('Temperature [K]') plt.title('Temperature') plt.plot() plt.savefig('Temperature.png') plt.figure() plt.plot(states.t, states.P) plt.xlabel('Time [s]') plt.ylabel('Pressure [Pa]') plt.title('Pressure') plt.plot() plt.savefig('Pressure.png') # ciekawe: x[3] , 5, 6, 33, 35, 36, 37?, 38, 43, # ar - 48, C - 8, CO - 14, CO2 - 15 plt.figure() plt.plot(states.t, states.X[:, [5]]) plt.xlabel('Time [s]') plt.ylabel('Concentration H2O') plt.title('H2O') plt.plot() plt.savefig('XH2O.png') plt.figure() plt.plot(states.t, states.X[:, [15]]) plt.xlabel('Time [s]') plt.ylabel('Concentration CO2') plt.title('CO2') plt.plot() plt.savefig('XCO2.png') plt.figure() plt.plot(states.t, states.X[:, [14]]) plt.xlabel('Time [s]') plt.ylabel('Concentration CO') plt.title('CO') plt.plot() plt.savefig('XCO.png') plt.figure() plt.plot(states.t, states.X[:, [36]]) plt.xlabel('Time [s]') plt.ylabel('Concentration NO2') plt.title('NO2') plt.plot() plt.savefig('XNO2.png') print('OK!')
# Create reservoirs for the two inlet streams and for the outlet stream. The # upsteam reservoirs could be replaced by reactors, which might themselves be # connected to reactors further upstream. The outlet reservoir could be # replaced with a reactor with no outlet, if it is desired to integrate the # composition leaving the mixer in time, or by an arbitrary network of # downstream reactors. res_a = ct.Reservoir(gas_a) res_b = ct.Reservoir(gas_b) downstream = ct.Reservoir(gas_b) # Create a reactor for the mixer. A reactor is required instead of a # reservoir, since the state will change with time if the inlet mass flow # rates change or if there is chemistry occurring. gas_b.TPX = 300.0, ct.one_atm, 'O2:0.21, N2:0.78, AR:0.01' mixer = ct.IdealGasReactor(gas_b) # create two mass flow controllers connecting the upstream reservoirs to the # mixer, and set their mass flow rates to values corresponding to # stoichiometric combustion. mfc1 = ct.MassFlowController(res_a, mixer, mdot=rho_a * 2.5 / 0.21) mfc2 = ct.MassFlowController(res_b, mixer, mdot=rho_b * 1.0) # connect the mixer to the downstream reservoir with a valve. outlet = ct.Valve(mixer, downstream, K=10.0) sim = ct.ReactorNet([mixer]) # Since the mixer is a reactor, we need to integrate in time to reach steady # state sim.advance_to_steady_state()
# The plug flow reactor is represented by a linear chain of zero-dimensional # reactors. The gas at the inlet to the first one has the specified inlet # composition, and for all others the inlet composition is fixed at the # composition of the reactor immediately upstream. Since in a PFR model there # is no diffusion, the upstream reactors are not affected by any downstream # reactors, and therefore the problem may be solved by simply marching from # the first to last reactor, integrating each one to steady state. TDY = gas.TDY cov = surf.coverages print(' distance X_CH4 X_H2 X_CO') # create a new reactor gas.TDY = TDY r = ct.IdealGasReactor(gas, energy='off') 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') # use a 'Wall' object to implement the reacting surface in the reactor. # Since walls have to be installed between two reactors/reservoirs, we'll # install it between the upstream reservoir and the reactor. The area is # set to the desired catalyst area in the reactor, and surface reactions # are included only on the side facing the reactor.
def run_reactor( cti_file, t_array=[548], surf_t_array=[ 548 ], # not used, but will be for different starting temperatures p_array=[1], v_array=[2.771e-10 ], # 14*7*(140e-4)^2*π/2*0.9=0.0002771(cm^3)=2.771e-10(m^3) o2_array=[0.88], nh3_array=[0.066], rtol=1.0e-11, atol=1.0e-22, reactor_type=0, energy="off", sensitivity=False, sensatol=1e-6, sensrtol=1e-6, reactime=1e5, ): # 14 aluminum plates, each of them containing seven semi-cylindrical microchannels of 280 µm width # and 140 µm depth, 9 mm long, arranged at equal distances of 280 µm try: array_i = int(os.getenv("SLURM_ARRAY_TASK_ID")) except TypeError: array_i = 0 # get git commit hash and message rmg_model_path = "../ammonia" repo = git.Repo(rmg_model_path) date = time.localtime(repo.head.commit.committed_date) git_date = f"{date[0]}_{date[1]}_{date[2]}_{date[3]}{date[4]}" git_sha = str(repo.head.commit)[0:6] git_msg = str(repo.head.commit.message)[0:50].replace(" ", "_").replace( "'", "_").replace("\n", "") git_file_string = f"{git_date}_{git_sha}_{git_msg}" # set sensitivity string for file path name if sensitivity: sensitivity_str = "on" else: sensitivity_str = "off" # this should probably be outside of function settings = list( itertools.product(t_array, surf_t_array, p_array, v_array, o2_array, nh3_array)) # constants pi = math.pi # set initial temps, pressures, concentrations temp = settings[array_i][1] # kelvin temp_str = str(temp)[0:3] pressure = settings[array_i][2] * ct.one_atm # Pascals surf_temp = temp X_o2 = settings[array_i][4] x_O2_str = str(X_o2)[0].replace(".", "_") X_nh3 = (settings[array_i][5]) x_NH3_str = str(X_nh3)[0:11].replace(".", "_") X_he = 1 - X_o2 - X_nh3 mw_nh3 = 17.0306e-3 # [kg/mol] mw_o2 = 31.999e-3 # [kg/mol] mw_he = 4.002602e-3 # [kg/mol] o2_ratio = X_nh3 / X_o2 # O2/NH3/He: typical is concentrations_rmg = {"O2(2)": X_o2, "NH3(6)": X_nh3, "He": X_he} # initialize cantera gas and surface gas = ct.Solution(cti_file, "gas") surf = ct.Interface(cti_file, "surface1", [gas]) # initialize temperatures gas.TPX = temp, pressure, concentrations_rmg surf.TP = temp, pressure # change this to surf_temp when we want a different starting temperature for the surface # if a mistake is made with the input, # cantera will normalize the mole fractions. # make sure that we are reporting/using # the normalized values X_o2 = float(gas["O2(2)"].X) X_nh3 = float(gas["NH3(6)"].X) X_he = float(gas["He"].X) # create gas inlet inlet = ct.Reservoir(gas) # create gas outlet exhaust = ct.Reservoir(gas) # Reactor volume number_of_reactors = 1001 rradius = 1.4e-4 #140µm to 0.00014m rtotal_length = 9e-3 #9mm to 0.009m rtotal_vol = (rradius**2) * pi * rtotal_length / 2 rlength = rtotal_length / 1001 # divide totareactor total volume rvol = (rtotal_vol) / number_of_reactors # Catalyst Surface Area site_density = (surf.site_density * 1000 ) # [mol/m^2] cantera uses kmol/m^2, convert to mol/m^2 cat_area_total = rradius * 2 / 2 * pi * rtotal_length # [m^3] cat_area = cat_area_total / number_of_reactors # reactor initialization if reactor_type == 0: r = ct.Reactor(gas, energy=energy) reactor_type_str = "Reactor" elif reactor_type == 1: r = ct.IdealGasReactor(gas, energy=energy) reactor_type_str = "IdealGasReactor" elif reactor_type == 2: r = ct.ConstPressureReactor(gas, energy=energy) reactor_type_str = "ConstPressureReactor" elif reactor_type == 3: r = ct.IdealGasConstPressureReactor(gas, energy=energy) reactor_type_str = "IdealGasConstPressureReactor" # calculate the available catalyst area in a differential reactor rsurf = ct.ReactorSurface(surf, r, A=cat_area) r.volume = rvol surf.coverages = "X(1):1.0" # flow controllers one_atm = ct.one_atm FC_temp = 293.15 volume_flow = settings[array_i][3] # [m^3/s] molar_flow = volume_flow * one_atm / (8.3145 * FC_temp) # [mol/s] mass_flow = molar_flow * (X_nh3 * mw_nh3 + X_o2 * mw_o2 + X_he * mw_he ) # [kg/s] mfc = ct.MassFlowController(inlet, r, mdot=mass_flow) # 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(r, exhaust, master=mfc, K=0.01) # initialize reactor network sim = ct.ReactorNet([r]) # set relative and absolute tolerances on the simulation sim.rtol = 1.0e-8 sim.atol = 1.0e-16 ################################################# # Run single reactor ################################################# # round numbers for filepath strings so they're easier to read # temp_str = '%s' % '%.3g' % tempn cat_area_str = "%s" % "%.3g" % cat_area # if it doesn't already exist, g species_path = (os.path.dirname(os.path.abspath(__file__)) + f"/{git_file_string}/species_pictures") results_path = ( os.path.dirname(os.path.abspath(__file__)) + f"/{git_file_string}/{reactor_type_str}/energy_{energy}/sensitivity_{sensitivity_str}/{temp_str}/results" ) flux_path = ( os.path.dirname(os.path.abspath(__file__)) + f"/{git_file_string}/{reactor_type_str}/energy_{energy}/sensitivity_{sensitivity_str}/{temp_str}/flux_diagrams/{x_O2_str}/{x_NH3_str}" ) # create species folder for species pictures if it does not already exist try: os.makedirs(species_path, exist_ok=True) save_pictures(git_path=rmg_model_path, species_path=species_path) except OSError as error: print(error) try: os.makedirs(results_path, exist_ok=True) except OSError as error: print(error) try: os.makedirs(flux_path, exist_ok=True) except OSError as error: print(error) gas_ROP_str = [i + " ROP [kmol/m^3 s]" for i in gas.species_names] # surface ROP reports gas and surface ROP. these values are not redundant gas_surf_ROP_str = [ i + " surface ROP [kmol/m^2 s]" for i in gas.species_names ] surf_ROP_str = [i + " ROP [kmol/m^2 s]" for i in surf.species_names] gasrxn_ROP_str = [ i + " ROP [kmol/m^3 s]" for i in gas.reaction_equations() ] surfrxn_ROP_str = [ i + " ROP [kmol/m^2 s]" for i in surf.reaction_equations() ] output_filename = ( results_path + f"/Spinning_basket_area_{cat_area_str}_energy_{energy}" + f"_temp_{temp}_O2_{x_O2_str}_NH3_{x_NH3_str}.csv") outfile = open(output_filename, "w") writer = csv.writer(outfile) # Sensitivity atol, rtol, and strings for gas and surface reactions if selected # slows down script by a lot if sensitivity: sim.rtol_sensitivity = sensrtol sim.atol_sensitivity = sensatol sens_species = [ "NH3(6)", "O2(2)", "N2(4)", "NO(5)", "N2O(7)" ] #change THIS to your species, can add "," and other species # turn on sensitive reactions for i in range(gas.n_reactions): r.add_sensitivity_reaction(i) for i in range(surf.n_reactions): rsurf.add_sensitivity_reaction(i) # thermo sensitivities. leave off for now as they can cause solver crashes # for i in range(gas.n_species): # r.add_sensitivity_species_enthalpy(i) # for i in range(surf.n_species): # rsurf.add_sensitivity_species_enthalpy(i) for j in sens_species: gasrxn_sens_str = [ j + " sensitivity to " + i for i in gas.reaction_equations() ] surfrxn_sens_str = [ j + " sensitivity to " + i for i in surf.reaction_equations() ] # gastherm_sens_str = [j + " thermo sensitivity to " + i for i in gas.species_names] # surftherm_sens_str = [j + " thermo sensitivity to " + i for i in surf.species_names] sens_list = gasrxn_sens_str + surfrxn_sens_str # + gastherm_sens_str writer.writerow([ "Distance (mm)", "T (C)", "P (Pa)", "V (M^3/s)", "X_nh3 initial", "X_o2 initial", "X_he initial", "(NH3/O2)", "T (C) final", "Rtol", "Atol", "reactor type", "energy on?" ] + gas.species_names + surf.species_names + gas_ROP_str + gas_surf_ROP_str + surf_ROP_str + gasrxn_ROP_str + surfrxn_ROP_str + sens_list) else: writer.writerow([ "Distance (mm)", "T (C)", "P (Pa)", "V (M^3/s)", "X_nh3 initial", "X_o2 initial", "X_he initial", "(NH3/O2)", "T (C) final", "Rtol", "Atol", "reactor type", "energy on?" ] + gas.species_names + surf.species_names + gas_ROP_str + gas_surf_ROP_str + surf_ROP_str + gasrxn_ROP_str + surfrxn_ROP_str) t = 0.0 dt = 0.1 iter_ct = 0 # run the simulation first_run = True distance_mm = 0 for n in range(number_of_reactors): # Set the state of the reservoir to match that of the previous reactor gas.TDY = TDY = r.thermo.TDY inlet.syncState() sim.reinitialize() previous_coverages = surf.coverages # in case we want to retry if n > 0: # Add a first row in the CSV with just the feed try: sim.advance_to_steady_state() except ct.CanteraError: t = sim.time sim.set_initial_time(0) gas.TDY = TDY surf.coverages = previous_coverages r.syncState() sim.reinitialize() new_target_time = 0.01 * t logging.warning( f"Couldn't reach {t:.1g} s so going to try {new_target_time:.1g} s" ) try: sim.advance(new_target_time) except ct.CanteraError: outfile.close() raise # save flux diagrams at beginning of run if first_run == True: save_flux_diagrams(gas, suffix=flux_path, timepoint="beginning", species_path=species_path) save_flux_diagrams(surf, suffix=flux_path, timepoint="beginning", species_path=species_path) first_run = False if sensitivity: # get sensitivity for sensitive species i (e.g. methanol) in reaction j for i in sens_species: g_nrxn = gas.n_reactions s_nrxn = surf.n_reactions # g_nspec = gas.n_species # s_nspec = surf.n_species gas_sensitivities = [ sim.sensitivity(i, j) for j in range(g_nrxn) ] surf_sensitivities = [ sim.sensitivity(i, j) for j in range(g_nrxn, g_nrxn + s_nrxn) ] # gas_therm_sensitivities = [sim.sensitivity(i,j) # for j in range(g_nrxn+s_nrxn,g_nrxn+s_nrxn+g_nspec)] # surf_therm_sensitivities = [sim.sensitivity(i,j) # for j in range(g_nrxn+s_nrxn+g_nspec,g_nrxn+s_nrxn+g_nspec+s_nspec)] sensitivities_all = ( gas_sensitivities + surf_sensitivities # + gas_therm_sensitivities ) writer.writerow([ distance_mm, temp, gas.P, volume_flow, X_nh3, X_o2, X_he, o2_ratio, gas.T, sim.rtol, sim.atol, reactor_type_str, energy, ] + list(gas.X) + list(surf.X) + list(gas.net_production_rates) + list(surf.net_production_rates) + list(gas.net_rates_of_progress) + list(surf.net_rates_of_progress) + sensitivities_all, ) else: writer.writerow([ distance_mm, temp, gas.P, volume_flow, X_nh3, X_o2, X_he, o2_ratio, gas.T, sim.rtol, sim.atol, reactor_type_str, energy, ] + list(gas.X) + list(surf.X) + list(gas.net_production_rates) + list(surf.net_production_rates) + list(gas.net_rates_of_progress) + list(surf.net_rates_of_progress)) iter_ct += 1 distance_mm = n * rlength * 1.0e3 # distance in mm outfile.close() # save flux diagrams at the end of the run save_flux_diagrams(gas, suffix=flux_path, timepoint="end", species_path=species_path) save_flux_diagrams(surf, suffix=flux_path, timepoint="end", species_path=species_path) return
import os import csv import numpy as np import cantera as ct #----------------------------------------------------------------------- # First create each gas needed, and a reactor or reservoir for each one. #----------------------------------------------------------------------- # create an argon gas object and set its state ar = ct.Solution('argon.xml') ar.TP = 1000.0, 20.0 * ct.one_atm # create a reactor to represent the side of the cylinder filled with argon r1 = ct.IdealGasReactor(ar) # create a reservoir for the environment, and fill it with air. env = ct.Reservoir(ct.Solution('air.xml')) # use GRI-Mech 3.0 for the methane/air mixture, and set its initial state gri3 = ct.Solution('gri30.xml') gri3.TPX = 500.0, 0.2 * ct.one_atm, 'CH4:1.1, O2:2, N2:7.52' # create a reactor for the methane/air side r2 = ct.IdealGasReactor(gri3) #----------------------------------------------------------------------------- # Now couple the reactors by defining common walls that may move (a piston) or # conduct heat #-----------------------------------------------------------------------------
def run_sim(i,solution_object, condition, sys_args='none', info=False,**usr_args): """ Function to run Cantera reactor simulation for autoigntion conditions Parameters ---------- i : int An integer value that represents which number initial condition the program is on solution_object : obj Cantera solution object condition An object contining initial conditions (temperature, pressure, mole fractions) info : boolean A boolean value that is true if additional features such as plotting or writing csv files will be used. Returns ---------- Output Plot of Temp vs Time Points of interest CSV file Hdf5 file mass_fractions.hdf5 : [initial_condition] [index] [Pressure] [Reaction Rates of Progress] [Species Mass Fractions] [Species Net Production Rates Original] [Temp] [Time] return_obj : obj sim result .time .temp .initial_temperature_array .sp_data .test (h5py object) .tau .Temp .frac Example ------- run_sim(gas_solution, points='y', plot='y', initial_sim='y') """ #Set up variables func_start_time = tm.time() solution = solution_object initial_temperature = float(condition.temperature) pressure = float(condition.pressure)*float(ct.one_atm) #Set up species fractions frac = '' for reactant in condition.species.iteritems(): if reactant[0] in solution.species_names: frac += str(reactant[0]) + ':' + str(reactant[1]) + ',' frac = frac[:-1] #Set up variables solution.TPX = initial_temperature, pressure, frac #101325 Pa species = solution.species() reactions = solution.reactions() #run sim to find ignition delay from when the tempurature first reaches 400 Kelvin above its original value. #Set up variables for the simulation. reactor = ct.IdealGasReactor(solution) #Set up reactor simulation = ct.ReactorNet([reactor]) current_time = 0.0 stop_time = 25 group_index = 0 times1 = [] temps = [] #first column is time, second is temperature mass = reactor.mass sdata = np.zeros([0, len(reactor.Y)]) production_data = np.zeros([0, len(solution.net_production_rates)]) state_list = list() #Prepare mass_fractions.hdf5 file. f1 = h5py.File('mass_fractions.hdf5', 'a') try: group_name = str(initial_temperature) + '_' + str(pressure) + '_' + str(frac) except ValueError: print "Duplicate initial conditions, or check to make sure mass fractions file isn't in directory. If it is, delete it""" individual = f1.create_group(group_name) #Run simulation timer_start =tm.time() while current_time < stop_time: group_index += 1 try: current_time = simulation.step() except Exception: error_string = 'Cantera autoignition_error @ %sK initial temperature' %initial_temperature print error_string return #Store information at this timestep times1.append(current_time) temps.append(reactor.T) species_data = reactor.Y grp = individual.create_group(str(group_index)) grp['Temp'] = reactor.T grp['Time'] = current_time grp['Pressure'] = reactor.thermo.P species_production_rates = reactor.kinetics.net_production_rates try: net_rates_of_progress = reactor.kinetics.net_rates_of_progress except Exception: return 0 net_rates_of_progress = reactor.kinetics.net_rates_of_progress grp.create_dataset('Species Mass Fractions', data=species_data) grp.create_dataset('Reaction Rates of Progress', data=net_rates_of_progress) grp.create_dataset('Species Net Production Rates Original', data=species_production_rates) species_data = species_data[:, np.newaxis].T #translate from [n, 1] to [1,n] sdata = np.vstack((sdata, species_data)) production_rates = np.array(solution.net_production_rates) production_rates = production_rates[:, np.newaxis].T production_data = np.vstack((production_data, production_rates)) #Organize information collected from the simulation sample = get_range(times1, temps, sdata, production_data) timer_stop = tm.time() #strips all data except that within a 40 point sample range around ignition for grp in f1[group_name].keys(): if int(grp) not in sample.index: f1[group_name].__delitem__(str(grp)) #utility functions. def plot(i): import matplotlib.pyplot as plt plt.clf() #plot combustion point #plt.plot(sample.derivative_max[0], sample.derivative_max[1], 'ro', ms=7, label='ignition point') #plot initial and final sample points #plt.plot(sample.initial_point[0], sample.initial_point[1], 'rx', ms=5, mew=2) #plt.plot(sample.final_point[0], sample.final_point[1], 'rx', ms=5, mew=2) #plot temp vs time plt.plot(sample.times_total, sample.temps_total) plt.xlabel('Time (s)') plt.title('Mixture Temperature vs Time') plt.ylabel('Temperature (K)') plt.axis([sample.times_total[0], sample.tau * 2, sample.temps_total[0] - 200, sample.temps_total[len(sample.temps_total) - 1] + 200]) #plt.legend() plt.savefig("fig" + "_ic" + str(i) + ".png", bbox_inches='tight') plt.close() def writecsv(sdata, i): names = str(solution.species_names) tt = ['Time (s)', 'Temp (K)'] names = solution.species_names name_array = np.append(tt, names) #sdata = sdata.astype('|S10') file_data = np.vstack((name_array, sdata)) #open and write to file input_file_name_stripped = os.path.splitext(sys_args.data_file)[0] output_file_name = os.path.join(input_file_name_stripped + '_species_data' + 'ic_' + str(i) + '.csv') with open(output_file_name, 'wb') as f: np.savetxt(f, file_data, fmt=('%+12s'), delimiter=',') #os.system('atom '+ output_file_name) def writehdf5(sdata,i): input_file_name_stripped = os.path.splitext(sys_args.data_file)[0] output_file_name = os.path.join(input_file_name_stripped + '_ic_' + str(i) + '.csv') if not (os.path.exists("./hdf5_files")): os.system("mkdir hdf5_files") os.system("cp mass_fractions.hdf5 mass_fractions_" + output_file_name) os.system("mv mass_fractions_" + output_file_name + " ./hdf5_files") os.system("cp production_rates.hdf5 production_rates_" + output_file_name) os.system("mv production_rates_" + output_file_name + " ./hdf5_files") #format matrix for hdf5 #names = str(solution.species_names) #tt = ['Time (s)', 'Temp (K)'] #names = solution.species_names #name_array = np.append(tt, names) #sdata = sdata.astype('|S10') #file_data = np.vstack((name_array, sdata)) #open and write to file #input_file_name_stripped = os.path.splitext(sys_args.data_file)[0] #output_file_name = os.path.join(input_file_name_stripped + '_species_data.hdf5') #with h5py.File(output_file_name, 'w') as f: #Times = f.create_dataset("Times", data=sample.times_total) #Temps = f.create_dataset("Temps", data=sample.temps_total) #sgroup = f.create_group('Species_Data') #for i, sp in enumerate(solution.species_names): #sgroup.create_dataset(sp, data=sdata[i]) def write_ai_times(): f = open("autoignition_times.txt", "a") f.write(str(sample.temps_total[0]) + ", " + str(sample.tau) + "\n") f.close() def points(): print("\nTime[s] Temp[K] Index Point") print(str(sample.initial_point[0]) + " " + str("{0:.2f}".format(sample.initial_point[1]))\ + " " + str(sample.initial_point[2]) + " " + "Initial sample point") print(str(sample.tau) + " " + str("{0:.2f}".format(sample.derivative_max[1])) + " " + str(sample.derivative_max[2])\ + " " + "Ignition point") print(str(sample.final_point[0]) + " " + str("{0:.2f}".format(sample.final_point[1]))\ + " " + str(sample.final_point[2]) + " " + "Final sample point") #terminal use case if sys_args is not 'none' and info: if sys_args.plot: plot(i) if sys_args.writecsv: writecsv(sample.species_data,i) if sys_args.writehdf5: writehdf5(sample.species_data,i) if sys_args.points: points() if sys_args.write_ai_times: write_ai_times() #Create and return an object that contains criticial information about the simulation. class return_obj: def __init__(self, time, temp, sp_data, f1, tau, Temp, frac): self.time = time self.temp = temp self.initial_temperature_array = [] self.sp_data = sp_data self.test = f1 self.tau = tau self.Temp = initial_temperature self.frac = frac self.tau_array = [] return return_obj(sample.times, sample.temps, sample.species_data, f1, sample.tau, initial_temperature, frac)
injector_close = 365. / 180. * np.pi injector_mass = 3.2e-5 # kg injector_t_open = (injector_close - injector_open) / 2. / np.pi / f # Simulation time and resolution sim_n_revolutions = 8. sim_n_timesteps = 100000. ################################################################### # load reaction mechanism gas = ct.Solution(reaction_mechanism) # define initial state gas.TPX = T_inlet, p_inlet, comp_inlet r = ct.IdealGasReactor(gas) # 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)
# divide the double for loop into 32*30 parallel jobs pres_idx = jobid curr_pres = ig_press_cont[pres_idx] #for n in range (0,np.size(ig_temp_cont)):# 32 jobs #print(n) #for o in range (0,np.size(ig_press_cont)):# 30 jobs tau_result = np.zeros((32)) temp_result = np.zeros((32)) pres_result = np.zeros((32)) for idx in range(len(ig_temp_cont)): curr_temp = ig_temp_cont[idx] print("job_id:{} pressure:{} temperature: {}".format( jobid, curr_pres, curr_temp)) gas.TPX = curr_temp, curr_pres, x_init r1 = ct.IdealGasReactor(gas) #Ignition Delay reactor sim2 = ct.ReactorNet([r1]) time = 0 #Initialize Time Temp_cur = 0 #ig_temp_cont[n] Press_cv = [] Temp_cv = [] timeapp = [] while (Temp_cur < curr_temp + 50 and time < 20e-3): time += 1.e-6 sim2.advance(time) time_cur = time #milliseconds Press = r1.thermo.P Temp = r1.T Temp_cur = Temp # times.append(time_cur)
# the first to last reactor, integrating each one to steady state. # (This approach is anologous to the one presented in 'surf_pfr.py', which # additionally includes surface chemistry) #%% # import the gas model and set the initial conditions ignition_delay=[] for T_0 in (i): gas2 = ct.Solution(reaction_mechanism) gas2.TPX = T_0, pressure, composition_0 mass_flow_rate2 = u_0 * gas2.density * area 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
def JSR_isothermal_stdst(Temps, gas, Pressure, concentrations, residenceTime, reactorVolume, pressureValveCoefficient=0.01, maxsimulationTime=1000): stirredReactor_list = [] gas_List = [] for i in np.arange(len(Temps)): # Inlet gas conditions reactorTemperature = Temps[i] #Kelvin reactorPressure = Pressure #in atm. gas.TPX = reactorTemperature, reactorPressure, concentrations # Reactor parameters # Instrument parameters # This is the "conductance" of the pressure valve and will determine its efficiency in # holding the reactor pressure to the desired conditions. pressureValveCoefficient = pressureValveCoefficient # This parameter will allow you to decide if the valve's conductance is acceptable. If there # is a pressure rise in the reactor beyond this tolerance, you will get a warning maxPressureRiseAllowed = 0.001 # Simulation termination criterion maxSimulationTime = maxsimulationTime # seconds fuelAirMixtureTank = ct.Reservoir(gas) exhaust = ct.Reservoir(gas) stirredReactor = ct.IdealGasReactor(gas, energy='off', volume=reactorVolume) massFlowController = ct.MassFlowController(upstream=fuelAirMixtureTank, downstream=stirredReactor, mdot=stirredReactor.mass / residenceTime) pressureRegulator = ct.Valve(upstream=stirredReactor, downstream=exhaust, K=pressureValveCoefficient) reactorNetwork = ct.ReactorNet([stirredReactor]) # now compile a list of all variables for which we will store data columnNames = [ stirredReactor.component_name(item) for item in range(stirredReactor.n_vars) ] columnNames = ['pressure'] + columnNames # use the above list to create a DataFrame timeHistory = pd.DataFrame(columns=columnNames) # Start the stopwatch tic = time.time() # Set simulation start time to zero t = 0 counter = 1 while t < maxSimulationTime: t = reactorNetwork.step() # We will store only every 10th value. Remember, we have 1200+ species, so there will be # 1200 columns for us to work with if (counter % 10 == 0): #Extract the state of the reactor state = np.hstack([ stirredReactor.thermo.P, stirredReactor.mass, stirredReactor.volume, stirredReactor.T, stirredReactor.thermo.X ]) #Update the dataframe timeHistory.loc[t] = state counter += 1 # Stop the stopwatch toc = time.time() print('Simulation Took {:3.2f}s to compute, with {} steps'.format( toc - tic, counter)) #state = np.hstack([stirredReactor.thermo.P, stirredReactor.mass, #stirredReactor.volume, stirredReactor.T, stirredReactor.thermo.X]) #for j in np.arange(len(speciesName)): #component_final_X[i,j] = stirredReactor.get_state()[stirredReactor.component_index(speciesName[j])] #component_final_X[i,j] = state[stirredReactor.component_index(speciesName[j])+1] # We now check to see if the pressure rise during the simulation, a.k.a the pressure valve # was okay pressureDifferential = timeHistory['pressure'].max( ) - timeHistory['pressure'].min() if (abs(pressureDifferential / reactorPressure) > maxPressureRiseAllowed): print( "WARNING: Non-trivial pressure rise in the reactor. Adjust K value in valve" ) stirredReactor_list.append(stirredReactor) #%matplotlib notebook #plt.style.use('ggplot') #plt.style.use('seaborn-pastel') #plt.rcParams['axes.labelsize'] = 18 #plt.rcParams['xtick.labelsize'] = 14 #plt.rcParams['ytick.labelsize'] = 14 #plt.rcParams['figure.autolayout'] = True #plt.figure() #plt.semilogx(timeHistory.index, timeHistory['B2CO'],'-o') #plt.xlabel('Time (s)') #plt.ylabel(r'Mole Fraction : $X_{CO}$'); return stirredReactor_list, gas