Пример #1
0
def solve(gas, t):
    # Set the temperature and pressure for gas
    # t is different from t0
    gas.TPX = t, pressure, composition
    surf.TP = t, pressure
    TDY = gas.TDY
    cov = surf.coverages

    # create a new reactor
    gas.TDY = TDY
    r = ct.IdealGasReactor(gas, energy='on')
    r.volume = rvol

    upstream = ct.Reservoir(gas, name='upstream')
    downstream = ct.Reservoir(gas, name='downstream')
    rsurf = ct.ReactorSurface(surf, r, A=cat_area)
    m = ct.MassFlowController(upstream, r, mdot=mass_flow_rate)

    v = ct.PressureController(r, downstream, master=m, K=1e-5)

    sim = ct.ReactorNet([r])
    sim.max_err_test_fails = 20
    sim.rtol = 1.0e-9
    sim.atol = 1.0e-21

    # define time, space, and other information vectors
    z2 = (np.arange(NReactors)) * rlen * 1e3

    t_r2 = np.zeros_like(z2)  # residence time in each reactor

    t2 = np.zeros_like(z2)
    states2 = ct.SolutionArray(gas)

    for n in range(NReactors):
        # Set the state of the reservoir to match that of the previous reactor
        gas.TDY = r.thermo.TDY
        upstream.syncState()
        sim.reinitialize()

        sim.advance_to_steady_state()
        dist = n * rlen * 1.0e3  # distance in mm

        t_r2[n] = r.mass / mass_flow_rate  # residence time in this reactor
        t2[n] = np.sum(t_r2)
        states2.append(gas.state)
    print('Temperature of Gas :', t, ' residence time :', t2[-1])
    MolFrac_CH4 = states2('CH4').X
    MolFrac_CO2 = states2('CO2').X
    MolFrac_CO = states2('CO').X
    MolFrac_H2 = states2('H2').X
    MolFrac_H2O = states2('H2O').X
    MolFrac_O2 = states2('O2').X
    kq = np.zeros(6)
    kq[0] = MolFrac_CH4[-1] * 100
    kq[1] = MolFrac_CO2[-1] * 100
    kq[2] = MolFrac_CO[-1] * 100
    kq[3] = MolFrac_H2[-1] * 100
    kq[4] = MolFrac_H2O[-1] * 100
    kq[5] = MolFrac_O2[-1] * 100
    return kq
Пример #2
0
    def test_pressure_controller_errors(self):
        self.make_reactors()
        res = ct.Reservoir(self.gas1)
        mfc = ct.MassFlowController(res, self.r1, mdot=0.6)

        p = ct.PressureController(self.r1, self.r2, master=mfc, K=0.5)

        with self.assertRaises(ct.CanteraError):
            p = ct.PressureController(self.r1, self.r2, K=0.5)
            p.mdot(0.0)

        with self.assertRaises(ct.CanteraError):
            p = ct.PressureController(self.r1, self.r2, master=mfc)
            p.mdot(0.0)

        with self.assertRaises(ct.CanteraError):
            p = ct.PressureController(self.r1, self.r2)
            p.mdot(0.0)
Пример #3
0
def cstr0(self, F, P, V, T=None, dt=2.0, t=None, disp=0):
    # mix streams if there is more than one
    self, F = mixer(self, F)
    # set initial conditions
    self.TP = T, P
    # create a new reactor
    if T == None or T == 0:
        r = ct.IdealGasReactor(self, energy='on')
    else:
        r = ct.IdealGasReactor(self, energy='off')
    r.volume = V
    # create up and downstream reservoirs
    upstream = ct.Reservoir(self)
    downstream = ct.Reservoir(self)
    # set mass flow into reactor
    m = ct.MassFlowController(upstream, r, mdot=F)
    # set valve to hold pressure constant
    ct.PressureController(r, downstream, master=m, K=1e-5)
    # create reactor network
    s = ct.ReactorNet([r])
    s.rtol, s.atol, s.max_err_test_fails = rel_tol, abs_tol, test_fails
    # set parameters
    time = 0
    residual = 1
    all_done = False
    # forces reinitialization
    s.set_initial_time(0)
    while not all_done:
        Yo = r.thermo.Y
        To = r.thermo.T
        try:
            time += dt
            s.advance(time)
        except:
            dt *= 0.1
            time += dt
            s.advance(time)
        if t != None and time >= t: all_done = False
        if time > 10 * dt:
            all_done = True
            residual = slope(Yo, r.thermo.Y, dt)
            if T == None:
                residual = np.max(np.append(residual,slope(To,r.thermo.T,dt)))
            if residual > rel_tol:
                all_done = False
                break
        if disp == True: print 'Residual: %1.3e' %(residual)
    return self, time, residual
Пример #4
0
    def test_pressure_controller(self):
        self.makeReactors(nReactors=1)
        g = ct.Solution('h2o2.xml')
        g.TPX = 500, 2*101325, 'H2:1.0'
        inletReservoir = ct.Reservoir(g)
        g.TP = 300, 101325
        outletReservoir = ct.Reservoir(g)

        mfc = ct.MassFlowController(inletReservoir, self.r1)
        mdot = lambda t: np.exp(-100*(t-0.5)**2)
        mfc.setMassFlowRate(mdot)

        pc = ct.PressureController(self.r1, outletReservoir)
        pc.setMaster(mfc)
        pc.setPressureCoeff(1e-5)

        t = 0
        while t < 1.0:
            t = self.net.step(1.0)
            self.assertNear(mdot(t), mfc.mdot(t))
            dP = self.r1.thermo.P - outletReservoir.thermo.P
            self.assertNear(mdot(t) + 1e-5 * dP, pc.mdot(t))
Пример #5
0
def compute(X):

    global combustor
    global residence_time

    x1, x2, x3, x4, x5 = X
    inject_parameters(x1, x2, x3, x4, x5)

    #导入network计算
    win32api.ShellExecute(0, 'open', 'C:\\Users\\86183\\network_flow.exe', '', '', 1)
    #停止程序执行,等待network计算结果
    time.sleep(8)
    #识别result文件中某几行的数据之和
    path = os.getcwd()+ "\\result.dat"
    f=open(path,'r')
    data=f.readlines()
    f.close()

    for i in range(len(data)):
        line=data[i].split()[1]
        data[i]=float(line)

# print(data)
    result=np.sum(data[1:2]+data[2:3])
    # print(result)
    # print(result/3)

    #CRN 化学反应网络法
    # 设置反应器气体
    gas = ct.Solution('gri30.yaml')

    # 确定油气等效比、燃气温度、燃气压强
    equiv_ratio =result/3 
    gas.TP = 300.0, 101325
    gas.set_equivalence_ratio(equiv_ratio, 'CH4:1.0', 'O2:1.0, N2:3.76')
    inlet = ct.Reservoir(gas)

# 让反应器达到平衡状态
    gas.equilibrate('HP')
    combustor = ct.IdealGasReactor(gas)
    combustor.volume = 1.0

# 设置出口排放
    exhaust = ct.Reservoir(gas)

# 计算进出口质量流量

    inlet_mfc = ct.MassFlowController(inlet, combustor, mdot=mdot)

    outlet_mfc = ct.PressureController(combustor, exhaust, master=inlet_mfc, K=0.01)

    sim = ct.ReactorNet([combustor])

# 设置驻留时间,计算驻留时间逐渐减小所对应的反应器温度
    states = ct.SolutionArray(gas, extra=['tres'])

    temperatureList=[]
    while combustor.T > 500:
        sim.set_initial_time(0.0) 
        sim.advance_to_steady_state()
        # print('tres = {:.2e}; T = {:.1f}'.format(residence_time, combustor.T))
        states.append(combustor.thermo.state, tres=residence_time)
        residence_time *= 0.9  
        temperatureList.append(combustor.T)
    # print(temperatureList)

    maxT=max(temperatureList)
    return (-1)*maxT
