SLACKHA / pyJac

Creates C and CUDA analytical Jacobians for chemical kinetics ODE systems
http://slackha.github.io/pyJac/
MIT License
52 stars 23 forks source link

Problem to reproduce chemical explosive mode analysis #25

Closed ghost closed 6 years ago

ghost commented 6 years ago

I doubt if CEMA is usable to other mechanism. Or the pyjac gave me wrong Jacobian.

I try to reproduce the figure 1-b of the following paper. I assumed that the GRI3.0 Mech in cantera is usable for H2-air autoignition as the mechanism from Li, J., Zhao, Z. W., Kazakov, A. & Dryer, F. L. 2004 An updated comprehensive kinetic model of hydrogen combustion. Intl J. Chem. Kinet. 36, 566–575., which was used in Lu's paper.

The problem is I did not detect the zero crossing. It is zero, but not crossing. And the transition is a very hard turn.

Paper: Lu, T. F., C. S. Yoo, J. H. Chen and C. K. Law (2010). "Three-dimensional direct numerical simulation of a turbulent lifted hydrogen jet flame in heated coflow: a chemical explosive mode analysis." Journal of Fluid Mechanics 652: 45-64.

Figure 1:Temperature and selected species profiles in auto-ignition of a stoichiometric hydrogen–air mixture under atmospheric pressure and an initial temperature of 1100 K. (a) Temperature and species concentrations and (b) time scale, λexp , of the explosive mode and the defectiveness index, dexp , of the chemical Jacobian. The dotted lines indicate the crossover point, where λexp =0.

code:

import pyjac as pj
import cantera as ct
import numpy as np

#create gas from original mechanism file gri30.cti
gas = ct.Solution('gri30.cti')
#reorder the gas to match pyJac
n2_ind = gas.species_index('N2')
specs = gas.species()[:]
specs2 = specs[:n2_ind] + specs[n2_ind + 1:] + [specs[n2_ind]]

names = []
print("index{}:{}".format(0,'T'))
names.append('T')
for i,s in enumerate(specs2[:-1]):
    print("index{}:{}".format(i+1,s.name))
    names.append(s.name)
gas = ct.Solution(thermo='IdealGas', kinetics='GasKinetics',
        species=specs2,
        reactions=gas.reactions())

# generate the c code to ./out folder
pj.create_jacobian(lang='c',gas=gas)

## generation
## !python -m pyjac.libgen --source_dir ./out --lang c
## !python -m pyjac.pywrap --source_dir ./out --lang c

%matplotlib inline
import matplotlib
matplotlib.rcParams['figure.figsize']=(12,10)
import matplotlib.pyplot as plt
#import matplotlib.animation as manimation
import pyjacob
from scipy.linalg import eig,eigvals,norm
from numpy import argmax,argmin,count_nonzero
import pyjac as pj
import cantera as ct
import numpy as np
import scipy as sp
from scipy.io import savemat

## problem setup
gas = ct.Solution('gri30.cti')
#reorder the gas to match pyJac
n2_ind = gas.species_index('N2')
specs = gas.species()[:]
specs2 = specs[:n2_ind] + specs[n2_ind + 1:] + [specs[n2_ind]]
gas = ct.Solution(thermo='IdealGas', kinetics='GasKinetics', species=specs2, reactions=gas.reactions())
Tin = 1100
P = ct.one_atm
gas.TPY = Tin, P, "H2:0.125, O2:1, N2:3.76"

# get the diabatic flame temperature Taf
oldstate = gas.TPY
gas.equilibrate('HP')
Taf = gas.T
gas.TPY=oldstate

r = ct.IdealGasConstPressureReactor(gas, name='R1')
sim = ct.ReactorNet([r])

## Henrici Measure
def HenriciMeasure(A):
    return norm(jac.dot(jac.T)-jac.T.dot(jac),ord=2)/(norm(jac,ord=2)**2)

ts=[]
Ts=[]
num_abs=[]
HM=np.array([])
ermax=np.array([],dtype=complex)
ermin=np.array([],dtype=complex)
eimax=np.array([],dtype=complex)
Jacs=[]
g=[]
## simulation
t=0.0
print('Reactor state: time[s], temp[K], press[Pa], specific internal energy[J/kg]')

