From charlesreid1

Let's analyze the effect of diluants (nitrogen and carbon dioxide) on the adiabatic flame temperature. Why, you ask? This issue of dilution is of central importance to oxy-fuel combustion, in which effluent gas containing carbon dioxide is recycled into the front of the reactor, so you're not burning with pure oxygen - a big safety hazard and an extremely hot process that'll mess up air-fired reactors.

Background

Adiabatic Flame Temperature Review

Let's review what the AFT is.

Computing the AFT in Cantera

We can compute an adiabatic flame temperature with Cantera by initializing a batch reactor, which will be adiabatic by default, and advancing it until combustion has completed. The final temperature is the adiabatic flame temperature.

in pseudocode,

function compute_adiabatic_flame_T:
    create gas phase object with associated reaction network
    set gas state
    create reactor with gas in it
    create reactor network with reactor in it
    advance reactor network for a while
    return reactor temperature

Translating that to real Python code,

from Cantera import *
from Cantera.Reactor import *
from numpy import *

def compute_adiabatic_flame_T( X0, T0, P0, dt=5.0e-3 ):
    print "Computing an adiabatic flame temperature..."
    g = GRI30()
    g.set(X = X0, T = T0, P = P0)
    r = Reactor(g)
    n = ReactorNet([r])
    ttotal = 0.10
    t = 0.0
    while t < ttotal:
        t = t + dt
        n.advance(t)
    return r.temperature()

Now we can feed a list of composition vectors or composition strings, and get a list of adiabatic flame temperatures.

Adiabatic Flame Temperature vs Equivalence Ratio

A classical plot for adiabatic flame temperature is the adiabatic flame temperature versus equivalence ratio. The equivalence ratio is defined as:


\phi = \frac{ \mbox{fuel-oxidizer ratio} }{ \mbox{stoich fuel-oxidizer ratio} }

Now we can continue building on the code above. Let's create a function to vary equivalence ratio, and compute a corresponding adiabatic flame temperature. We won't worry for now about how to go from an equivalence ratio to a gas composition.

First, the pseudocode:

for phi in range_of_phis:
 convert phi to composition
 call compute_adiabatic_flame_T

Next, continuing the script above,

def equivalence_ratio_test():
    T0 = 1073.15
    P0 = 3*OneAtm
    phis = logspace(-1,1,10)
    
    afts = []
    for phi in phis:
        X = phi_to_X(phi)
        afts.append(compute_adiabatic_flame_T(X,T0,P0))
    
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.semilogx(phis,afts,'bo')
    ax.set_xlabel('Phi')
    ax.set_ylabel('Adiabatic Flame Temp [K]')
    ax.set_title('Adiab Flame Temp vs Equivalence Ratio')
    fig.savefig('AFTvsDilFrac.eps')
    fig.savefig('AFTvsDilFrac.png')

    plt.draw()
    plt.show()

Nitrogen and Carbon Dioxide Dilution

Air is composed of a 3.7:1 mix of nitrogen and oxygen. To mimic air in an oxy-fired system, an operator would try and mimic the dilution of oxygen with carbon dioxide by mixing them in a similar ratio. However, this is a variable that the operator can (must) control, because not all of the properties of a carbon dioxide-diluted flame match those of a nitrogen-diluted flame. It can be varied to make those properties match more closely.

I'll be analyzing the case of ethane combustion (mainly because ethane is slightly easier to ignite, meaning the combustion simulations are shorter and therefore faster).

Nitrogen vs Carbon Dioxide

(Comparison of physical properties, thermal heat capacities, etc.)

Diluting Function

It will be useful to have a function that takes a diluant species (like 'N2' or 'CO2') and a fraction, and dilutes the mixture of fuel and oxidizer accordingly.

If we have a mixture of N moles, and we are diluting it with a fraction y of nitrogen or carbon dioxide, we can dilute it in one of two ways:

Method 1: Maintain N total moles by taking (1-y)N moles of the original mixture and yN moles of diluant.

Method 2: Maintain N moles of original mixture by taking N moles of the original mixture and \frac{y}{1-y} N moles of diluant.

Method 2 makes life easier, because that way we only have to modify one component - the diluent.

Let's assume an equivalence ratio value \phi of (.............), which means our fuel to oxidizer ratio is (.......) The pseudocode, then, is:

function component fraction to composition:
     moles of ethane = 1.0
     moles of oxygen = 2.0
     total moles = moles of ethane + moles of oxygen 
     moles nitrogen = ( y / (1-y) ) * total moles
     assemble composition vector
     return composition vector

Converting this to Python code,

def component_frac_to_X(spname,frac):
    d = {}
    d['C2H6'] = 1.0
    d['O2']   = 2.0
    mole_sum  = sum([d[k] for k in d.keys()])
    d[spname]   = (frac/(1-frac))*mole_sum

    return convert_composition_dict_to_string(d)

Adiabatic Flame Temperature Versus Diluant Concentration

Now we can get on with the business of computing our adiabatic flame temperature as a function of diluant concentration.

For nitrogen concentration, let's analyze the range of 10% to 90%, not because we would actually dilute with nitrogen, but just to see what kind of functional dependence the model predicts.

For carbon dioxide concentration, let's also analyze 10% to 90%. Then we can compare the trends, as well as identifying the CO2 dilution that gives the same adiabatic flame temperature as an air-fired flame.

Starting with the pseudocode:

for each nitrogen fraction:
  call component fraction to composition
  call compute adiabatic flame temperature

for each carbon dioxide fraction:
  call component fraction to composition
  call compute adiabatic flame temperature

We put the two in separate loops, in case we want a different number of dilution fractions for the species.

Translating this into Python code,

def nitrogen_co2_test():
    T0 = 1073.15
    P0 = 3*OneAtm
    n2fracs  = linspace(0.1,0.9,9)
    co2fracs = linspace(0.1,0.9,9)

    n2temps = []
    co2temps = []
    for n2frac in n2fracs:
        n2X = component_frac_to_X('N2',n2frac)
        n2temps.append(compute_adiabatic_flame_T(n2X,T0,P0))

    for co2frac in co2fracs:
        co2X = component_frac_to_X('CO2',co2frac)
        co2temps.append(compute_adiabatic_flame_T(co2X,T0,P0,1.0e-3))

    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.plot(n2fracs,n2temps,'bo-',  label='N2')
    ax.plot(co2fracs,co2temps,'ro-',label='CO2')
    ax.set_xlabel('Diluant Fraction')
    ax.set_ylabel('Adiabatic Flame Temp [K]')
    ax.set_title('Adiab Flame Temp vs Diluant Fraction')
    fig.savefig('AFTvsDilFrac.eps')
    fig.savefig('AFTvsDilFrac.png')

    plt.draw()
    plt.show()


Flags