Пример #6
0
def simulatePFRorTPRwithCantera(model_name, canteraGasPhaseObject, canteraSurfacePhaseObject, simulation_settings_module):
    #simulation_settings_module_name must be a string if it is provided. The module itself should not be passed as an argument. This is intentional.
    canteraPhases ={}
    gas = canteraGasPhaseObject
    surf = canteraSurfacePhaseObject
    canteraPhases['gas'] = gas
    canteraPhases['surf'] = surf
       
    
    '''Now the code that handles *either* isothermal PFR or surface TPR. In future, will allow PFR TPR as well'''
    #We are going to model things as a flow reactor made of CSTRs, with no flow for the surface TPR case, following this example: https://cantera.org/examples/python/reactors/surf_pfr.py.html
    #it could probably also have been done as a flow reactor with no flow:  https://cantera.org/examples/python/surface_chemistry/catalytic_combustion.py.html
      

    
    '''START OF settings that users should change.'''
    flow_type = simulation_settings_module.flow_type
    T_gas_feed = simulation_settings_module.T_gas_feed
    T_surf = simulation_settings_module.T_surf
    velocity = simulation_settings_module.velocity
    reactor_cross_area = simulation_settings_module.reactor_cross_area
    cat_area_per_vol = simulation_settings_module.cat_area_per_vol
    porosity = simulation_settings_module.porosity
    length = simulation_settings_module.length
    P_gas = simulation_settings_module.P_gas
    gas_composition = simulation_settings_module.gas_composition
    t_step_size = simulation_settings_module.t_step_size
    t_final = simulation_settings_module.t_final
    NReactors = simulation_settings_module.NReactors
    print_frequency = simulation_settings_module.print_frequency
    heating_rate = simulation_settings_module.heating_rate
    surface_coverages =  simulation_settings_module.surface_coverages
    rtol = simulation_settings_module.rtol
    atol = simulation_settings_module.atol
    exportOutputs = simulation_settings_module.exportOutputs
    '''END OF settings that users should change.'''
    


    #Initiate concentrations output file and headers.
    if exportOutputs == True:
        concentrations_output_filename = model_name + "_output_concentrations.csv"
        outfile = open(concentrations_output_filename,'w')
        writer = csv.writer(outfile)
    concentrationsArrayHeaderList = ['Distance (m)', 'time(s)',  'T_gas (K)', 'T_surf (K)', 'P (atm)'] + \
                    gas.species_names + surf.species_names
    concentrationsArrayHeader = str(concentrationsArrayHeaderList)[1:-1] #The species names were imported when "surf" and "gas" objects were created.
    if exportOutputs == True:    
        writer.writerow(concentrationsArrayHeaderList)
    
    
    if flow_type == "Static":
        velocity = 0
        NReactors = 2 #In the surf_pfr example. For static, we only need 1 reactor, but the rlen formula below has a minimum value of 2. #FIXME
    
    num_t_steps = ceil(t_final / t_step_size) #rounds up.
    
    rlen = length/(NReactors-1) #This is each individual CSTR's length. #FIXME: Why is this not just Nreactors? Is it because of reservoirs?...
    #rvol is the reactor volume. We're modeling this as as a single CSTR with no flow.
    rvol = reactor_cross_area * rlen * porosity #Each individual CSTR gas volume reduced by porosity.
    # catalyst area in one reactor
    cat_area = cat_area_per_vol * rvol
    
    
    #Set the initial conditions for the gas and the surface.
    gas.TPX = T_gas_feed, P_gas, gas_composition
    surf.TP = T_surf, P_gas
    surf.X = surface_coverages
    
    mass_flow_rate = velocity * reactor_cross_area * gas.density  #linear velocity times area is volume/s, times kg/vol becomes kg/s. I think that for this example we neglect effects of surface adsorption regarding how much mass is in the gas phase?
    
    TDY = gas.TDY #Get/Set temperature [K] and density [kg/m^3 or kmol/m^3], and mass fractions. From: https://cantera.org/documentation/docs-2.4/sphinx/html/cython/thermo.html
    cov = surf.coverages #This is normalized coverages, built in from InerfacePhase class: https://cantera.github.io/docs/sphinx/html/cython/thermo.html#cantera.InterfacePhase
    #It would also be possible to type surface.site_density, giving [kmol/m^2] for surface phases. Also possible to use surf.set_unnormalized_coverages(self, cov) for cases when don't want to use normalized coverages.
    
    # create a new reactor
    gas.TDY = TDY #<-- If TDY was changing, and if original gas wanted to be kept, this could have been gas = copy.deepcopy(gas)?
    reactor = ct.IdealGasReactor(gas, energy='off')
    reactor.volume = rvol
    
    # create a reservoir to represent the reactor immediately upstream. Note
    # that the gas object is set already to the state of the upstream reactor
    upstream_of_CSTR = ct.Reservoir(gas, name='upstream') #A cantera reservoir never changes no matter what goes in/out: https://cantera.org/documentation/docs-2.4/sphinx/html/cython/zerodim.html#cantera.Reservoir
    
    # create a reservoir for the reactor to exhaust into. The composition of
    # this reservoir is irrelevant.
    downstream_of_CSTR = ct.Reservoir(gas, name='downstream') #A cantera reservoir never changes no matter what goes in/out: https://cantera.org/documentation/docs-2.4/sphinx/html/cython/zerodim.html#cantera.Reservoir
    
    #Note: these are reservoirs for individual CSTRs. It's used in a clever way in this example, as will be seen later.
    #I would prefer to call these reservoirs "feed" and "exhaust", and would consider to fill the downstream/exhaust with inert to make it clear that it is different. Or maybe make deep copies of gas called "feed_gas" and "exhaust_gas".
    
    # Add the reacting surface to the reactor. The area is set to the desired
    # catalyst area in the reactor.
    rsurf = ct.ReactorSurface(surf, reactor, A=cat_area)  #Here is where the catalyst site density gets used.  Note that cat_area is actually in meters now, even though site_density is defined as mol/cm^2 in the cti file.
    
    # The mass flow rate into the reactor will be fixed by using a
    # MassFlowController object.
    f_mfr = ct.MassFlowController(upstream_of_CSTR, reactor, mdot=mass_flow_rate) #mass flow controller makes flow that goes from the first argument to the second argument with mdot providing "The mass flow rate [kg/s] through this device at time t [s]."s
    
    # We need an outlet to the downstream reservoir. This will determine the
    # pressure in the reactor. The value of K will only affect the transient
    # pressure difference.
    e_mfr = ct.PressureController(reactor, downstream_of_CSTR, master=f_mfr, K=1e-5)  #This makes flow that goes from first argument to second argument with the mass flow controller "f_mfr" controlling the pressure here. K has units of kg/s/Pa times the pressure difference. this "v" that comes out is in same units as the mass flow rate, it's in kg/s.   https://cantera.org/documentation/docs-2.4/sphinx/html/cython/zerodim.html#flowdevice e_mfr is the exhaust mass flow rate.
    
    sim = ct.ReactorNet([reactor]) #This is normally a list of reactors that are coupled.
    sim.max_err_test_fails = 12 #Even after looking at docs, I'm not sure what this does. I think this is how many relative and absolute tolerance failures there can be in a single time step before the resolution of the ODE integrator does that step again with finer resolution? https://cantera.org/documentation/docs-2.4/sphinx/html/cython/zerodim.html#reactor-networks
    
    #Static versus PFR flag
    
    
    
    # set relative and absolute tolerances on the simulation
    sim.rtol = rtol
    sim.atol = atol
    gas_rates = [] #NOTE: These are just the rates from the surface phase. These are *not* including any homogeneous rates.
    surface_rates = [] #NOTE: These are just the rates from the surface phase. These are *not* including any homogeneous rates.
    sim_times = [] #NOTE: This is less useful if one uses advance_to_steady_state
    sim_dist = [] #This is the distance in the reactor. If one is using flow and advance_to_steady_state, then this is representative of the kinetics.
    concentrationsArray = []
    #Print some things out for before the simulation, these are basically headers.
    if print_frequency != None:
        print(concentrationsArrayHeader)
    
    if flow_type == "PFR":
        for n in range(NReactors): #iterate across the CSTRs.
            # Set the state of the reservoir to match that of the previous reactor
            gas.TDY = reactor.thermo.TDY #<-- setting gas to *most recent* state of the reactor.
            upstream_of_CSTR.syncState() #<-- this is an interesting trick. Once one CSTR has been simulated, we set the current values (the one from the just simulated CSTR) to be upstream value. This works because we're doing implicit ODE down a reactor length, and probably only gives the right answer for steady state kinetics. It also only works because we don't care what's happening downstream. We have a fixed K for the e_mfr, and also f_mfr.
            sim.reinitialize()
            sim.advance_to_steady_state() #<-- we advance the current CSTR to steady state before going to the next one.  For a tpr, we'd probably use advance(self, double t) instead. The syntax would be something like sim.advance(10.0) for 10 seconds. https://cantera.github.io/docs/sphinx/html/cython/examples/reactors_reactor1.html#py-example-reactor1-py
        
            dist = n * rlen # distance in m <-- this could have been defined above, and I would have... but it's probably good to have it down here to make it clear that only 'n" is being used rather than this value, to simulate the reactor.
            sim_dist.append(dist)
            sim_times.append(sim.time)
            gas_rates.append(surf.get_net_production_rates('gas'))
            surface_rates.append(surf.get_net_production_rates('surf'))
            # write the gas mole fractions and surface coverages vs. distance
            rowListOfStrings = [dist, sim.time, gas.T, surf.T, reactor.thermo.P/ct.one_atm] + \
                        list(gas.concentrations) + list(surf.coverages)
            if exportOutputs == True:
                writer.writerow(rowListOfStrings)
                concentrationsArray.append(np.array(rowListOfStrings))
            if print_frequency != None: 
                if not n % print_frequency: #This only prints every specified number of steps.
                    try:
                        with np.printoptions(precision=3):
                            print(np.array(rowListOfStrings))
                    except:
                            print(np.array(rowListOfStrings))
    
    if flow_type == "Static":
        T_surf_0 = T_surf
        #sim.set_max_time_step(t_step_size) #Cantera's sim.step normally advances in a variable way.
        for i_step in range(num_t_steps): #iterate across the CSTRs.
            time = i_step*t_step_size
            T_surf = T_surf_0 + time*heating_rate
            surf.TP = T_surf, P_gas
            if simulation_settings_module.piecewise_coverage_dependence == True:
                modified_reactions_parameters_array = canteraKineticsParametersParser.calculatePiecewiseCoverageDependentModifiedParametersArray(simulation_settings_module, surf.species_names, surf.coverages) #This feature requires the piecewise coverage dependence settings AND the reactions_parameters_array to already be inside the surf object **in advance**
                canteraKineticsParametersParser.modifyReactionsInOnePhase(surf, modified_reactions_parameters_array, ArrheniusOnly=True) #TODO: Right now this is constrainted to Arrhenius only because cantera does not yet allow modifyReactionsInOnePhase to do more (but it's planned to change in developers roadmap)                
            surf.advance_coverages(t_step_size)  #sim.advance(time) would not work. Changing T with time is not officially supported but happens to work with surf.advance_coverages. Supported way to change temperature during simulation for arbitrary reactors is to use custom integrator: https://cantera.org/examples/python/reactors/custom.py.html
            dist = 0.0
            sim_dist.append(dist)
            sim_times.append(time)
            gas_rates.append(surf.get_net_production_rates('gas'))
            surface_rates.append(surf.get_net_production_rates('surf'))        
            rowListOfStrings = [dist, time, gas.T, surf.T, reactor.thermo.P/ct.one_atm] + \
                    list(gas.X) + list(surf.coverages)
            if exportOutputs == True:
                writer.writerow(rowListOfStrings)
                concentrationsArray.append(np.array(rowListOfStrings))
            if print_frequency != None:
                if not i_step % print_frequency: #This only prints every specified number of steps.
                    try:
                        with np.printoptions(precision=3):
                            print(np.array(rowListOfStrings))
                    except:
                            print(np.array(rowListOfStrings))

           
    #Need to get things into a stackable state. Figured out from numpy shape that I needed to do at_least2D and transpose.
    sim_dist =  np.atleast_2d(sim_dist).transpose()
    sim_times =  np.atleast_2d(sim_times).transpose()
    gas_rates =  np.array(gas_rates)
    surface_rates =  np.array(surface_rates)    
    gasRatesArray = np.hstack((sim_dist,sim_times, gas_rates))
    surfaceRatesArray = np.hstack((sim_dist,sim_times, surface_rates))
    rates_all_array = np.hstack((sim_dist,sim_times, gas_rates, surface_rates))
    concentrationsArray = np.array(concentrationsArray)
    concentrationsArrayHeader = concentrationsArrayHeader
    gasRatesArrayHeader = 'dist(m), time(s),'+str(gas.species_names).replace("'","")[1:-1]
    surfaceRatesArrayHeader = 'dist(m),time(s),'+str(surf.species_names).replace("'","")[1:-1]
    rates_all_array_header = 'dist(m),time(s),'+str(gas.species_names).replace("'","")[1:-1]+"," + str(surf.species_names).replace("'","")[1:-1]
    canteraSimulationsObject = sim
    cantera_phase_rates = {"gas":gasRatesArray, "surf":surfaceRatesArray}
    cantera_phase_rates_headers = {"gas":gasRatesArrayHeader, "surf":surfaceRatesArrayHeader}
    
    if exportOutputs == True:
        np.savetxt(model_name + "_output_rates_all.csv", rates_all_array, delimiter=",", comments = '', header = rates_all_array_header)
        np.savetxt(model_name + "_output_rates_gas.csv", gasRatesArray, delimiter=",", comments = '', header = gasRatesArrayHeader )
        np.savetxt(model_name + "_output_rates_surf.csv", surfaceRatesArray, delimiter=",", comments = '', header = surfaceRatesArrayHeader)     
    if exportOutputs == True:    
        outfile.close()
    return concentrationsArray, concentrationsArrayHeader, rates_all_array, rates_all_array_header, cantera_phase_rates, canteraPhases, cantera_phase_rates_headers, canteraSimulationsObject
