Project

Reduction of Reaction Mechanism for Methane

Objective:

The aim of this project is to reduce the reaction mechanism size from that of the GRI 3.0 Mechanism for methane (`CH_4`) fuel with various state values to compute the maximum temperature and the ignition delay for the given tolerance criteria(1%) based on the temperature sensitivity.

Procedure:

The code is done along the following stages as shown below:

  • An input.txt file is created to provide the required input data.
  • Input data is read and temperature sensitivity is performed for each of the combinations of state values.
  • For each of the combinations of state values, GRI 3.0 Mechanism is sorted from high sensitive to least sensitive reactions. For all the combinations a .csv file is created to store the sorted reaction numbers.
  • Then for each combination of state values using the sorted reaction numbers, a new gas instance is created and reactions are passed in the descending order of sensitive reactions from 40 most sensitive to all the 325 reactions to determine the maximum temperature and ignition delay with the addition of each reaction to the mechanism. Finally, to obtain the reduced mechanism size within the given tolerance criteria.
  • The same is plotted along with the reference values of the GRI 3.0 mechanism.

Solution:

Input File:

Fuel			CH4
Air				O2:1,N2:3.76
Temperature		950 1050 1150
Pressure		3 4 5
Phi				0.5 1 1.5
End_time		5
Delta_t			0.001
Min_size		40
Tol_igd			0.01
Tol_Tmax		0.01

The input file is used to parse input values for the main code. All the lines are self-explanatory. End_time is the simulation time, Delta_t is the time step for simulation, Min_size is minimum mechanism size, Tol_igd and Tol_Tmax are the tolerance criteria for determining the ignition delay and maximum temperature, respectively.

Python Script to find sensitive reactions order for each combination of State values:

import os
import cantera as ct 
import matplotlib.pyplot as plt 
import numpy as np 

# Extracting input data
class FileReader:
	def __init__(self, filename):
		self.filename = filename
		self.input_dict = {}

	def validate_file(self):
		return os.path.isfile(self.filename)

	def read(self):
		if self.validate_file():
			for line in open(self.filename,'r'):
				self.input_dict[line.split()[0]] = line.split()[1:]
		else:
			print('File name does not exist')

a = FileReader('input.txt')
a.read()

T_val         = [float(x) for x in a.input_dict['Temperature']]
P_val         = [float(x) for x in a.input_dict['Pressure']]
Phi_val       = [float(x) for x in a.input_dict['Phi']]
t_end         = [float(x) for x in a.input_dict['End_time']]
dt            = [float(x) for x in a.input_dict['Delta_t']]
min_reactions = [float(x) for x in a.input_dict['Min_size']]
tol_igd       = [float(x) for x in a.input_dict['Tol_igd']]
tol_Tmax      = [float(x) for x in a.input_dict['Tol_Tmax']]
fuel          = [x for x in a.input_dict['Fuel']]


t_size=np.size(T_val)*np.size(P_val)*np.size(Phi_val)
#--------------------------------------------------------------------------------------#

gas = ct.Solution('gri30.cti')
air ='O2:1, N2:3.76'


             #listing all the equations for gas instance of Solution Class of gri30.xml
n_reactions = gas.n_reactions                     # recording total no. of reactions involved (i.e., 325)

sensitivity_store = np.zeros((n_reactions,t_size))
reactionNo_store = np.zeros((n_reactions,t_size))
cntr = 0;

for T in T_val:
	for P in P_val:
		for Phi in Phi_val:

			sensitivity = [0]*n_reactions  
			reactions = gas.reaction_equations() 

			gas.TP = T, P*1e5
			gas.set_equivalence_ratio(Phi, fuel[0], air)

			r = ct.IdealGasReactor(gas)
			sim = ct.ReactorNet([r])

			for i in range (0,gas.n_reactions):
				r.add_sensitivity_reaction(i)

			sim.rtol = 1e-6
			sim.atol = 1e-15
			sim.rtol_sensitivity = 1e-6
			sim.atol_sensitivity = 1e-6

			for t in np.arange(0,t_end[0]+dt[0],dt[0]):
				sim.advance(t)

				for i in range (gas.n_reactions):
					S = sim.sensitivity('temperature', i)

					if np.abs(S)>np.abs(sensitivity[i]): 
						sensitivity[i] = S

			reaction_number = np.arange(1,n_reactions+1)  # creating an array for reaction numbers
			abs_sensitivity = np.abs(sensitivity)         # calculating the absolute value of sensitivity 

			reactions = [a for b, a in sorted (zip(abs_sensitivity,reactions), reverse = True)]
			reaction_number = [a for b, a in sorted (zip(abs_sensitivity,reaction_number), reverse = True)]
			sensitivity = [a for b, a in sorted (zip(abs_sensitivity,sensitivity), reverse = True)]

			sensitivity_store[:,cntr]=sensitivity
			reactionNo_store[:,cntr]=reaction_number
			cntr = cntr+1;


