From 840bdfffab00fe2731aa0edfd8ecffcedb33b0ae Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Thu, 7 Feb 2013 23:41:30 +0000 Subject: [PATCH] [Cython] Translated more samples to use use the new API --- .../examples/onedim/stagnation_flame.py | 94 +++++++++++ .../cantera/examples/reactors/combustor.py | 92 ++++++++++ .../cython/cantera/examples/reactors/mix1.py | 72 ++++++++ .../cantera/examples/reactors/piston.py | 90 ++++++++++ .../cantera/examples/reactors/reactor1.py | 63 +++++++ .../cantera/examples/reactors/reactor2.py | 113 +++++++++++++ .../cantera/examples/reactors/sensitivity1.py | 92 ++++++++++ .../cantera/examples/reactors/surf_pfr.py | 158 ++++++++++++++++++ 8 files changed, 774 insertions(+) create mode 100644 interfaces/cython/cantera/examples/onedim/stagnation_flame.py create mode 100644 interfaces/cython/cantera/examples/reactors/combustor.py create mode 100644 interfaces/cython/cantera/examples/reactors/mix1.py create mode 100644 interfaces/cython/cantera/examples/reactors/piston.py create mode 100644 interfaces/cython/cantera/examples/reactors/reactor1.py create mode 100644 interfaces/cython/cantera/examples/reactors/reactor2.py create mode 100644 interfaces/cython/cantera/examples/reactors/sensitivity1.py create mode 100644 interfaces/cython/cantera/examples/reactors/surf_pfr.py diff --git a/interfaces/cython/cantera/examples/onedim/stagnation_flame.py b/interfaces/cython/cantera/examples/onedim/stagnation_flame.py new file mode 100644 index 000000000..afd7f6ddb --- /dev/null +++ b/interfaces/cython/cantera/examples/onedim/stagnation_flame.py @@ -0,0 +1,94 @@ +""" +A detached flat flame stabilized at a stagnation point + +This script simulates a lean hydrogen-oxygen flame stabilized in a strained +flowfield at an axisymmetric stagnation point on a non-reacting surface. The +solution begins with a flame attached to the inlet (burner), and the mass flow +rate is progressively increased, causing the flame to detach and move closer +to the surface. + +This example illustrates use of the new 'prune' grid refinement parameter, +which allows grid points to be removed if they are no longer required to +resolve the solution. This is important here, since the flamefront moves as +the mass flowrate is increased. Without using 'prune', a large number of grid +points would be concentrated upsteam of the flame, where the flamefront had +been previously. (To see this, try setting prune to zero.) +""" + +import cantera as ct +import numpy as np +import os + +# parameter values +p = 0.05 * ct.one_atm # pressure +tburner = 373.0 # burner temperature +tsurf = 500.0 + +# each mdot value will be solved to convergence, with grid refinement, and +# then that solution will be used for the next mdot +mdot = [0.06, 0.07, 0.08, 0.09, 0.1, 0.11, 0.12] # kg/m^2/s + +rxnmech = 'h2o2.cti' # reaction mechanism file +comp = 'H2:1.8, O2:1, AR:7' # premixed gas composition + +# The solution domain is chosen to be 50 cm, and a point very near the +# downstream boundary is added to help with the zero-gradient boundary +# condition at this boundary. +initial_grid = np.linspace(0.0, 0.2, 12) # m + +tol_ss = [1.0e-5, 1.0e-13] # [rtol atol] for steady-state problem +tol_ts = [1.0e-4, 1.0e-9] # [rtol atol] for time stepping +loglevel = 1 # amount of diagnostic output (0 to 5) +refine_grid = True + +# Grid refinement parameters +ratio = 3 +slope = 0.1 +curve = 0.2 +prune = 0.06 + +# Set up the problem +gas = ct.Solution(rxnmech) + +# set state to that of the unburned gas at the burner +gas.TPX = tburner, p, comp + +# Create the stagnation flow object with a non-reactive surface. (To make the +# surface reactive, supply a surface reaction mechanism. See example +# catalytic_combustion.py for how to do this.) +sim = ct.ImpingingJet(gas=gas, grid=initial_grid) + +# set the properties at the inlet +sim.inlet.mdot = mdot[0] +sim.inlet.X = comp +sim.inlet.T = tburner + +# set the surface state +sim.surface.T = tsurf + +sim.flame.set_steady_tolerances(default=tol_ss) +sim.flame.set_transient_tolerances(default=tol_ts) +sim.set_grid_min(1e-4) +sim.energy_enabled = False + +sim.set_initial_guess(products='equil') # assume adiabatic equilibrium products +sim.show_solution() + +sim.solve(loglevel, refine_grid) + +sim.set_refine_criteria(ratio=ratio, slope=slope, curve=curve, prune=prune) +sim.energy_enabled = True + +outfile = 'stflame1.xml' +if os.path.exists(outfile): + os.remove(outfile) + +for m,md in enumerate(mdot): + sim.inlet.mdot = md + sim.solve(loglevel,refine_grid) + sim.save(outfile, 'mdot{}'.format(m), 'mdot = {} kg/m2/s'.format(md)) + + # write the velocity, temperature, and mole fractions to a CSV file + sim.write_csv('stflame1_{}.csv'.format(m), quiet=False) + +sim.show_stats() diff --git a/interfaces/cython/cantera/examples/reactors/combustor.py b/interfaces/cython/cantera/examples/reactors/combustor.py new file mode 100644 index 000000000..62e97eb35 --- /dev/null +++ b/interfaces/cython/cantera/examples/reactors/combustor.py @@ -0,0 +1,92 @@ +""" +A combustor. Two separate stream - one pure methane and the other air, both at +300 K and 1 atm flow into an adiabatic combustor where they mix. We are +interested in the steady-state burning solution. Since at 300 K no reaction +will occur between methane and air, we need to use an 'igniter' to initiate +the chemistry. A simple igniter is a pulsed flow of atomic hydrogen. After the +igniter is turned off, the system approaches the steady burning solution. +""" + +import math +import csv + +import cantera as ct + +# use reaction mechanism GRI-Mech 3.0 + +gas = ct.Solution('gri30.xml') + +# create a reservoir for the fuel inlet, and set to pure methane. +gas.TPX = 300.0, ct.one_atm, 'CH4:1.0' +fuel_in = ct.Reservoir(gas) +fuel_mw = gas.mean_molecular_weight + +# use predefined function Air() for the air inlet +air = ct.Solution('air.xml') +air_in = ct.Reservoir(air) +air_mw = air.mean_molecular_weight + +# to ignite the fuel/air mixture, we'll introduce a pulse of radicals. The +# steady-state behavior is independent of how we do this, so we'll just use a +# stream of pure atomic hydrogen. +gas.TPX = 300.0, ct.one_atm, 'H:1.0' +igniter = ct.Reservoir(gas) + +# create the combustor, and fill it in initially with N2 +gas.TPX = 300.0, ct.one_atm, 'N2:1.0' +combustor = ct.Reactor(gas) +combustor.volume = 1.0 + +# create a reservoir for the exhaust +exhaust = ct.Reservoir(gas) + +# lean combustion, phi = 0.5 +equiv_ratio = 0.5 + +# compute fuel and air mass flow rates +factor = 0.1 +air_mdot = factor * 9.52 * air_mw +fuel_mdot = factor * equiv_ratio * fuel_mw + +# create and install the mass flow controllers. Controllers m1 and m2 provide +# constant mass flow rates, and m3 provides a short Gaussian pulse only to +# ignite the mixture +m1 = ct.MassFlowController(fuel_in, combustor, mdot=fuel_mdot) + +# note that this connects two reactors with different reaction mechanisms and +# different numbers of species. Downstream and upstream species are matched by +# name. +m2 = ct.MassFlowController(air_in, combustor, mdot=air_mdot) + +# The igniter will use a Gaussian time-dependent mass flow rate. +fwhm = 0.2 +amplitude = 0.1 +t0 = 1.0 +igniter_mdot = lambda t: amplitude * math.exp(-(t-t0)**2 * 4 * math.log(2) / fwhm**2) +m3 = ct.MassFlowController(igniter, combustor, mdot=igniter_mdot) + +# put a valve on the exhaust line to regulate the pressure +v = ct.Valve(combustor, exhaust, K=1.0) + +# the simulation only contains one reactor +sim = ct.ReactorNet([combustor]) + +# take single steps to 6 s, writing the results to a CSV file for later +# plotting. +tfinal = 6.0 +tnow = 0.0 +Tprev = combustor.T +tprev = tnow +outfile = open('combustor.csv','w') +csvwriter = csv.writer(outfile) + +while tnow < tfinal: + tnow = sim.step(tfinal) + tres = combustor.mass/v.mdot(tnow) + Tnow = combustor.T + if abs(Tnow - Tprev) > 1.0 or tnow-tprev > 2e-2: + tprev = tnow + Tprev = Tnow + csvwriter.writerow([tnow, combustor.T, tres] + + list(combustor.thermo.X)) +outfile.close() diff --git a/interfaces/cython/cantera/examples/reactors/mix1.py b/interfaces/cython/cantera/examples/reactors/mix1.py new file mode 100644 index 000000000..b592362f6 --- /dev/null +++ b/interfaces/cython/cantera/examples/reactors/mix1.py @@ -0,0 +1,72 @@ +""" +Mixing two streams. + +Since reactors can have multiple inlets and outlets, they can be used to +implement mixers, splitters, etc. In this example, air and methane are mixed +in stoichiometric proportions. Due to the low temperature, no reactions occur. +Note that the air stream and the methane stream use *different* reaction +mechanisms, with different numbers of species and reactions. When gas flows +from one reactor or reservoir to another one with a different reaction +mechanism, species are matched by name. If the upstream reactor contains a +species that is not present in the downstream reaction mechanism, it will be +ignored. In general, reaction mechanisms for downstream reactors should +contain all species that might be present in any upstream reactor. +""" + +import cantera as ct + +# Use air for stream a. +gas_a = ct.Solution('air.xml') +gas_a.TPX = 300.0, ct.one_atm, 'O2:0.21, N2:0.78, AR:0.01' +rho_a = gas_a.density + + +# Use GRI-Mech 3.0 for stream b (methane) and for the mixer. If it is desired +# to have a pure mixer, with no chemistry, use instead a reaction mechanism +# for gas_b that has no reactions. +gas_b = ct.Solution('gri30.xml') +gas_b.TPX = 300.0, ct.one_atm, 'CH4:1' +rho_b = gas_b.density + +# Create reservoirs for the two inlet streams and for the outlet stream. The +# upsteam reservoirs could be replaced by reactors, which might themselves be +# connected to reactors further upstream. The outlet reservoir could be +# replaced with a reactor with no outlet, if it is desired to integrate the +# composition leaving the mixer in time, or by an arbitrary network of +# downstream reactors. +res_a = ct.Reservoir(gas_a) +res_b = ct.Reservoir(gas_b) +downstream = ct.Reservoir(gas_b) + +# Create a reactor for the mixer. A reactor is required instead of a +# reservoir, since the state will change with time if the inlet mass flow +# rates change or if there is chemistry occurring. +gas_b.TPX = 300.0, ct.one_atm, 'O2:0.21, N2:0.78, AR:0.01' +mixer = ct.Reactor(gas_b) + +# create two mass flow controllers connecting the upstream reservoirs to the +# mixer, and set their mass flow rates to values corresponding to +# stoichiometric combustion. +mfc1 = ct.MassFlowController(res_a, mixer, mdot=rho_a*2.5/0.21) +mfc2 = ct.MassFlowController(res_b, mixer, mdot=rho_b*1.0) + +# connect the mixer to the downstream reservoir with a valve. +outlet = ct.Valve(mixer, downstream, K=10.0) + +sim = ct.ReactorNet([mixer]) + +# Since the mixer is a reactor, we need to integrate in time to reach steady +# state. A few residence times should be enough. +print('{:>14s} {:>14s} {:>14s} {:>14s} {:>14s}'.format( + 't [s]', 'T [K]', 'h [J/kg]', 'P [Pa]', 'X_CH4')) + +t = 0.0 +for n in range(30): + tres = mixer.mass/(mfc1.mdot(t) + mfc2.mdot(t)) + t += 0.5*tres + sim.advance(t) + print('{:14.5g} {:14.5g} {:14.5g} {:14.5g} {:14.5g}'.format( + t, mixer.T, mixer.thermo.h, mixer.thermo.P, mixer.thermo['CH4'].X[0])) + +# view the state of the gas in the mixer +print(mixer.thermo.report()) diff --git a/interfaces/cython/cantera/examples/reactors/piston.py b/interfaces/cython/cantera/examples/reactors/piston.py new file mode 100644 index 000000000..f8a21bcb2 --- /dev/null +++ b/interfaces/cython/cantera/examples/reactors/piston.py @@ -0,0 +1,90 @@ +""" +Gas 1: a stoichiometric H2/O2/Ar mixture +Gas 2: a wet CO/O2 mixture + + ------------------------------------- + | || | + | || | + | gas 1 || gas 2 | + | || | + | || | + ------------------------------------- + +The two volumes are connected by an adiabatic free piston. The piston speed is +proportional to the pressure difference between the two chambers. + +Note that each side uses a *different* reaction mechanism +""" + +import sys + +import cantera as ct + +fmt = '%10.3f %10.1f %10.4f %10.4g %10.4g %10.4g %10.4g' +print('%10s %10s %10s %10s %10s %10s %10s' % ('time [s]','T1 [K]','T2 [K]', + 'V1 [m^3]', 'V2 [m^3]', + 'V1+V2 [m^3]','X(CO)')) + +gas1 = ct.Solution('h2o2.cti') +gas1.TPX = 900.0, ct.one_atm, 'H2:2, O2:1, AR:20' + +gas2 = ct.Solution('gri30.xml') +gas2.TPX = 900.0, ct.one_atm, 'CO:2, H2O:0.01, O2:5' + +r1 = ct.Reactor(gas1) +r1.volume = 0.5 +r2 = ct.Reactor(gas2) +r2.volume = 0.1 +w = ct.Wall(r1, r2, K=1.0e3) + +net = ct.ReactorNet([r1, r2]) + +tim = [] +t1 = [] +t2 = [] +v1 = [] +v2 = [] +v = [] +xco = [] +xh2 = [] + +for n in range(30): + time = (n+1)*0.002 + net.advance(time) + print(fmt % (time, r1.T, r2.T, r1.volume, r2.volume, + r1.volume + r2.volume, r2.thermo['CO'].X[0])) + + tim.append(time * 1000) + t1.append(r1.T) + t2.append(r2.T) + v1.append(r1.volume) + v2.append(r2.volume) + v.append(r1.volume + r2.volume) + xco.append(r2.thermo['CO'].X[0]) + xh2.append(r1.thermo['H2'].X[0]) + + +# plot the results if matplotlib is installed. +if '--plot' in sys.argv: + import matplotlib.pyplot as plt + plt.subplot(2,2,1) + plt.plot(tim,t1,'-',tim,t2,'r-') + plt.xlabel('Time (ms)') + plt.ylabel('Temperature (K)') + plt.subplot(2,2,2) + plt.plot(tim,v1,'-',tim,v2,'r-',tim,v,'g-') + plt.xlabel('Time (ms)') + plt.ylabel('Volume (m3)') + plt.subplot(2,2,3) + plt.plot(tim,xco) + plt.xlabel('Time (ms)') + plt.ylabel('CO Mole Fraction (right)') + plt.subplot(2,2,4) + plt.plot(tim,xh2) + plt.xlabel('Time (ms)') + plt.ylabel('H2 Mole Fraction (left)') + plt.tight_layout() + plt.show() + +else: + print("""To view a plot of these results, run this script with the option --plot""") diff --git a/interfaces/cython/cantera/examples/reactors/reactor1.py b/interfaces/cython/cantera/examples/reactors/reactor1.py new file mode 100644 index 000000000..df21a3374 --- /dev/null +++ b/interfaces/cython/cantera/examples/reactors/reactor1.py @@ -0,0 +1,63 @@ +""" +Constant-pressure, adiabatic kinetics simulation. +""" + +import sys +import numpy as np + +import cantera as ct + +gri3 = ct.Solution('gri30.xml') +air = ct.Solution('air.xml') + +gri3.TPX = 1001.0, ct.one_atm, 'H2:2,O2:1,N2:4' +r = ct.Reactor(gri3) +env = ct.Reservoir(air) + +# Define a wall between the reactor and the environment, and +# make it flexible, so that the pressure in the reactor is held +# at the environment pressure. +w = ct.Wall(r, env) +w.expansion_rate_coeff = 1.0e6 # set expansion parameter. dV/dt = KA(P_1 - P_2) +w.area = 1.0 + +sim = ct.ReactorNet([r]) +time = 0.0 +times = np.zeros(100) +data = np.zeros((100,4)) + +print('%10s %10s %10s %14s' % ('t [s]','T [K]','P [Pa]','u [J/kg]')) +for n in range(100): + time += 1.e-5 + sim.advance(time) + times[n] = time * 1e3 # time in ms + data[n,0] = r.T + data[n,1:] = r.thermo['OH','H','H2'].X + print('%10.3e %10.3f %10.3f %14.6e' % (sim.time, r.T, + r.thermo.P, r.thermo.u)) + +# Plot the results if matplotlib is installed. +# See http://matplotlib.org/ to get it. +if '--plot' in sys.argv[1:]: + import matplotlib.pyplot as plt + plt.clf() + plt.subplot(2, 2, 1) + plt.plot(times, data[:,0]) + plt.xlabel('Time (ms)') + plt.ylabel('Temperature (K)') + plt.subplot(2, 2, 2) + plt.plot(times, data[:,1]) + plt.xlabel('Time (ms)') + plt.ylabel('OH Mole Fraction') + plt.subplot(2, 2, 3) + plt.plot(times, data[:,2]) + plt.xlabel('Time (ms)') + plt.ylabel('H Mole Fraction') + plt.subplot(2, 2, 4) + plt.plot(times,data[:,3]) + plt.xlabel('Time (ms)') + plt.ylabel('H2 Mole Fraction') + plt.tight_layout() + plt.show() +else: + print("To view a plot of these results, run this script with the option --plot") diff --git a/interfaces/cython/cantera/examples/reactors/reactor2.py b/interfaces/cython/cantera/examples/reactors/reactor2.py new file mode 100644 index 000000000..4a58f25e6 --- /dev/null +++ b/interfaces/cython/cantera/examples/reactors/reactor2.py @@ -0,0 +1,113 @@ +""" +This script simulates the following situation. A closed cylinder with volume 2 +m^3 is divided into two equal parts by a massless piston that moves with speed +proportional to the pressure difference between the two sides. It is +initially held in place in the middle. One side is filled with 1000 K argon at +20 atm, and the other with a combustible 500 K methane/air mixture at 0.1 atm +(phi = 1.1). At t = 0 the piston is released and begins to move due to the +large pressure difference, compressing and heating the methane/air mixture, +which eventually explodes. At the same time, the argon cools as it expands. +The piston is adiabatic, but some heat is lost through the outer cylinder +walls to the environment. + +Note that this simulation, being zero-dimensional, takes no account of shock +wave propagation. It is somewhat artifical, but nevertheless instructive. +""" + +import sys +import os +import csv +import numpy as np + +import cantera as ct + +#----------------------------------------------------------------------- +# First create each gas needed, and a reactor or reservoir for each one. +#----------------------------------------------------------------------- + +# create an argon gas object and set its state +ar = ct.Solution('argon.xml') +ar.TP = 1000.0, 20.0 * ct.one_atm + +# create a reactor to represent the side of the cylinder filled with argon +r1 = ct.Reactor(ar) + +# create a reservoir for the environment, and fill it with air. +env = ct.Reservoir(ct.Solution('air.xml')) + +# use GRI-Mech 3.0 for the methane/air mixture, and set its initial state +gri3 = ct.Solution('gri30.xml') +gri3.TPX = 500.0, 0.2 * ct.one_atm, 'CH4:1.1, O2:2, N2:7.52' + +# create a reactor for the methane/air side +r2 = ct.Reactor(gri3) + +#----------------------------------------------------------------------------- +# Now couple the reactors by defining common walls that may move (a piston) or +# conduct heat +#----------------------------------------------------------------------------- + +# add a flexible wall (a piston) between r2 and r1 +w = ct.Wall(r2, r1, A=1.0, K=0.5e-4, U=100.0) + +# heat loss to the environment. Heat loss always occur through walls, so we +# create a wall separating r1 from the environment, give it a non-zero area, +# and specify the overall heat transfer coefficient through the wall. +w2 = ct.Wall(r2, env, A=1.0, U=500.0) + +sim = ct.ReactorNet([r1, r2]) + +# Now the problem is set up, and we're ready to solve it. +print('finished setup, begin solution...') + +time = 0.0 +n_steps = 300 +outfile = open('piston.csv', 'w') +csvfile = csv.writer(outfile) +csvfile.writerow(['time (s)','T1 (K)','P1 (Bar)','V1 (m3)', + 'T2 (K)','P2 (Bar)','V2 (m3)']) +temp = np.zeros((n_steps, 2)) +pres = np.zeros((n_steps, 2)) +vol = np.zeros((n_steps, 2)) +tm = np.zeros(n_steps) + +for n in range(n_steps): + time += 4.e-4 + print(n, time, r2.T) + sim.advance(time) + tm[n] = time + temp[n,:] = r1.T, r2.T + pres[n,:] = 1.0e-5*r1.thermo.P, 1.0e-5*r2.thermo.P + vol[n,:] = r1.volume, r2.volume + csvfile.writerow([tm[n], temp[n,0], pres[n,0], vol[n,0], + temp[n,1], pres[n,1], vol[n,1]]) +outfile.close() +print('Output written to file piston.csv') +print('Directory: '+os.getcwd()) + +if '--plot' in sys.argv: + import matplotlib.pyplot as plt + plt.clf() + plt.subplot(2,2,1) + h = plt.plot(tm, temp[:,0],'g-',tm, temp[:,1],'b-') + #plt.legend(['Reactor 1','Reactor 2'],2) + plt.xlabel('Time (s)') + plt.ylabel('Temperature (K)') + + plt.subplot(2,2,2) + plt.plot(tm, pres[:,0],'g-',tm, pres[:,1],'b-') + #plt.legend(['Reactor 1','Reactor 2'],2) + plt.xlabel('Time (s)') + plt.ylabel('Pressure (Bar)') + + plt.subplot(2,2,3) + plt.plot(tm, vol[:,0],'g-',tm, vol[:,1],'b-') + #plt.legend(['Reactor 1','Reactor 2'],2) + plt.xlabel('Time (s)') + plt.ylabel('Volume (m$^3$)') + + plt.figlegend(h, ['Reactor 1', 'Reactor 2'], loc='lower right') + plt.tight_layout() + plt.show() +else: + print("""To view a plot of these results, run this script with the option -plot""") diff --git a/interfaces/cython/cantera/examples/reactors/sensitivity1.py b/interfaces/cython/cantera/examples/reactors/sensitivity1.py new file mode 100644 index 000000000..1257daecb --- /dev/null +++ b/interfaces/cython/cantera/examples/reactors/sensitivity1.py @@ -0,0 +1,92 @@ +""" +Constant-pressure, adiabatic kinetics simulation with sensitivity analysis +""" + +import sys +import numpy as np + +import cantera as ct + +gri3 = ct.Solution('gri30.xml') +temp = 1500.0 +pres = ct.one_atm + +gri3.TPX = temp, pres, 'CH4:0.1, O2:2, N2:7.52' +r = ct.Reactor(gri3) + +air = ct.Solution('air.xml') +air.TP = temp, pres +env = ct.Reservoir(air) + +# Define a wall between the reactor and the environment, and make it flexible, +# so that the pressure in the reactor is held at the environment pressure. +w = ct.Wall(r, env) +w.expansion_rate_coeff = 1.0e6 # set expansion parameter. dV/dt = KA(P_1 - P_2) +w.area = 1.0 + +sim = ct.ReactorNet([r]) + +# enable sensitivity with respect to the rates of the first 10 +# reactions (reactions 0 through 9) +for i in range(10): + r.add_sensitivity_reaction(i) + +# set the tolerances for the solution and for the sensitivity coefficients +sim.rtol = 1.0e-6 +sim.atol = 1.0e-15 +sim.rtol_sensitivity = 1.0e-6 +sim.atol_sensitivity = 1.0e-6 + + +n_times = 400 +tim = np.zeros(n_times) +data = np.zeros((n_times,6)) + +time = 0.0 +for n in range(n_times): + time += 5.0e-6 + sim.advance(time) + tim[n] = 1000 * time + data[n,0] = r.T + data[n,1:4] = r.thermo['OH','H','CH4'].X + + # sensitivity of OH to reaction 2 + data[n,4] = sim.sensitivity('OH',2) + + # sensitivity of OH to reaction 3 + data[n,5] = sim.sensitivity('OH',3) + + print('%10.3e %10.3f %10.3f %14.6e %10.3f %10.3f' % + (sim.time, r.T, r.thermo.P, r.thermo.u, data[n,4], data[n,5])) + +# plot the results if matplotlib is installed. +# see http://matplotlib.org/ to get it +if '--plot' in sys.argv: + import matplotlib.pyplot as plt + plt.subplot(2,2,1) + plt.plot(tim,data[:,0]) + plt.xlabel('Time (ms)') + plt.ylabel('Temperature (K)') + plt.subplot(2,2,2) + plt.plot(tim,data[:,1]) + plt.xlabel('Time (ms)') + plt.ylabel('OH Mole Fraction') + plt.subplot(2,2,3) + plt.plot(tim,data[:,2]) + plt.xlabel('Time (ms)') + plt.ylabel('H Mole Fraction') + plt.subplot(2,2,4) + plt.plot(tim,data[:,3]) + plt.xlabel('Time (ms)') + plt.ylabel('H2 Mole Fraction') + plt.tight_layout() + + plt.figure(2) + plt.plot(tim,data[:,4],'-',tim,data[:,5],'-g') + plt.legend([sim.sensitivity_parameter_name(2),sim.sensitivity_parameter_name(3)],'best') + plt.xlabel('Time (ms)') + plt.ylabel('OH Sensitivity') + plt.tight_layout() + plt.show() +else: + print("""To view a plot of these results, run this script with the option '--plot""") diff --git a/interfaces/cython/cantera/examples/reactors/surf_pfr.py b/interfaces/cython/cantera/examples/reactors/surf_pfr.py new file mode 100644 index 000000000..4f5bbef24 --- /dev/null +++ b/interfaces/cython/cantera/examples/reactors/surf_pfr.py @@ -0,0 +1,158 @@ +""" +This example solves a plug flow reactor problem, where the chemistry is +surface chemistry. The specific problem simulated is the partial oxidation of +methane over a platinum catalyst in a packed bed reactor. +""" + +import csv + +import cantera as ct + +# unit conversion factors to SI +cm = 0.01 +minute = 60.0 + +####################################################################### +# Input Parameters +####################################################################### + +tc = 800.0 # Temperature in Celsius +length = 0.3 * cm # Catalyst bed length +area = 1.0 * cm**2 # Catalyst bed area +cat_area_per_vol = 1000.0 / cm # Catalyst particle surface area per unit volume +velocity = 40.0 * cm / minute # gas velocity +porosity = 0.3 # Catalyst bed porosity + +# input file containing the surface reaction mechanism +cti_file = 'methane_pox_on_pt.cti' + +output_filename = 'surf_pfr_output.csv' + +# The PFR will be simulated by a chain of 'NReactors' stirred reactors. +NReactors = 201 +dt = 1.0 + +##################################################################### + +t = tc + 273.15 # convert to Kelvin + +# import the gas model and set the initial conditions +gas = ct.Solution(cti_file, 'gas') +gas.TPX = t, ct.one_atm, 'CH4:1, O2:1.5, AR:0.1' + +# import the surface model +surf = ct.Interface(cti_file,'Pt_surf', [gas]) +surf.TP = t, ct.one_atm + +rlen = length/(NReactors-1) +rvol = area * rlen * porosity + +outfile = open(output_filename,'w') +writer = csv.writer(outfile) +writer.writerow(['Distance (mm)', 'T (C)', 'P (atm)'] + + gas.species_names + surf.species_names) + +# catalyst area in one reactor +cat_area = cat_area_per_vol * rvol + +mass_flow_rate = velocity * gas.density * area + +# The plug flow reactor is represented by a linear chain of zero-dimensional +# reactors. The gas at the inlet to the first one has the specified inlet +# composition, and for all others the inlet composition is fixed at the +# composition of the reactor immediately upstream. Since in a PFR model there +# is no diffusion, the upstream reactors are not affected by any downstream +# reactors, and therefore the problem may be solved by simply marching from +# the first to last reactor, integrating each one to steady state. + +TDY = gas.TDY +cov = surf.coverages + +print((' {:>10s}'*4).format('distance', 'X_CH4', 'X_H2', 'X_CO')) + +for n in range(NReactors): + surf.TP = TDY[0], ct.one_atm + surf.coverages = cov + + # create a new reactor + gas.TDY = TDY + r = ct.Reactor(gas, energy='off') + r.volume = rvol + + # create a reservoir to represent the reactor immediately upstream. Note + # that the gas object is set already to the state of the upstream reactor + upstream = ct.Reservoir(gas, name='upstream') + + # create a reservoir for the reactor to exhaust into. The composition of + # this reservoir is irrelevant. + downstream = ct.Reservoir(gas, name='downstream') + + # use a 'Wall' object to implement the reacting surface in the reactor. + # Since walls have to be installed between two reactors/reserviors, we'll + # install it between the upstream reservoir and the reactor. The area is + # set to the desired catalyst area in the reactor, and surface reactions + # are included only on the side facing the reactor. + w = ct.Wall(upstream, r, A=cat_area, kinetics=[None, surf]) + + # We need a valve between the reactor and the downstream reservoir. This + # will determine the pressure in the reactor. Set K large enough that the + # pressure difference is very small. + v = ct.Valve(r, downstream, K=1e-4) + + # The mass flow rate into the reactor will be fixed by using a + # MassFlowController object. + m = ct.MassFlowController(upstream, r, mdot=mass_flow_rate) + + sim = ct.ReactorNet([r]) + sim.max_err_test_fails = 12 + + # set relative and absolute tolerances on the simulation + sim.rtol = 1.0e-9 + sim.atol = 1.0e-21 + + T_start, rho_start, Y_start = r.thermo.TDY + cov_start = surf.coverages + V_start = r.volume + + Tu_start, rhou_start, Yu_start = upstream.thermo.TDY + + time = 0 + all_done = False + while not all_done: + time += dt + sim.advance(time) + + # check whether surface coverages are in steady state. This will be + # the case if the creation and destruction rates for a surface (but + # not gas) species are equal. + all_done = True + + # Note: netProduction = creation - destruction. By supplying the + # surface object as an argument, only the values for the surface + # species are returned by these methods + sdot = surf.get_net_production_rates(surf) + cdot = surf.get_creation_rates(surf) + ddot = surf.get_destruction_rates(surf) + + for ks in range(surf.n_species): + ratio = abs(sdot[ks]/(cdot[ks] + ddot[ks])) + if ratio > 1.0e-9 or time < 10*dt: + all_done = False + + # Save the reactor and surface states, in preparation for the simulation + # of the next reactor downstream, where this object will set the inlet + # conditions and the initial surface coverages + TDY = r.thermo.TDY + cov = surf.coverages + + dist = n * rlen * 1.0e3 # distance in mm + + if not n % 10: + print((' {:10f}'*4).format(dist, *gas['CH4','H2','CO'].X)) + + # write the gas mole fractions and surface coverages vs. distance + writer.writerow([dist, r.T - 273.15, r.thermo.P/ct.one_atm] + + list(gas.X) + list(surf.coverages)) + +outfile.close() +print("Results saved to '{}'".format(output_filename))