Пример #7
0
def mdot(t):
    return combustor1.mass / residence_time1

inlet1_mfc = ct.MassFlowController(inlet1, combustor1, mdot=mdot)

#gas2
def mdot(t):
    return combustor2.mass / residence_time2

inlet2_mfc = ct.MassFlowController(inlet2, combustor2, mdot=mdot)

# A PressureController has a baseline mass flow rate matching the 'master'
# MassFlowController, with an additional pressure-dependent term. By explicitly
# including the upstream mass flow rate, the pressure is kept constant without
# needing to use a large value for 'K', which can introduce undesired stiffness.
outlet1_mfc = ct.PressureController(combustor1, exhaust1, master=inlet1_mfc, K=0.01) #gas1
outlet2_mfc = ct.PressureController(combustor2, exhaust2, master=inlet2_mfc, K=0.01) #gas2

# the simulation
sim1 = ct.ReactorNet([combustor1]) #gas1
sim2 = ct.ReactorNet([combustor2]) #gas2

# Run a loop over decreasing residence times, until the reactor is extinguished,
# saving the state after each iteration.
states1 = ct.SolutionArray(gas1, extra=['tres1'])
states2 = ct.SolutionArray(gas2, extra=['tres2'])

residence_time2=0.1
residence_time1 = 0.1  # starting residence time
while combustor1.T > 500 or combustor2.T > 500:
    sim1.set_initial_time(0.0)  # reset the integrator