np.savetxt('f_array.csv',sensitivity_store, delimiter=',', fmt = '%.15f')
np.savetxt('f_array1.csv',reactionNo_store, delimiter=',', fmt = '%d')

At first, the input file is extracted and sensitivity analysis is performed for each of the combinations of state values. Then, the reactions are sorted from highest to least sensitive reactions w.r.t to temperature. All the sorted reaction numbers for each combination are stored in the f_array1.csv file. Detail explanation to determine the sensitive reactions can be obtained from the following link: <https://skill-lync.com/projects/Sensitivity-Analysis-for-Combustion-of-Methane-13366>

Python Script to determine the reduced mechanism size:

# function to determine the reduced mechanism size
def reduction_mechanism(T,P,Phi,fuel,air,dt,t_end,min_size,Tol_igd, Tol_Tmax,case,reaction_numbers_sorted):
	# inputs(temperature, pressure, fuel, air, time step, final time, minimum size, tolerance criteria ignition delay, tolerance for temperature, case No., reaction numbers)
	import os
	import cantera as ct 
	import matplotlib.pyplot as plt 
	import numpy as np 

	reactions_sorted = []
	ign_delay_arr = []
	T_max_arr = []
	mech_size = []

	T_ign = T+400

	gas = ct.Solution('gri30.cti')
	gas.TP = T,P*1e5   # Pressure in bar
	gas.set_equivalence_ratio(Phi, fuel, air)
	spec = ct.Species.listFromFile('gri30.cti')

	r=ct.IdealGasReactor(gas)
	sim = ct.ReactorNet([r])
	T_max_ref =0
	check = 0
	# To determine the maximum temperature and ignition delay for GRI 3.0 mechanism
	for t in np.arange(0,t_end+dt,dt):
		sim.advance(t)

		if (gas.T > T_ign and check == 0):
			ign_delay_ref = t
			check+=1
		if (gas.T > T_max_ref):
			T_max_ref = gas.T

	# To find the reduced mechanism size 
	const=True
	for i in range (gas.n_reactions):
		reactions_sorted.append(gas.reaction(int(reaction_numbers_sorted[i])-1)) # to pass reactions one by one
		gas1 = ct.Solution(thermo = 'IdealGas', kinetics = 'GasKinetics', species = spec, reactions = reactions_sorted) # creating gas instance
		gas1.TP = T,P*1e5
		gas1.set_equivalence_ratio(Phi, fuel, air)

		r1=ct.IdealGasReactor(gas1)
		sim1 = ct.ReactorNet([r1])
		Flag = 0

		ign_delay=0
		T_max=0
		for t in np.arange(0,t_end+dt,dt):
			sim1.advance(t)

			if (gas1.T > T_ign and Flag == 0):
				ign_delay = t
				Flag+=1

			if (gas1.T > T_max):
				T_max = gas1.T
		try:                                 # appending the values of ignition delay, mechanism size, maximum temperature

			if(i>=min_size):
				ign_delay_arr.append(ign_delay)
				T_max_arr.append(T_max)
				mech_size.append(i)
		except NameError:					# passing the NameError
			pass

		# computing accuracy
		err_T_max = np.abs(((T_max_ref - T_max)/T_max_ref)*100) # % error in maximum temperature
		err_ign_delay = np.abs(((ign_delay_ref - ign_delay)/ign_delay_ref)*100)	# % error in ignition delay		

		if (err_T_max < Tol_Tmax and err_ign_delay < Tol_igd and const is True):
			red_mech_size=i+1
			T_record=T_max
			ign_record=ign_delay	
			const = False

	T_max_ref=[T_max_ref]*len(T_max_arr)               	#increasing the size to match the lengths to ease in plot
	ign_delay_ref=[ign_delay_ref]*len(ign_delay_arr)
	#Plotting Maximum Temperature	
	plt.subplot(2,1,1)
	plt.plot(mech_size,T_max_arr, color = 'red', label = 'Calculated Max Temperature')
	plt.plot(mech_size,T_max_ref, color = 'blue', linestyle = 'dashed', label = 'Reference Max Temperature= '+str(format(max(T_max_ref),'0.4f'))+' [K]')
	plt.xlabel('No. of sorted reactions most sensitive to Temperature')
	plt.ylabel('Maximum Temperature [K]')
	plt.legend(loc = 'best')

	titlename='Case : '+str(case)+'\n State Variables T : '+str(T)+' [K], P : '+str(P)+' [bar], $\phi$ : '+str(Phi)
	plt.title(titlename)
	#Plotting ignition delay	
	plt.subplot(2,1,2)
	plt.plot(mech_size,ign_delay_arr, color = 'green', label = 'Calculated Ignition Delay')
	plt.plot(mech_size,ign_delay_ref, color = 'black', linestyle = 'dashed', label = 'Reference Ignition Delay = '+str(format(max(ign_delay_ref),'0.4f'))+' sec')
	plt.xlabel('No. of sorted reactions most sensitive to Temperature')
	plt.ylabel('Ignition Delay [sec]')	
	plt.legend(loc = 'best')

	plt.tight_layout()

	manager = plt.get_current_fig_manager()   	# to display images in full window
	manager.resize(*manager.window.maxsize())

	figname='Case_No_'+str(case)+'.png'         # image name to saved as
			
	plt.savefig(figname)						# saving image

	plt.close()

	list1 = [case,T,P,Phi,T_max_ref[0],ign_delay_ref[0],T_record,ign_record,red_mech_size]  # returning the values for comparison and write in separate CSV

	return list1