#sim.advance(1e-4)
for t in np.arange(0e-6,120e-6,1e-6):
    sim.advance(t)
    print('Reactor state: %10.3e %10.3f %10.3f %14.6e' %(sim.time, r.T, r.thermo.P, r.thermo.u))
    #setup the state vector
    y = np.zeros(gas.n_species)
    y[0] = r.T
    y[1:] = gas.Y[:-1]

    #create a dydt vector
    dydt = np.zeros_like(y)
    pyjacob.py_dydt(0, P, y, dydt)

    #create a jacobian vector
    jac = np.zeros(gas.n_species * gas.n_species)

    #evaluate the Jacobian
    pyjacob.py_eval_jacobian(0, P, y, jac)

    # reshape, and transpose
    jac = jac.reshape((gas.n_species,gas.n_species)).T

    # rescaling using (Taf - Tin)
    jac[0,:] *= 1.0/(Taf -Tin)
    jac[:,0] *= (Taf -Tin)

    # delete argon
    #jac=np.delete(jac,(ar_ind),axis=0)
    #jac=np.delete(jac,(ar_ind),axis=1)

    # get eigen
    evals = eigvals(jac)
    print('max real, min real, max imag, HenriciMeasure')

    ts.append(t)
    Ts.append(r.T)
    HM = np.append(HM,HenriciMeasure(jac))
    ermax=np.append(ermax,evals[argmax(evals.real)])
    ermin=np.append(ermin,evals[argmin(evals.real)])
    eimax=np.append(eimax,evals[argmax(evals.imag)])
    num_abs.append(np.max(eigvals((jac+jac.T)/2)))
    Jacs.append(jac)
    g.append(gas.gibbs_mass)
    print(ermax[-1],
          ermin[-1],
          eimax[-1],
          HM[-1])

matplotlib.rcParams.update({'font.size': 18})
from matplotlib.ticker import FormatStrFormatter

fig, ax1 = plt.subplots()
ax1.plot(ts, ermax.real, 'b-')
ax1.set_xlabel('Residence time (s)')
# Make the y-axis label, ticks and tick labels match the line color.
ax1.set_ylabel('$\Re(\lambda)_{max} (Hz)$', color='b')
ax1.tick_params('y', colors='b')
ax1.set_xlim(60e-6,120e-6)
ax1.set_ylim(-0.15e6,0.45e6)

ax2 = ax1.twinx()
ax2.plot(ts, Ts, 'r-')
ax2.set_ylabel('Temperature (K)', color='r')
ax2.set_ylim(1000,2500)
ax2.tick_params('y', colors='r')

ax1.yaxis.set_major_formatter(FormatStrFormatter('%.2e'))
ax1.xaxis.set_major_formatter(FormatStrFormatter('%.2e'))

fig.tight_layout()
plt.show()

The ermax.real is:

array([  1.70195628e+05,   1.70195760e+05,   1.70195902e+05,
         1.70196060e+05,   1.70196237e+05,   1.70196438e+05,
         1.70196666e+05,   1.70196928e+05,   1.70197229e+05,
         1.70197576e+05,   1.70197978e+05,   1.70198446e+05,
         1.70198991e+05,   1.70199627e+05,   1.70200373e+05,
         1.70201248e+05,   1.70202275e+05,   1.70203484e+05,
         1.70204908e+05,   1.70206587e+05,   1.70208567e+05,
         1.70210906e+05,   1.70213669e+05,   1.70216935e+05,
         1.70220797e+05,   1.70225364e+05,   1.70230769e+05,
         1.70237165e+05,   1.70244735e+05,   1.70253696e+05,
         1.70264305e+05,   1.70276863e+05,   1.70291730e+05,
         1.70309329e+05,   1.70330159e+05,   1.70354811e+05,
         1.70383979e+05,   1.70418481e+05,   1.70459281e+05,
         1.70507510e+05,   1.70564491e+05,   1.70631776e+05,
         1.70711173e+05,   1.70804785e+05,   1.70915053e+05,
         1.71044791e+05,   1.71197232e+05,   1.71376064e+05,
         1.71585468e+05,   1.71830136e+05,   1.72115278e+05,
         1.72446607e+05,   1.72830276e+05,   1.73272782e+05,
         1.73780796e+05,   1.74360922e+05,   1.75019375e+05,
         1.75761547e+05,   1.76591497e+05,   1.77511338e+05,
         1.78520590e+05,   1.79615526e+05,   1.80788608e+05,
         1.82028130e+05,   1.83318208e+05,   1.84639285e+05,
         1.85969326e+05,   1.87285805e+05,   1.88568528e+05,
         1.89803131e+05,   1.90984846e+05,   1.92121909e+05,
         1.93237778e+05,   1.94371430e+05,   1.95575406e+05,
         1.96912033e+05,   1.98449081e+05,   2.00256536e+05,
         2.02405813e+05,   2.04971574e+05,   2.08035051e+05,
         2.11687334e+05,   2.16031484e+05,   2.21182917e+05,
         2.27267254e+05,   2.34413526e+05,   2.42737707e+05,
         2.52305819e+05,   2.63053515e+05,   2.74612343e+05,
         2.85935185e+05,   2.94494962e+05,   2.94629158e+05,
         2.74502121e+05,   2.12566946e+05,   8.19161773e+04,
         6.06157159e-10,   6.91056685e-09,   9.69866731e-09,
         4.71398954e-08,   2.75187769e-09,   1.66374166e-08,
         1.97155045e-10,   0.00000000e+00,   1.46331961e-08,
         4.91116710e-09,   7.64694115e-09,   6.73114322e-09,
         0.00000000e+00,   1.34487498e-09,   1.15210688e-08,
         2.45036358e-09,   1.11550176e-09,   1.05966695e-09,
         5.40201261e-09,   1.68147097e-10,   2.75339835e-09,
         1.52019141e-08,   3.33575305e-09,   8.80152505e-11,
         1.26077613e-09])
skyreflectedinmirrors commented 6 years ago

@chengdi123000

From the Paper (3.2. CEMA for the lifted flame):

. The least-negative eigenvalue aside from the zero eigenvalues associated with the conservation modes is displayed for non-explosive mixtures in figure 6

From this I infer they discarded zero-eigenvalues (resulting from conservation of mass / species / energy, as we discussed the other day).

Even after discarding eigenvalues identically equal to zero, I was not able to reproduce the plot you mention in the paper. However, by filtering eigenvalues by some small threshold (1e-5 in my case), I was able to achieve the expected behavior post-ignition, here's the updated example code:

%matplotlib inline
import matplotlib
matplotlib.rcParams['figure.figsize']=(12,10)
import matplotlib.pyplot as plt
#import matplotlib.animation as manimation
import pyjacob
from scipy.linalg import eig,eigvals,norm
from numpy import argmax,argmin,count_nonzero
import pyjac as pj
import cantera as ct
import numpy as np
import scipy as sp
from scipy.io import savemat

## problem setup
gas = ct.Solution('gri30.cti')
#reorder the gas to match pyJac
n2_ind = gas.species_index('N2')
specs = gas.species()[:]
specs2 = specs[:n2_ind] + specs[n2_ind + 1:] + [specs[n2_ind]]
gas = ct.Solution(thermo='IdealGas', kinetics='GasKinetics', species=specs2, reactions=gas.reactions())
Tin = 1100
P = ct.one_atm
gas.TPY = Tin, P, "H2:0.125, O2:1, N2:3.76"

# get the diabatic flame temperature Taf
oldstate = gas.TPY
gas.equilibrate('HP')
Taf = gas.T
gas.TPY=oldstate

r = ct.IdealGasConstPressureReactor(gas, name='R1')
sim = ct.ReactorNet([r])

## Henrici Measure
def HenriciMeasure(A):
    return norm(jac.dot(jac.T)-jac.T.dot(jac),ord=2)/(norm(jac,ord=2)**2)

ts=[]
Ts=[]
num_abs=[]
HM=np.array([])
ermax=np.array([],dtype=complex)
ermin=np.array([],dtype=complex)
eimax=np.array([],dtype=complex)
ermax_nonzero=np.array([],dtype=complex)
Jacs=[]
g=[]
## simulation
t=0.0
print('Reactor state: time[s], temp[K], press[Pa], specific internal energy[J/kg]')