Пример #8
0
def run_reactor_ss(
    cti_file,
    t_array=[528],
    p_array=[75],
    v_array=[0.00424],
    h2_array=[0.75],
    co2_array=[0.5],
    rtol=1.0e-11,
    atol=1.0e-22,
    reactor_type=0,
    energy="off",
    sensitivity=False,
    sensatol=1e-6,
    sensrtol=1e-6,
    reactime=1e5,
):
    ''' 
    run the reactor to steady state. saves a single CSV output file with one row of data. 
    results saved in new folder marked "steady state" under reactor type
    
    '''

    import pandas as pd
    import numpy as np
    import time
    import cantera as ct
    from matplotlib import pyplot as plt
    import csv
    import math
    import os
    import sys
    import re
    import itertools
    import logging
    from collections import defaultdict
    import git

    try:
        array_i = int(os.getenv("SLURM_ARRAY_TASK_ID"))
    except TypeError:
        array_i = 0

    # get git commit hash and message

    repo = git.Repo("/work/westgroup/lee.ting/cantera/ammonia")
    git_sha = str(repo.head.commit)[0:6]
    git_msg = str(repo.head.commit.message)[0:20].replace(" ", "_").replace(
        "'", "_")

    # this should probably be outside of function
    settings = list(
        itertools.product(t_array, p_array, v_array, h2_array, co2_array))

    # constants
    pi = math.pi

    # set initial temps, pressures, concentrations
    temp = settings[array_i][0]  # kelvin
    temp_str = str(temp)[0:3]
    pressure = settings[array_i][1] * ct.one_atm  # Pascals

    X_h2 = settings[array_i][3]
    x_h2_str = str(X_h2)[0:3].replace(".", "_")
    x_CO_CO2_str = str(settings[array_i][4])[0:3].replace(".", "_")

    if X_h2 == 0.75:
        X_h2o = 0.05
    else:
        X_h2o = 0

    X_co = (1 - (X_h2 + X_h2o)) * (settings[array_i][4])
    X_co2 = (1 - (X_h2 + X_h2o)) * (1 - settings[array_i][4])

    # normalize mole fractions just in case
    # X_co = X_co/(X_co+X_co2+X_h2)
    # X_co2= X_co2/(X_co+X_co2+X_h2)
    # X_h2 = X_h2/(X_co+X_co2+X_h2)

    mw_co = 28.01e-3  # [kg/mol]
    mw_co2 = 44.01e-3  # [kg/mol]
    mw_h2 = 2.016e-3  # [kg/mol]
    mw_h2o = 18.01528e-3  # [kg/mol]

    co2_ratio = X_co2 / (X_co + X_co2)
    h2_ratio = (X_co2 + X_co) / X_h2

    # CO/CO2/H2/H2: typical is
    concentrations_rmg = {"O2(2)": X_co, "NH3(6)": X_co2, "He": X_h2}

    # initialize cantera gas and surface
    gas = ct.Solution(cti_file, "gas")

    # surf_grab = ct.Interface(cti_file,'surface1_grab', [gas_grab])
    surf = ct.Interface(cti_file, "surface1", [gas])

    # gas_grab.TPX =
    gas.TPX = temp, pressure, concentrations_rmg
    surf.TP = temp, pressure

    # create gas inlet
    inlet = ct.Reservoir(gas)

    # create gas outlet
    exhaust = ct.Reservoir(gas)

    # Reactor volume
    rradius = 35e-3
    rlength = 70e-3
    rvol = (rradius**2) * pi * rlength

    # Catalyst Surface Area
    site_density = (surf.site_density * 1000
                    )  # [mol/m^2]cantera uses kmol/m^2, convert to mol/m^2
    cat_weight = 4.24e-3  # [kg]
    cat_site_per_wt = (300 *
                       1e-6) * 1000  # [mol/kg] 1e-6mol/micromole, 1000g/kg
    cat_area = site_density / (cat_weight * cat_site_per_wt)  # [m^3]

    # 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"

    rsurf = ct.ReactorSurface(surf, r, A=cat_area)
    r.volume = rvol
    surf.coverages = "X(1):1.0"

    # flow controllers (Graaf measured flow at 293.15 and 1 atm)
    one_atm = ct.one_atm
    FC_temp = 293.15
    volume_flow = settings[array_i][2]  # [m^3/s]
    molar_flow = volume_flow * one_atm / (8.3145 * FC_temp)  # [mol/s]
    mass_flow = molar_flow * (X_co * mw_co + X_co2 * mw_co2 + X_h2 * mw_h2 +
                              X_h2o * mw_h2o)  # [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-11
    sim.atol = 1.0e-22

    #################################################
    # Run single reactor
    #################################################

    # round numbers so they're easier to read
    # temp_str = '%s' % '%.3g' % tempn

    cat_area_str = "%s" % "%.3g" % cat_area
    results_path = (
        os.path.dirname(os.path.abspath(__file__)) +
        f"/{git_sha}_{git_msg}/{reactor_type_str}/steady_state/{temp_str}/results"
    )

    flux_path = (
        os.path.dirname(os.path.abspath(__file__)) +
        f"/{git_sha}_{git_msg}/{reactor_type_str}/steady_state/{temp_str}/flux_diagrams/{x_h2_str}/{x_CO_CO2_str}"
    )
    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}_h2_{x_h2_str}_COCO2_{x_CO_CO2_str}.csv")

    outfile = open(output_filename, "w")
    writer = csv.writer(outfile)

    writer.writerow([
        "T (C)",
        "P (atm)",
        "V (M^3/s)",
        "X_co initial",
        "X_co2 initial",
        "X_h2 initial",
        "X_h2o initial",
        "CO2/(CO2+CO)",
        "(CO+CO2/H2)",
        "T (C) final",
        "Rtol",
        "Atol",
        "reactor type",
    ] + gas.species_names + surf.species_names + gas_ROP_str +
                    gas_surf_ROP_str + surf_ROP_str + gasrxn_ROP_str +
                    surfrxn_ROP_str)

    # Run Simulation to steady state
    sim.advance_to_steady_state()

    # Record steady state data to CSV
    writer.writerow([
        temp,
        pressure,
        volume_flow,
        X_co,
        X_co2,
        X_h2,
        X_h2o,
        co2_ratio,
        h2_ratio,
        gas.T,
        sim.rtol,
        sim.atol,
        reactor_type_str,
    ] + 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))

    outfile.close()

    # save flux diagrams at the end of the run
    save_flux_diagrams(gas, suffix=flux_path, timepoint="end")
    save_flux_diagrams(surf, suffix=flux_path, timepoint="end")
    return
Пример #9
0
def monolithFull(gas, surf, temp, mol_in, verbose=False, sens=False):
    """
    Verbose prints out values as you go along
    Sens is for sensitivity, in the form [perturbation, reaction #]
    """
    ch4, o2, ar = mol_in
    ratio = ch4 / (2 * o2)
    ratio = round(ratio, 1)
    ch4 = str(ch4)
    o2 = str(o2)
    ar = str(ar)
    X = str('CH4(2):' + ch4 + ', O2(3):' + o2 + ', Ar:' + ar)
    gas.TPX = 273.15, ct.one_atm, X  # need to initialize mass flow rate at STP
    # mass_flow_rate = velocity * gas.density_mass * area  # kg/s
    mass_flow_rate = flow_rate * gas.density_mass
    gas.TPX = temp, ct.one_atm, X
    temp_cat = temp
    surf.TP = temp_cat, ct.one_atm
    surf.coverages = 'X(1):1.0'
    gas.set_multiplier(1.0)

    TDY = gas.TDY
    cov = surf.coverages

    if verbose is True:
        print(
            '  distance(mm)   X_CH4        X_O2        X_H2       X_CO       X_H2O       X_CO2'
        )

    # create a new reactor
    gas.TDY = TDY
    r = ct.IdealGasReactor(gas)
    r.volume = rvol

    # create a reservoir to represent the reactor immediately upstream. Note
    # that the gas object is set already to the state of the upstream reactor
    upstream = ct.Reservoir(gas, name='upstream')

    # create a reservoir for the reactor to exhaust into. The composition of
    # this reservoir is irrelevant.
    downstream = ct.Reservoir(gas, name='downstream')

    # Add the reacting surface to the reactor. The area is set to the desired
    # catalyst area in the reactor.
    rsurf = ct.ReactorSurface(surf, r, A=cat_area)

    # The mass flow rate into the reactor will be fixed by using a
    # MassFlowController object.
    # mass_flow_rate = velocity * gas.density_mass * area  # kg/s
    # mass_flow_rate = flow_rate * gas.density_mass
    m = ct.MassFlowController(upstream, r, mdot=mass_flow_rate)

    # We need an outlet to the downstream reservoir. This will determine the
    # pressure in the reactor. The value of K will only affect the transient
    # pressure difference.
    v = ct.PressureController(r, downstream, master=m, K=1e-5)

    sim = ct.ReactorNet([r])
    sim.max_err_test_fails = 12

    # set relative and absolute tolerances on the simulation
    sim.rtol = 1.0e-10
    sim.atol = 1.0e-20

    gas_names = gas.species_names
    surf_names = surf.species_names
    gas_out = []  # in

    surf_out = []
    dist_array = []
    T_array = []

    surf.set_multiplier(0.0)  # no surface reactions until the gauze
    for n in range(NReactors):
        # Set the state of the reservoir to match that of the previous reactor
        gas.TDY = r.thermo.TDY
        upstream.syncState()
        if n == on_catalyst:
            surf.set_multiplier(1.0)
            if sens is not False:
                surf.set_multiplier(1.0 + sens[0], sens[1])
        if n == off_catalyst:
            surf.set_multiplier(0.0)
        sim.reinitialize()
        sim.advance_to_steady_state()
        dist = n * reactor_len * 1.0e3  # distance in mm
        dist_array.append(dist)
        T_array.append(surf.T)
        kmole_flow_rate = mass_flow_rate / gas.mean_molecular_weight  # kmol/s
        gas_out.append(1000 * 60 * kmole_flow_rate *
                       gas.X.copy())  # molar flow rate in moles/minute
        surf_out.append(surf.X.copy())

        # make reaction diagrams
        out_dir = 'rxnpath'
        os.path.exists(out_dir) or os.makedirs(out_dir)
        elements = ['H', 'O']
        locations_of_interest = [1000, 1200, 1400, 1600, 1800, 1999]
        if sens is False:
            for l in locations_of_interest:
                if n == l:
                    location = str(int(n / 100))
                    diagram = ct.ReactionPathDiagram(surf, 'X')
                    diagram.title = 'rxn path'
                    diagram.label_threshold = 1e-9
                    dot_file = out_dir + '/rxnpath-' + str(
                        ratio) + '-x-' + location + 'mm.dot'
                    img_file = out_dir + '/rxnpath-' + str(
                        ratio) + '-x-' + location + 'mm.pdf'
                    img_path = os.path.join(out_dir, img_file)
                    diagram.write_dot(dot_file)
                    os.system('dot {0} -Tpng -o{1} -Gdpi=200'.format(
                        dot_file, img_file))

                    for element in elements:
                        diagram = ct.ReactionPathDiagram(surf, element)
                        diagram.title = element + 'rxn path'
                        diagram.label_threshold = 1e-9
                        dot_file = out_dir + '/rxnpath-' + str(
                            ratio
                        ) + '-surf-' + location + 'mm-' + element + '.dot'
                        img_file = out_dir + '/rxnpath-' + str(
                            ratio
                        ) + '-surf-' + location + 'mm-' + element + '.pdf'
                        img_path = os.path.join(out_dir, img_file)
                        diagram.write_dot(dot_file)
                        os.system('dot {0} -Tpng -o{1} -Gdpi=200'.format(
                            dot_file, img_file))
        else:
            pass

        if verbose is True:
            if not n % 100:
                print(
                    '  {0:10f}  {1:10f}  {2:10f}  {3:10f} {4:10f} {5:10f} {6:10f}'
                    .format(
                        dist,
                        *gas['CH4(2)', 'O2(3)', 'H2(6)', 'CO(7)', 'H2O(5)',
                             'CO2(4)'].X * 1000 * 60 * kmole_flow_rate))

    gas_out = np.array(gas_out)
    surf_out = np.array(surf_out)
    gas_names = np.array(gas_names)
    surf_names = np.array(surf_names)
    data_out = gas_out, surf_out, gas_names, surf_names, dist_array, T_array
    return data_out