Here for each combination of state values using the sorted reaction numbers, a new gas instance is created and reactions are passed in the descending order of sensitive reactions from 40 most sensitive to all the 325 reactions to determine the maximum temperature and ignition delay with the addition of each reaction to the mechanism. Finally, to obtain the reduced mechanism size within the given tolerance criteria.

To ease the formation of code, this is written as a function wherein state values, tolerance criteria, and all the necessary conditions are passed which returns the list of required values viz., maximum temperature, ignition delays, and images are stored in the folder for that particular case. Detail explanation to determine the auto-ignition delay and maximum temperatures can be obtained from <https://skill-lync.com/projects/Autoignition-delay-calculation-using-Cantera-60447>

Name Error:

As we are using the minimum size of 40 reactions, at times there are chances that Cantera may come across errors due to an insufficient number of reactions to obtain the solution. So we pass this error by using the exception of NameError.

Strictly speaking in the above code we are passing reactions from 1 to 325 of the sorted reactions, but we are recording only values for more than 40 reactions. In order to avoid the error from 1 to 40 reactions due to an insufficient number of reactions in mechanism size, we use exception and put the criteria of tolerance to obtain the solution within the tolerance by passing the rest of the reactions to determine the reduced size of the mechanism for the given tolerance criteria.

Python Script to call the function of reduction mechanism and writing the results in CSV file:

import csv
# reading the f_array1.csv file where all the sorted reaction numbers are stored.
def readcsv(filename):	
    ifile = open(filename, "rU")
    reader = csv.reader(ifile, delimiter=",")

    rownum = 0	
    a = []

    for row in reader:
        a.append (row)
        rownum += 1
    
    ifile.close()
    return a

reaction_numbers_sorted1 = readcsv('f_array1.csv')

# Extracting input data
import os
class FileReader:
	def __init__(self, filename):
		self.filename = filename
		self.input_dict = {}

	def validate_file(self):
		return os.path.isfile(self.filename)

	def read(self):
		if self.validate_file():
			for line in open(self.filename,'r'):
				self.input_dict[line.split()[0]] = line.split()[1:]
		else:
			print('File name does not exist')

a = FileReader('input.txt')
a.read()

T_val         = [float(x) for x in a.input_dict['Temperature']]
P_val         = [float(x) for x in a.input_dict['Pressure']]
Phi_val       = [float(x) for x in a.input_dict['Phi']]
t_end         = [float(x) for x in a.input_dict['End_time']]
dt            = [float(x) for x in a.input_dict['Delta_t']]
min_size      = [float(x) for x in a.input_dict['Min_size']]
Tol_igd       = [float(x) for x in a.input_dict['Tol_igd']]
Tol_Tmax      = [float(x) for x in a.input_dict['Tol_Tmax']]
fuel          = [x for x in a.input_dict['Fuel']]
air           = [x for x in a.input_dict['Air']]


case=0
# writing the csv file
with open('data.csv','w',newline='') as file:
	writer = csv.writer(file)
	writer.writerow(["Case No.","Temperature [K]", "Pressure [bar]", "Equivalence Ratio", "Maximum Temperature [K]", "Ignition Delay [s]",  "Maximum Temperature [K]", "Ignition Delay [s]","Mechanism Size"])