#sim.advance(1e-4)
for t in np.arange(0e-6,120e-6,1e-6):
    sim.advance(t)
    print('Reactor state: %10.3e %10.3f %10.3f %14.6e' %(sim.time, r.T, r.thermo.P, r.thermo.u))
    #setup the state vector
    y = np.zeros(gas.n_species)
    y[0] = r.T
    y[1:] = gas.Y[:-1]

    #create a dydt vector
    dydt = np.zeros_like(y)
    pyjacob.py_dydt(0, P, y, dydt)

    #create a jacobian vector
    jac = np.zeros(gas.n_species * gas.n_species)

    #evaluate the Jacobian
    pyjacob.py_eval_jacobian(0, P, y, jac)

    # reshape, and transpose
    jac = jac.reshape((gas.n_species,gas.n_species)).T

    # rescaling using (Taf - Tin)
    jac[0,:] *= 1.0/(Taf -Tin)
    jac[:,0] *= (Taf -Tin)

    # delete argon
    #jac=np.delete(jac,(ar_ind),axis=0)
    #jac=np.delete(jac,(ar_ind),axis=1)

    # get eigen
    evals = eigvals(jac)
    print('max real, min real, max imag, max nonzero, HenriciMeasure')

    ts.append(t)
    Ts.append(r.T)
    HM = np.append(HM,HenriciMeasure(jac))
    ermax=np.append(ermax,evals[argmax(evals.real)])
    ermin=np.append(ermin,evals[argmin(evals.real)])
    eimax=np.append(eimax,evals[argmax(evals.imag)])
    # test nonzero
    epsilon = 1e-5
    nonzero = evals[np.where(np.abs(evals.real) > epsilon)]
    ermax_nonzero = np.append(ermax_nonzero, nonzero[argmax(nonzero.real)])
    num_abs.append(np.max(eigvals((jac+jac.T)/2)))
    Jacs.append(jac)
    g.append(gas.gibbs_mass)
    print(ermax[-1],
          ermin[-1],
          eimax[-1],
          ermax_nonzero[-1],
          HM[-1])

matplotlib.rcParams.update({'font.size': 18})
from matplotlib.ticker import FormatStrFormatter

fig, ax1 = plt.subplots()
ax1.plot(ts, ermax_nonzero.real, 'b-')
ax1.set_xlabel('Residence time (s)')
# Make the y-axis label, ticks and tick labels match the line color.
ax1.set_ylabel('$\Re(\lambda)_{max} (Hz)$', color='b')
ax1.tick_params('y', colors='b')
ax1.set_xlim(60e-6,120e-6)
ax1.set_ylim(-1e3,1e3)
ax1.plot([0, ts[-1]], [0, 0], 'k-')

ax2 = ax1.twinx()
ax2.plot(ts, Ts, 'r-')
ax2.set_ylabel('Temperature (K)', color='r')
ax2.set_ylim(1000,2500)
ax2.tick_params('y', colors='r')

ax1.yaxis.set_major_formatter(FormatStrFormatter('%.2e'))
ax1.xaxis.set_major_formatter(FormatStrFormatter('%.2e'))

fig.tight_layout()
plt.show()

This gives the following plot

Considering the scale of eigenvalues involved (maximum appears to be O(10^5)), this seems like a reasonable floating point precision threshold to me. You might try emailing the authors directly to see if they utilized something similar?

ghost commented 6 years ago

cema_h2o2_autoignition

Thank you @arghdos. I reproduced the almost same result.

I think CEMA might be mechanism dependent.

And

code:

wget https://combustion.llnl.gov/content/assets/docs/combustion/h2_v1a_therm.txt
wget https://combustion.llnl.gov/content/assets/docs/combustion/h2_v1b_mech.txt
wget https://combustion.llnl.gov/content/assets/docs/combustion/h2_v1a_tran.txt
rm libc_pyjac.so 
rm pyjacob.cpython-36m-x86_64-linux-gnu.so
rm -r ./out ./obj ./build
python -m pyjac -l c -i h2_v1b_mech.txt -t h2_v1a_therm.txt
python -m pyjac.libgen -l c -so ./out 
python -m pyjac.pywrap -l c -so ./out 

ck2cti \
--input=h2_v1b_mech.txt \
--thermo=h2_v1a_therm.txt \
--transport=h2_v1a_tran.txt \
--output=h2_llnl.cti
import pyjacob
from numpy.linalg import eig,eigvals
import pyjac as pj
import cantera as ct
import numpy as np
import scipy as sp