Пример #10
0
def run_reactor(
    cti_file,
    t_array=[528],
    p_array=[75],
    v_array=[0.00424],
    h2_array=[0.75],
    co2_array=[0.5],
    rtol=1.0e-11,
    atol=1.0e-22,
    reactor_type=0,
    energy="off",
    sensitivity=False,
    sensatol=1e-6,
    sensrtol=1e-6,
    reactime=1e5,
):

    import pandas as pd
    import numpy as np
    import time
    import cantera as ct
    from matplotlib import pyplot as plt
    import csv
    import math
    import os
    import sys
    import re
    import itertools
    import logging
    from collections import defaultdict
    import git

    try:
        array_i = int(os.getenv("SLURM_ARRAY_TASK_ID"))
    except TypeError:
        array_i = 0

    # get git commit hash and message

    repo = git.Repo("/work/westgroup/lee.ting/cantera/ammonia/")
    git_sha = str(repo.head.commit)[0:6]
    git_msg = str(repo.head.commit.message)[0:20].replace(" ", "_").replace(
        "'", "_")

    # this should probably be outside of function
    settings = list(
        itertools.product(t_array, p_array, v_array, h2_array, co2_array))

    # constants
    pi = math.pi

    # set initial temps, pressures, concentrations
    temp = settings[array_i][0]  # kelvin
    temp_str = str(temp)[0:3]
    pressure = settings[array_i][1] * ct.one_atm  # Pascals

    X_h2 = settings[array_i][3]
    x_h2_str = str(X_h2)[0:3].replace(".", "_")
    x_CO_CO2_str = str(settings[array_i][4])[0:3].replace(".", "_")

    if X_h2 == 0.75:
        X_h2o = 0.05
    else:
        X_h2o = 0

    X_co = (1 - (X_h2 + X_h2o)) * (settings[array_i][4])
    X_co2 = (1 - (X_h2 + X_h2o)) * (1 - settings[array_i][4])

    # normalize mole fractions just in case
    # X_co = X_co/(X_co+X_co2+X_h2)
    # X_co2= X_co2/(X_co+X_co2+X_h2)
    # X_h2 = X_h2/(X_co+X_co2+X_h2)

    mw_co = 28.01e-3  # [kg/mol]
    mw_co2 = 44.01e-3  # [kg/mol]
    mw_h2 = 2.016e-3  # [kg/mol]
    mw_h2o = 18.01528e-3  # [kg/mol]

    co2_ratio = X_co2 / (X_co + X_co2)
    h2_ratio = (X_co2 + X_co) / X_h2

    # CO/CO2/H2/H2: typical is
    concentrations_rmg = {"O2(2)": X_co, "NH3(6)": X_co2, "He": X_h2}

    # initialize cantera gas and surface
    gas = ct.Solution(cti_file, "gas")

    # surf_grab = ct.Interface(cti_file,'surface1_grab', [gas_grab])
    surf = ct.Interface(cti_file, "surface1", [gas])

    # gas_grab.TPX =
    gas.TPX = temp, pressure, concentrations_rmg
    surf.TP = temp, pressure

    # create gas inlet
    inlet = ct.Reservoir(gas)

    # create gas outlet
    exhaust = ct.Reservoir(gas)

    # Reactor volume
    rradius = 35e-3
    rlength = 70e-3
    rvol = (rradius**2) * pi * rlength

    # Catalyst Surface Area
    site_density = (surf.site_density * 1000
                    )  # [mol/m^2]cantera uses kmol/m^2, convert to mol/m^2
    cat_weight = 4.24e-3  # [kg]
    cat_site_per_wt = (300 *
                       1e-6) * 1000  # [mol/kg] 1e-6mol/micromole, 1000g/kg
    cat_area = site_density / (cat_weight * cat_site_per_wt)  # [m^3]

    # 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"

    rsurf = ct.ReactorSurface(surf, r, A=cat_area)
    r.volume = rvol
    surf.coverages = "X(1):1.0"

    # flow controllers (Graaf measured flow at 293.15 and 1 atm)
    one_atm = ct.one_atm
    FC_temp = 293.15
    volume_flow = settings[array_i][2]  # [m^3/s]
    molar_flow = volume_flow * one_atm / (8.3145 * FC_temp)  # [mol/s]
    mass_flow = molar_flow * (X_co * mw_co + X_co2 * mw_co2 + X_h2 * mw_h2 +
                              X_h2o * mw_h2o)  # [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-11
    sim.atol = 1.0e-22

    #################################################
    # Run single reactor
    #################################################

    # round numbers so they're easier to read
    # temp_str = '%s' % '%.3g' % tempn

    cat_area_str = "%s" % "%.3g" % cat_area
    results_path = (
        os.path.dirname(os.path.abspath(__file__)) +
        f"/{git_sha}_{git_msg}/{reactor_type_str}/transient/{temp_str}/results"
    )
    # results_path_csp = (
    #     os.path.dirname(os.path.abspath(__file__))
    #     + f"/{git_sha}_{git_msg}/{reactor_type_str}/transient/{temp_str}/results/csp"
    # )
    flux_path = (
        os.path.dirname(os.path.abspath(__file__)) +
        f"/{git_sha}_{git_msg}/{reactor_type_str}/transient/{temp_str}/flux_diagrams/{x_h2_str}/{x_CO_CO2_str}"
    )
    try:
        os.makedirs(results_path, exist_ok=True)
    except OSError as error:
        print(error)

    # try:
    #     os.makedirs(results_path_csp, 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 might be redundant, not sure.

    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}_h2_{x_h2_str}_COCO2_{x_CO_CO2_str}.csv")
    # output_filename_csp = (
    #     results_path_csp
    #     + f"/Spinning_basket_area_{cat_area_str}_energy_{energy}"
    #     + f"_temp_{temp}_h2_{x_h2_str}_COCO2_{x_CO_CO2_str}.csv"
    # )
    outfile = open(output_filename, "w")
    # outfile_csp = open(output_filename_csp, "w")
    writer = csv.writer(outfile)
    # writer_csp = csv.writer(outfile_csp)

    # 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)"
        ]  #change THIS to your species, can add "," and other species

        # turn on sensitive reactions/species
        for i in range(gas.n_reactions):
            r.add_sensitivity_reaction(i)

        for i in range(surf.n_reactions):
            rsurf.add_sensitivity_reaction(i)

        # 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([
            "T (C)",
            "P (atm)",
            "V (M^3/s)",
            "X_co initial",
            "X_co2initial",
            "X_h2 initial",
            "X_h2o initial",
            "CO2/(CO2+CO)",
            "(CO+CO2/H2)",
            "T (C) final",
            "Rtol",
            "Atol",
            "reactor type",
        ] + 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([
            "T (C)",
            "P (atm)",
            "V (M^3/s)",
            "X_co initial",
            "X_co2 initial",
            "X_h2 initial",
            "X_h2o initial",
            "CO2/(CO2+CO)",
            "(CO+CO2/H2)",
            "T (C) final",
            "Rtol",
            "Atol",
            "reactor type",
        ] + gas.species_names + surf.species_names + gas_ROP_str +
                        gas_surf_ROP_str + surf_ROP_str + gasrxn_ROP_str +
                        surfrxn_ROP_str)

    # writer_csp.writerow(
    #     ["iter", "t", "dt", "Density[kg/m3]", "Pressure[Pascal]", "Temperature[K]",]
    #     + gas.species_names
    #     + surf.species_names
    # )

    t = 0.0
    dt = 0.1
    iter_ct = 0
    # run the simulation
    first_run = True

    while t < reactime:
        # save flux diagrams at beginning of run
        if first_run == True:
            save_flux_diagrams(gas, suffix=flux_path, timepoint="beginning")
            save_flux_diagrams(surf, suffix=flux_path, timepoint="beginning")
            first_run = False
        t += dt
        sim.advance(t)
        #         if t % 10 < 0.01:

        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([
                temp,
                pressure,
                volume_flow,
                X_co,
                X_co2,
                X_h2,
                X_h2o,
                co2_ratio,
                h2_ratio,
                gas.T,
                sim.rtol,
                sim.atol,
                reactor_type_str,
            ] + 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([
                temp,
                pressure,
                volume_flow,
                X_co,
                X_co2,
                X_h2,
                X_h2o,
                co2_ratio,
                h2_ratio,
                gas.T,
                sim.rtol,
                sim.atol,
                reactor_type_str,
            ] + 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))

        # writer_csp.writerow(
        #     [
        #         iter_ct,
        #         sim.time,
        #         dt,
        #         gas.density,
        #         gas.P,
        #         gas.T,
        #     ]
        #     + list(gas.X)
        #     + list(surf.X)
        # )

        iter_ct += 1

    outfile.close()
    # outfile_csp.close()

    # save flux diagrams at the end of the run
    save_flux_diagrams(gas, suffix=flux_path, timepoint="end")
    save_flux_diagrams(surf, suffix=flux_path, timepoint="end")
    return