for T in T_val:
	for P in P_val:
		for Phi in Phi_val:
			
			reaction_numbers_sorted = [ x[case] for x in reaction_numbers_sorted1]
		
			

			list2=reduction_mechanism(T,P,Phi,fuel[0],air[0],dt[0],t_end[0],min_size[0],Tol_igd[0],Tol_Tmax[0],case+1,reaction_numbers_sorted)
			
			with open('data.csv','a',newline='') as fd:    # appending the data of each combination
				wr = csv.writer(fd,dialect='excel')
				wr.writerow(list2)

			case+=1

Here, the reduction mechanism function is called by passing each combination of state values and corresponding sorted reactions. For, each call of a function, an image is stored and the list is returned which is written in a data.csv file which is used to compare the results between different state values.

Results:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

Comparison:

Case No. State Values Reference Values with GRI 3.0 Mechanism Computed with the tolerance of 1%
Temperature [k] Pressure [bar] Equivalence Ratio Maximum Temperature [K] Ignition Delay [s] Maximum Temperature [K] Ignition Delay [s] Mechanism Size
1 950 3 0.5 2283.660196 0.6 2283.635756 0.6 163
2 950 3 1 2827.509284 0.77 2827.780958 0.77 208
3 950 3 1.5 2724.848449 0.94 2724.870796 0.94 177
4 950 4 0.5 2286.378274 0.446 2286.37934 0.446 162
5 950 4 1 2854.329177 0.555 2854.111926 0.555 163
6 950 4 1.5 2733.608694 0.669 2733.76574 0.669 167
7 950 5 0.5 2288.300249 0.356 2288.301762 0.356 161
8 950 5 1 2859.971627 0.432 2860.070123 0.432 191
9 950 5 1.5 2739.985986 0.515 2740.223174 0.515 166
10 1050 3 0.5 2356.736154 0.103 2356.738104 0.103 160
11 1050 3 1 2862.492617 0.142 2862.462873 0.142 162
12 1050 3 1.5 2783.643146 0.176 2783.813168 0.176 160
13 1050 4 0.5 2359.697707 0.076 2359.703016 0.076 164
14 1050 4 1 2878.940846 0.103 2878.913899 0.103 141
15 1050 4 1.5 2792.65345 0.127 2792.37427 0.127 130
16 1050 5 0.5 2362.087139 0.06 2362.091354 0.06 167
17 1050 5 1 2913.388054 0.079 2913.222922 0.079 171
18 1050 5 1.5 2800.223274 0.098 2800.095108 0.098 133
19 1150 3 0.5 2428.116286 0.023 2428.272013 0.023 50
20 1150 3 1 2906.89638 0.032 2906.746901 0.032 166
21 1150 3 1.5 2836.809114 0.04 2837.036113 0.04 162
22 1150 4 0.5 2430.218819 0.018 2430.42293 0.018 147
23 1150 4 1 2913.105093 0.024 2913.000581 0.024 167
24 1150 4 1.5 2849.483198 0.03 2849.677225 0.03 156
25 1150 5 0.5 2435.003904 0.014 2434.918546 0.014 69
26 1150 5 1 2928.281419 0.019 2928.294251 0.019 161
27 1150 5 1.5 2864.091912 0.023 2864.364627 0.023 197

From the above figures, it is clear that for each of the state value combinations, there is higher inaccuracy with less number of reactions, and the accuracy increases upon adding the reactions to the mechanism size. From our analysis, it can be found that for our state values with an approximate size of 156 reactions sorted in the order of highest sensitivity to least w.r.t temperature, we can obtain the solution with 1% accuracy in determining the ignition delay and maximum temperature.

Future Suggestions:

For cases 19 & 25, it can be observed that the reduced mechanism size is 50 & 69, respectively. These are the odd one outs in comparison to other mechanism sizes. It is due to the fact that upon adding exact those numbers of reactions for that particular combination of state values, we get the result within tolerance which is evident from the figures of cases 19 & 25. Upon adding further reactions the error goes beyond 1%. In order to avoid such kind of errors, we need to put a check such that upon adding further reactions to the mechanism size, the error for at least next 25 to 30 additional reactions lies within the tolerance criteria. Then, that particular reduced size can be taken into consideration to avoid the above discrepancy.

NB: The codes and data files can be found from the link below:

https://drive.google.com/drive/folders/1ytii4CvNHnhYQVy-O0jwGSuEySW2YrGf?usp=sharing

Your portfolio, next

Ship work like this
in your own course.

Talk to a career counsellor

Free 20-minute call. We'll map the right course to where you want to work.

By submitting you agree to our Terms and Privacy Policy.

Talk to a career counsellor

Free 20-minute call. We'll map the right course to where you want to work.

By submitting you agree to our Terms and Privacy Policy.