## problem setup
#reorder the gas to match pyJac
gas = ct.Solution("h2_llnl.cti")
n2_ind = gas.species_index('N2')
specs = gas.species()[:]
specs2 = specs[:n2_ind] + specs[n2_ind + 1:] + [specs[n2_ind]]
gas = ct.Solution(thermo='IdealGas', kinetics='GasKinetics', species=specs2, reactions=gas.reactions())
# pressure = 60 Torr, T = 770 K
P = ct.one_atm
Tin = 1100 #K

gas.TPX = Tin, P, 'h2:2, o2:1, n2:3.73, ar: 0.0446' #calculated from volume fraction of air

# get the diabatic flame temperature Taf
oldstate = gas.TPY
gas.equilibrate('HP')
Taf = gas.T
gas.TPY=oldstate

r = ct.IdealGasConstPressureReactor(gas, name='R1')
sim = ct.ReactorNet([r])

ts=[]
Ts=[]
Jacs=[]
cem1=[]
cem2=[]
## simulation
t=0.0
print('Reactor state: time[s], temp[K], press[Pa], specific internal energy[J/kg]')

sim.advance(60e-6)
for t in np.arange(60e-6,100e-6,0.1e-6):
    sim.advance(t)
    print('Reactor state: %10.3e %10.3f %10.3f %14.6e' %(sim.time, r.T, r.thermo.P, r.thermo.u))
    #setup the state vector
    y = np.zeros(gas.n_species)
    y[0] = r.T
    y[1:] = gas.Y[:-1]

    #create a dydt vector
    dydt = np.zeros_like(y)
    pyjacob.py_dydt(0, P, y, dydt)

    #create a jacobian vector
    jac = np.zeros(gas.n_species * gas.n_species)

    #evaluate the Jacobian
    pyjacob.py_eval_jacobian(0, P, y, jac)

    # reshape, and transpose
    jac = jac.reshape((gas.n_species,gas.n_species)).T

    #rescaling using (Taf - Tin)
    jac[0,:] *= 1.0/(Taf -Tin)
    jac[:,0] *= (Taf -Tin)

    # delete argon
    #jac=np.delete(jac,(ar_ind),axis=0)
    #jac=np.delete(jac,(ar_ind),axis=1)

    # get eigen
    evals = eigvals(jac)
    ts.append(t)
    Ts.append(r.T)
    Jacs.append(jac)
    ser = np.sort(evals.real)
    e1,e2,e3,e4 = ser[[-1,-2,-4,-5]]
    cem1.append(e1 if e1> 1e-7 else e3)
    cem2.append(e2 if e2> 1e-7 else e4)
    print(ser[-5:])

from scipy.interpolate import UnivariateSpline
from scipy.optimize import minimize_scalar
spl = UnivariateSpline(ts, Ts, k=4, s=0)
dspl = spl.derivative()
tdf = minimize_scalar(lambda t:-dspl(t)).x

import matplotlib
import matplotlib.pyplot as plt
from matplotlib.ticker import FormatStrFormatter
matplotlib.rcParams['figure.figsize']=(12,10)
matplotlib.rcParams.update({'font.size': 22})
fig, ax1 = plt.subplots()
ax1.plot(ts,cem1,'-b',label='$\lambda_{exp}$')
ax1.plot(ts,cem2,'-.b',label="$\lambda_{exp2}")
ax1.set_xlabel('residence time ($ s$)')
# Make the y-axis label, ticks and tick labels match the line color.
ax1.set_ylabel('$\lambda_{exp}$(Hz)', color='b')
ax1.set_ybound(-0.25e6,0.45e6)
ax1.tick_params('y', colors='b')
ax1.yaxis.set_major_formatter(FormatStrFormatter('%.2e'))
ax1.axhline(0,linestyle='dotted')

ax2 = ax1.twinx()
ax2.plot(ts,Ts,'-.r',label='Temperature')
ax2.set_ylabel('Temperature,[K]', color='r')
ax2.tick_params('y', colors='r')
fig.tight_layout()
ax2.axvline(tdf,linestyle='dotted')
plt.show()