Пример #11
0
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
Пример #12
0
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
Пример #13
0
    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)
Пример #14
0
def runSinglePFR(T_0, pressure, composition_0, length, v_0, d, tempProfile,
                 factor, reactionIndex):

    start = time.time()

    # input file containing the reaction mechanism
    reaction_mechanism = 'HXD15_Battin_mech.xml'

    # import the gas model and set the initial conditions
    gas2 = ct.Solution(reaction_mechanism)
    gas2.TPX = T_0, pressure, composition_0
    mass_flow_rate2 = v_0 * gas2.density * (1.01325e5 / pressure) * (T_0 /
                                                                     298.15)

    # Resolution: The PFR will be simulated by 'n_steps' time steps or by a chain
    # of 'n_steps' stirred reactors.
    n_steps = 100
    area = math.pi * d * d / 4
    dz = length / n_steps
    r_vol = area * dz

    # create a new reactor
    r2 = ct.IdealGasReactor(gas2)
    r2.volume = r_vol

    # create a reservoir to represent the reactor immediately upstream. Note
    # that the gas object is set already to the state of the upstream reactor
    upstream = ct.Reservoir(gas2, name='upstream')

    # create a reservoir for the reactor to exhaust into. The composition of
    # this reservoir is irrelevant.
    downstream = ct.Reservoir(gas2, name='downstream')

    # The mass flow rate into the reactor will be fixed by using a
    # MassFlowController object.
    m = ct.MassFlowController(upstream, r2, mdot=mass_flow_rate2)

    # We need an outlet to the downstream reservoir. This will determine the
    # pressure in the reactor. The value of K will only affect the transient
    # pressure difference.
    v = ct.PressureController(r2, downstream, master=m, K=1.0e-9)

    sim2 = ct.ReactorNet([r2])

    from scipy.interpolate import UnivariateSpline
    [zz, tem] = readTemperatureProfile(tempProfile)
    para = UnivariateSpline(zz, tem)

    # modify the A factor of the given reaction
    gas2.set_multiplier(factor, reactionIndex - 1)

    # iterate through the PFR cells

    # define time, space, and other information vectors
    # z2 = (np.arange(n_steps+1) + 1) * dz
    # t_r2 = np.zeros_like(z2)  # residence time in each reactor
    # u2 = np.zeros_like(z2)
    # t2 = np.zeros_like(z2)

    for n in range(n_steps + 1):
        print n
        position = dz * float(n)
        # Set the state of the reservoir to match that of the previous reactor
        gas2.TDY = para(position), r2.thermo.density, r2.thermo.Y

        upstream.syncState()
        sim2.reinitialize()
        # integrate the reactor forward in time until steady state is reached
        sim2.advance_to_steady_state()
        # compute velocity and transform into time
        # u2[n] = mass_flow_rate2 / area / r2.thermo.density
        # t_r2[n] = r2.mass / mass_flow_rate2  # residence time in this reactor
        # t2[n] = np.sum(t_r2)
        # write output data
        # print position, r2.thermo.T, r2.thermo.P, u2[n], t2[n], r2.thermo['C3H6'].X[0]
        # moleFractionC3H6.append([dz*n, u2[n], t2[n], r2.thermo.T, r2.thermo['C3H6'].X[0]])

    end = time.time()
    print 'Done! Execution time is:'
    print end - start

    return [
        gas2.reaction_equation(reactionIndex - 1),
        r2.thermo['C3H6'].X[0] * 1.0e6
    ]
Пример #15
0
    def plug_flow_simulation(self, output_filename, energy='off'):
        """
        PLUG FLOW REACTOR SIMULATION

        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 thereactor 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.
        """

        gas, surf = self.gas, self.surf

        gas.TPX = self.temperature_c + 273.15, self.pressure, self.feed_mole_fractions
        surf.TP = self.temperature_c + 273.15, self.pressure
        surf.coverages = self.surface_coverages

        TDY = gas.TDY

        # create a new reactor
        gas.TDY = TDY
        r = ct.IdealGasReactor(gas, energy=energy)
        r.volume = self.gas_volume_per_reactor

        # create a reservoir to represent the reactor immediately upstream. Note
        # that the gas object is set already to the state of the upstream reactor
        upstream = ct.Reservoir(gas, name="upstream")

        # create a reservoir for the reactor to exhaust into. The composition of
        # this reservoir is irrelevant.
        downstream = ct.Reservoir(gas, name="downstream")

        # Add the reacting surface to the reactor. The area is set to the desired
        # catalyst area in the reactor.
        rsurf = ct.ReactorSurface(surf, r, A=self.cat_area_per_reactor)

        # The mass flow rate into the reactor will be fixed by using a
        # MassFlowController object.
        m = ct.MassFlowController(upstream, r, mdot=self.mass_flow_rate)

        # We need an outlet to the downstream reservoir. This will determine the
        # pressure in the reactor. The value of K will only affect the transient
        # pressure difference.
        v = ct.PressureController(r, downstream, master=m, K=1e-5)

        sim = ct.ReactorNet([r])
        sim.max_err_test_fails = 24

        # set relative and absolute tolerances on the simulation
        sim.rtol = self.rtol
        sim.atol = self.atol

        sim.verbose = False
        r.volume = self.gas_volume_per_reactor

        os.makedirs(self.results_directory, exist_ok=True)
        outfile = open(os.path.join(self.results_directory, output_filename),
                       "w")
        writer = csv.writer(outfile)
        writer.writerow(["Distance (mm)", "T (C)", "P (atm)"] +
                        gas.species_names + surf.species_names +
                        ["gas_heat", "surface_heat", "alpha"])

        print(
            "    distance(mm)    T(C)    NH3(6)    O2(2)    N2(4)    N2O(7)    NO(5)    H2O(3)    alpha"
        )

        for n in range(self.number_of_reactors):

            # Set the state of the reservoir to match that of the previous reactor
            gas.TDY = TDY = r.thermo.TDY
            upstream.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"
                    )
                    # self.save_flux_diagrams(path=f"data/H3NX(29)+{delta}")
                    # self.show_flux_diagrams(embed=True)
                    # self.report_rates()
                    # self.report_rate_constants()
                    try:
                        sim.advance(new_target_time)
                    except ct.CanteraError:
                        outfile.close()
                        raise

                # dont add fluxes at distance=0 because you just
                # have an almost infinite flux onto vacant surface
                self.add_fluxes()  # for the integration

            distance_mm = n * self.reactor_length * 1.0e3  # distance in mm

            # heat evolved by gas phase reaction:
            gas_heat = surface_heat = alpha = 1
            # heat evolved by surf phase reaction:
            surface_heat = self.cat_area_per_gas_volume * np.dot(
                surf.net_rates_of_progress, surf.delta_enthalpy)
            # fraction of heat release that is on surface:
            alpha = surface_heat / (surface_heat + gas_heat)

            if not n % 10:
                print(
                    "    {:10f}  {:7.1f} {:10f} {:10f} {:10f}  {:10f}  {:10f}  {:10f}  {:5.1e}"
                    .format(
                        distance_mm,
                        r.T - 273.15,
                        *gas["NH3(6)", "O2(2)", "N2(4)", "N2O(7)", "NO(5)",
                             "H2O(3)"].X,
                        alpha,
                    ))
                print("Highest surface coverages are:")
                for i in np.argsort(surf.coverages)[::-1][:5]:
                    print(surf.species_name(i), round(surf.coverages[i], 4))

            if n in (1, int(self.number_of_reactors / 2),
                     self.number_of_reactors - 1):
                # Will save at start, midpoint, and end
                self.save_flux_diagrams(
                    path=self.results_directory,
                    suffix=f"Temp_{r.T-273.15:.0f}C_Dist_{distance_mm:.1f}",
                )

            if (not (n - 1) % 100) or n == (self.number_of_reactors - 1):
                self.save_flux_data(
                    f"flux_data_Temp_{r.T-273.15:.0f}C_Dist_{distance_mm:.1f}")

            # write the gas mole fractions and surface coverages vs. distance
            writer.writerow(
                [distance_mm, r.T - 273.15, r.thermo.P / ct.one_atm] +
                list(gas.X) + list(surf.coverages) +
                [gas_heat, surface_heat, alpha])

        outfile.close()
        print("Results saved to '{0}'".format(output_filename))
Пример #16
0
    # create a reservoir to represent the reactor immediately 	upstream. Note
    # that the gas object is set already to the state of the 	upstream reactor
    upstream = ct.Reservoir(gas2, name='upstream')

    # create a reservoir for the reactor to exhaust into. The 	composition of
    # this reservoir is irrelevant.
    downstream = ct.Reservoir(gas2, name='downstream')

    # The mass flow rate into the reactor will be fixed by using 	a
    # MassFlowController object.
    m = ct.MassFlowController(upstream, r2, mdot=mass_flow_rate2)

    # We need an outlet to the downstream reservoir. This will 	determine the
    # pressure in the reactor. The value of K will only affect 	the transient
    # pressure difference.
    v = ct.PressureController(r2, downstream, master=m, K=1e-5)

    sim2 = ct.ReactorNet([r2])

    # define time, space, and other information vectors
    z2 = (np.arange(n_steps) + 1) * dz
    t_r2 = np.zeros_like(z2)  # residence time in each reactor
    u2 = np.zeros_like(z2)
    t2 = np.zeros_like(z2)
    states2 = ct.SolutionArray(r2.thermo)
    # iterate through the PFR cells
    for n in range(n_steps):
        # Set the state of the reservoir to match that of the 	previous reactor
        gas2.TDY = r2.thermo.TDY
        upstream.syncState()
        # integrate the reactor forward in time 	until steady state is reached
Пример #17
0
def monolith_simulation(path_to_cti, temp, mol_in, verbose=False, sens=False):
    """
    Set up and solve the monolith reactor simulation.

    Verbose prints out values as you go along
    Sens is for sensitivity, in the form [perturbation, reaction #]

    Args:
        path_to_cti: full path to the cti file
        temp (float): The temperature in Kelvin
        mol_in (3-tuple or iterable): the inlet molar ratios of (CH4, O2, Ar)
        verbose (Boolean): whether to print intermediate results
        sens (False or 2-tuple/list): if not False, then should be a 2-tuple or list [dk, rxn]
                in which dk = relative change (eg. 0.01) and rxn = the index of the surface reaction rate to change

    Returns:
        gas_out, # gas molar flow rate in moles/minute
        surf_out, # surface mole fractions
        gas_names, # gas species names
        surf_names, # surface species names
        dist_array, # distances (in mm)
        T_array # temperatures (in K)
    """
    sols_dict = setup_ct_solution(path_to_cti)
    gas, surf, i_ar, n_surf_reactions = sols_dict['gas'], sols_dict[
        'surf'], sols_dict['i_ar'], sols_dict['n_surf_reactions']
    print(
        f"Running monolith simulation with CH4 and O2 concs {mol_in[0], mol_in[1]} on thread {threading.get_ident()}"
    )
    ch4, o2, ar = mol_in
    ratio = ch4 / (2 * o2)

    X = f"CH4(2):{ch4}, O2(3):{o2}, Ar:{ar}"
    gas.TPX = 273.15, ct.one_atm, X  # need to initialize mass flow rate at STP
    # mass_flow_rate = velocity * gas.density_mass * area  # kg/s
    mass_flow_rate = flow_rate * gas.density_mass
    gas.TPX = temp, ct.one_atm, X
    temp_cat = temp
    surf.TP = temp_cat, ct.one_atm
    surf.coverages = 'X(1):1.0'
    gas.set_multiplier(1.0)

    TDY = gas.TDY
    cov = surf.coverages

    if verbose is True:
        print(
            '  distance(mm)   X_CH4        X_O2        X_H2       X_CO       X_H2O       X_CO2'
        )

    # create a new reactor
    gas.TDY = TDY
    r = ct.IdealGasReactor(gas)
    r.volume = rvol

    # create a reservoir to represent the reactor immediately upstream. Note
    # that the gas object is set already to the state of the upstream reactor
    upstream = ct.Reservoir(gas, name='upstream')

    # create a reservoir for the reactor to exhaust into. The composition of
    # this reservoir is irrelevant.
    downstream = ct.Reservoir(gas, name='downstream')

    # Add the reacting surface to the reactor. The area is set to the desired
    # catalyst area in the reactor.
    rsurf = ct.ReactorSurface(surf, r, A=cat_area)

    # The mass flow rate into the reactor will be fixed by using a
    # MassFlowController object.
    # mass_flow_rate = velocity * gas.density_mass * area  # kg/s
    # mass_flow_rate = flow_rate * gas.density_mass
    m = ct.MassFlowController(upstream, r, mdot=mass_flow_rate)

    # We need an outlet to the downstream reservoir. This will determine the
    # pressure in the reactor. The value of K will only affect the transient
    # pressure difference.
    v = ct.PressureController(r, downstream, master=m, K=1e-5)

    sim = ct.ReactorNet([r])
    sim.max_err_test_fails = 12

    # set relative and absolute tolerances on the simulation
    sim.rtol = 1.0e-10
    sim.atol = 1.0e-20

    gas_names = gas.species_names
    surf_names = surf.species_names
    gas_out = []
    surf_out = []
    dist_array = []
    T_array = []

    surf.set_multiplier(0.0)  # no surface reactions until the gauze
    for n in range(N_reactors):
        # Set the state of the reservoir to match that of the previous reactor
        gas.TDY = r.thermo.TDY
        upstream.syncState()
        if n == on_catalyst:
            surf.set_multiplier(1.0)
            if sens is not False:
                surf.set_multiplier(1.0 + sens[0], sens[1])
        if n == off_catalyst:
            surf.set_multiplier(0.0)
        sim.reinitialize()
        sim.advance_to_steady_state()
        dist = n * reactor_len * 1.0e3  # distance in mm
        dist_array.append(dist)
        T_array.append(surf.T)
        kmole_flow_rate = mass_flow_rate / gas.mean_molecular_weight  # kmol/s
        gas_out.append(1000 * 60 * kmole_flow_rate *
                       gas.X.copy())  # molar flow rate in moles/minute
        surf_out.append(surf.X.copy())

        # stop simulation when things are done changing, to avoid getting so many COVDES errors
        if n >= 1001:
            if np.max(abs(np.subtract(gas_out[-2], gas_out[-1]))) < 1e-15:
                break

        # make reaction diagrams
        # out_dir = 'rxnpath'
        # os.path.exists(out_dir) or os.makedirs(out_dir)
        # elements = ['H', 'O']
        # locations_of_interest = [1000, 1200, 1400, 1600, 1800, 1999]
        # if sens is False:
        #     if n in locations_of_interest:
        #             location = str(int(n / 100))
        #             diagram = ct.ReactionPathDiagram(surf, 'X')
        #             diagram.title = 'rxn path'
        #             diagram.label_threshold = 1e-9
        #             dot_file = f"{out_dir}/rxnpath-{ratio:.1f}-x-{location}mm.dot"
        #             img_file = f"{out_dir}/rxnpath-{ratio:.1f}-x-{location}mm.pdf"
        #             diagram.write_dot(dot_file)
        #             os.system('dot {0} -Tpng -o{1} -Gdpi=200'.format(dot_file, img_file))
        #
        #             for element in elements:
        #                 diagram = ct.ReactionPathDiagram(surf, element)
        #                 diagram.title = element + 'rxn path'
        #                 diagram.label_threshold = 1e-9
        #                 dot_file = f"{out_dir}/rxnpath-{ratio:.1f}-x-{location}mm-{element}.dot"
        #                 img_file = f"{out_dir}/rxnpath-{ratio:.1f}-x-{location}mm-{element}.pdf"
        #                 diagram.write_dot(dot_file)
        #                 os.system('dot {0} -Tpng -o{1} -Gdpi=200'.format(dot_file, img_file))
        # else:
        #     pass

        if verbose is True:
            if not n % 100:
                print(
                    '  {0:10f}  {1:10f}  {2:10f}  {3:10f} {4:10f} {5:10f} {6:10f}'
                    .format(
                        dist,
                        *gas['CH4(2)', 'O2(3)', 'H2(6)', 'CO(7)', 'H2O(5)',
                             'CO2(4)'].X * 1000 * 60 * kmole_flow_rate))

    gas_out = np.array(gas_out)
    surf_out = np.array(surf_out)
    gas_names = np.array(gas_names)
    surf_names = np.array(surf_names)
    data_out = gas_out, surf_out, gas_names, surf_names, dist_array, T_array, i_ar, n_surf_reactions
    print(
        f"Finished monolith simulation for CH4 and O2 concs {mol_in[0], mol_in[1]} on thread {threading.get_ident()}"
    )
    return data_out
Пример #18
0
# can access variables defined in the calling scope, including state variables
# of the Reactor object (combustor) itself.


def mdot(t):
    return combustor.mass / residence_time


inlet_mfc = ct.MassFlowController(inlet, combustor, mdot=mdot)

# A PressureController has a baseline mass flow rate matching the 'master'
# MassFlowController, with an additional pressure-dependent term. By explicitly
# including the upstream mass flow rate, the pressure is kept constant without
# needing to use a large value for 'K', which can introduce undesired stiffness.
outlet_mfc = ct.PressureController(combustor,
                                   exhaust,
                                   master=inlet_mfc,
                                   K=0.01)

# the simulation only contains one reactor
sim = ct.ReactorNet([combustor])

# Run a loop over decreasing residence times, until the reactor is extinguished,
# saving the state after each iteration.
states = ct.SolutionArray(gas, extra=['tres'])

residence_time = 0.1  # starting residence time
while combustor.T > 500:
    sim.set_initial_time(0.0)  # reset the integrator
    sim.advance_to_steady_state()
    print('tres = {:.2e}; T = {:.1f}'.format(residence_time, combustor.T))
    states.append(combustor.thermo.state, tres=residence_time)
Пример #19
0
def run_reactor(
    cti_file,
    t_array=[548],
    p_array=[1],
    v_array=[2.7155e-8], #14*7*(140e-4)^2*π/2*0.9=0.02715467 (cm3)
    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 mi-crochannels 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

    repo = git.Repo("/work/westgroup/lee.ting/cantera/ammonia/")
    git_sha = str(repo.head.commit)[0:6]
    git_msg = str(repo.head.commit.message)[0:20].replace(" ", "_").replace("'", "_")

    # this should probably be outside of function
    settings = list(itertools.product(t_array, p_array, v_array, o2_array, nh3_array))

    # constants
    pi = math.pi

    # set initial temps, pressures, concentrations
    temp = settings[array_i][0]  # kelvin
    temp_str = str(temp)[0:3]
    pressure = settings[array_i][1] * ct.one_atm  # Pascals

    X_o2 = settings[array_i][3]
    x_O2_str = str(X_o2)[0:3].replace(".", "_")
    if X_o2 == 0.88:
        X_he = 0.054
    else:
        X_he = 0
    X_nh3 = (1 - (X_o2 + X_he)) * (settings[array_i][4])
    x_NH3_str = str(X_nh3)[0:8].replace(".", "_")
    
    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_grab = ct.Interface(cti_file,'surface1_grab', [gas_grab])
    surf = ct.Interface(cti_file, "surface1", [gas])

    # gas_grab.TPX =
    gas.TPX = temp, pressure, concentrations_rmg
    surf.TP = temp, pressure

    # create gas inlet
    inlet = ct.Reservoir(gas)

    # create gas outlet
    exhaust = ct.Reservoir(gas)

    # Reactor volume
    rradius = 1.4e-4 #140µm to 0.00014m
    rlength = 9e-3 #9mm to 0.009m
    rvol = (rradius ** 2) * pi * rlength / 2

    # Catalyst Surface Area
    site_density = (surf.site_density * 1000)  # [mol/m^2]cantera uses kmol/m^2, convert to mol/m^2
    cat_area = rradius * 2 / 2 * pi * rlength # [m^3]
    #suface site density = 1.86e-9 mol/cm2 = 1.96e-5 mol/m2; molecular weight for Pt = 195.084 g/mol
    # per kg has 5.125997 moles Pt = 5.125997*6.022e23/1.12e15(cm-2) = 2.756138744e9 cm2/kg = 2.756e5m2/kg

    # 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"

    rsurf = ct.ReactorSurface(surf, r, A=cat_area)
    r.volume = rvol
    surf.coverages = "X(1):1.0"

    # flow controllers (Graaf measured flow at 293.15 and 1 atm)
    one_atm = ct.one_atm
    FC_temp = 293.15
    volume_flow = settings[array_i][2]  # [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-11
    sim.atol = 1.0e-22

    #################################################
    # Run single reactor
    #################################################

    # round numbers so they're easier to read
    # temp_str = '%s' % '%.3g' % tempn

    cat_area_str = "%s" % "%.3g" % cat_area
    results_path = (
        os.path.dirname(os.path.abspath(__file__))
        + f"/{git_sha}_{git_msg}/{reactor_type_str}/transient/{temp_str}/results"
    )
    results_path_csp = (
        os.path.dirname(os.path.abspath(__file__))
        + f"/{git_sha}_{git_msg}/{reactor_type_str}/transient/{temp_str}/results/csp"
    )
    flux_path = (
        os.path.dirname(os.path.abspath(__file__))
        + f"/{git_sha}_{git_msg}/{reactor_type_str}/transient/{temp_str}/flux_diagrams/{x_O2_str}/{x_NH3_str}"
    )
    try:
        os.makedirs(results_path, exist_ok=True)
    except OSError as error:
        print(error)

    try:
        os.makedirs(results_path_csp, 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 might be redundant, not sure.

    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"
    )
    output_filename_csp = (
        results_path_csp
        + f"/Spinning_basket_area_{cat_area_str}_energy_{energy}"
        + f"_temp_{temp}_h2_{x_O2_str}_NH3_{x_NH3_str}.csv"
    )
    outfile = open(output_filename, "w")
    outfile_csp = open(output_filename_csp, "w")
    writer = csv.writer(outfile)
    writer_csp = csv.writer(outfile_csp)

    # 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)"]  #change THIS to your species, can add "," and other species

        # turn on sensitive reactions/species
        for i in range(gas.n_reactions):
            r.add_sensitivity_reaction(i)

        for i in range(surf.n_reactions):
            rsurf.add_sensitivity_reaction(i)

        # 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(
            [
                "T (C)",
                "P (atm)",
                "V (M^3/s)",
                "X_nh3 initial", 
                "X_o2 initial",
                "X_he initial",
                "(NH3/O2)",
                "T (C) final",
                "Rtol",
                "Atol",
                "reactor type",
            ]
            + 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(
            [
                "T (C)",
                "P (atm)",
                "V (M^3/s)",
                "X_nh3 initial",
                "X_o2 initial",
                "X_he initial",
                "(NH3/O2)",
                "T (C) final",
                "Rtol",
                "Atol",
                "reactor type",
            ]
            + gas.species_names
            + surf.species_names
            + gas_ROP_str
            + gas_surf_ROP_str
            + surf_ROP_str
            + gasrxn_ROP_str
            + surfrxn_ROP_str
        )

    writer_csp.writerow(
        ["iter", "t", "dt", "Density[kg/m3]", "Pressure[Pascal]", "Temperature[K]",]
        + gas.species_names
        + surf.species_names
    )

    t = 0.0
    dt = 0.1
    iter_ct = 0
    # run the simulation
    first_run = True

    while t < reactime:
        # save flux diagrams at beginning of run
        if first_run == True:
            save_flux_diagrams(gas, suffix=flux_path, timepoint="beginning")
            save_flux_diagrams(surf, suffix=flux_path, timepoint="beginning")
            first_run = False
        t += dt
        sim.advance(t)
#         if t % 10 < 0.01:

        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(
                [
                    temp,
                    pressure,
                    volume_flow,
                    X_nh3,
                    X_o2,
                    X_he,
                    o2_ratio,
                    gas.T,
                    sim.rtol,
                    sim.atol,
                    reactor_type_str,
                ]
                + 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(
                [
                    temp,
                    pressure,
                    volume_flow,
                    X_nh3,
                    X_o2,
                    X_he,
                    o2_ratio,
                    gas.T,
                    sim.rtol,
                    sim.atol,
                    reactor_type_str,
                ]
                + 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)
            )

        writer_csp.writerow(
            [
                iter_ct,
                sim.time,
                dt,
                gas.density,
                gas.P,
                gas.T,
            ]
            + list(gas.X)
            + list(surf.X)
        )

        iter_ct += 1

    outfile.close()
    outfile_csp.close()

    # save flux diagrams at the end of the run
    save_flux_diagrams(gas, suffix=flux_path, timepoint="end")
    save_flux_diagrams(surf, suffix=flux_path, timepoint="end")
    return