LMRRfactory 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lmrrfactory-0.0.1/LICENSE +21 -0
- lmrrfactory-0.0.1/LMRRfactory/__init__.py +3 -0
- lmrrfactory-0.0.1/LMRRfactory/ext/__init__.py +3 -0
- lmrrfactory-0.0.1/LMRRfactory/masterFitter.py +472 -0
- lmrrfactory-0.0.1/LMRRfactory.egg-info/PKG-INFO +20 -0
- lmrrfactory-0.0.1/LMRRfactory.egg-info/SOURCES.txt +11 -0
- lmrrfactory-0.0.1/LMRRfactory.egg-info/dependency_links.txt +1 -0
- lmrrfactory-0.0.1/LMRRfactory.egg-info/requires.txt +4 -0
- lmrrfactory-0.0.1/LMRRfactory.egg-info/top_level.txt +1 -0
- lmrrfactory-0.0.1/PKG-INFO +20 -0
- lmrrfactory-0.0.1/README.md +2 -0
- lmrrfactory-0.0.1/setup.cfg +4 -0
- lmrrfactory-0.0.1/setup.py +28 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 TheBurkeLab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Class that allows for fitting of rate constants at various temperatures and pressures (k(T,P))
|
|
3
|
+
"""
|
|
4
|
+
from LMRRfactory.ext import cantera as ct
|
|
5
|
+
import numpy as np
|
|
6
|
+
from scipy.optimize import curve_fit
|
|
7
|
+
from scipy.optimize import least_squares
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
class masterFitter:
|
|
11
|
+
def __init__(self, T_ls, P_ls, inputFile,n_P=7, n_T=7, M_only=False):
|
|
12
|
+
self.T_ls = T_ls
|
|
13
|
+
self.P_ls = P_ls
|
|
14
|
+
self.n_P=n_P
|
|
15
|
+
self.n_T=n_T
|
|
16
|
+
self.P_min = P_ls[0]
|
|
17
|
+
self.P_max = P_ls[-1]
|
|
18
|
+
self.T_min = T_ls[0]
|
|
19
|
+
self.T_max = T_ls[-1]
|
|
20
|
+
self.M_only=M_only
|
|
21
|
+
|
|
22
|
+
# self.input = self.openyaml(inputFile)
|
|
23
|
+
with open(inputFile) as f:
|
|
24
|
+
self.input = yaml.safe_load(f)
|
|
25
|
+
with open("data/thirdbodydefaults.yaml") as f:
|
|
26
|
+
self.defaults = yaml.safe_load(f)
|
|
27
|
+
self.mech = self.yaml_custom_load(self.input['chemical-mechanism'])
|
|
28
|
+
|
|
29
|
+
self.pDepReactionNames=[]
|
|
30
|
+
self.pDepReactions = []
|
|
31
|
+
for reaction in self.mech['reactions']:
|
|
32
|
+
if reaction.get('type') == 'falloff' and 'Troe' in reaction:
|
|
33
|
+
self.pDepReactions.append(reaction)
|
|
34
|
+
self.pDepReactionNames.append(reaction['equation'])
|
|
35
|
+
elif reaction.get('type') == 'pressure-dependent-Arrhenius':
|
|
36
|
+
self.pDepReactions.append(reaction)
|
|
37
|
+
self.pDepReactionNames.append(reaction['equation'])
|
|
38
|
+
elif reaction.get('type') == 'Chebyshev':
|
|
39
|
+
self.pDepReactions.append(reaction)
|
|
40
|
+
self.pDepReactionNames.append(reaction['equation'])
|
|
41
|
+
|
|
42
|
+
if len(self.pDepReactions)==0:
|
|
43
|
+
print("No pressure-dependent reactions found in mechanism. Please choose another mechanism.")
|
|
44
|
+
else:
|
|
45
|
+
self.shortMech = self.zippedMech()
|
|
46
|
+
|
|
47
|
+
def yaml_custom_load(self,fname):
|
|
48
|
+
with open(fname) as f:
|
|
49
|
+
data = yaml.safe_load(f)
|
|
50
|
+
newMolecList = []
|
|
51
|
+
for molec in data['phases'][0]['species']:
|
|
52
|
+
if str(molec).lower()=="false":
|
|
53
|
+
newMolecList.append("NO")
|
|
54
|
+
else:
|
|
55
|
+
newMolecList.append(molec)
|
|
56
|
+
data['phases'][0]['species'] = newMolecList
|
|
57
|
+
for species in data['species']:
|
|
58
|
+
name = str(species['name']).lower()
|
|
59
|
+
if name == "false":
|
|
60
|
+
species['name']="NO"
|
|
61
|
+
for reaction in data['reactions']:
|
|
62
|
+
effs = reaction.get('efficiencies')
|
|
63
|
+
if effs is not None:
|
|
64
|
+
for key in list(effs.keys()):
|
|
65
|
+
keyStr = str(key).lower()
|
|
66
|
+
if keyStr == "false":
|
|
67
|
+
reaction['efficiencies']["NO"] = reaction['efficiencies'].pop(key)
|
|
68
|
+
return data
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def zippedMech(self):
|
|
72
|
+
blend=self.blendedInput()
|
|
73
|
+
shortMechanism={
|
|
74
|
+
'units': self.mech['units'],
|
|
75
|
+
'phases': self.mech['phases'],
|
|
76
|
+
'species': self.mech['species'],
|
|
77
|
+
'reactions': []
|
|
78
|
+
}
|
|
79
|
+
blendRxnNames = []
|
|
80
|
+
for rxn in blend['reactions']:
|
|
81
|
+
blendRxnNames.append(rxn['equation'])
|
|
82
|
+
for mech_rxn in self.mech['reactions']:
|
|
83
|
+
if mech_rxn['equation'] in blendRxnNames:
|
|
84
|
+
idx = blendRxnNames.index(mech_rxn['equation'])
|
|
85
|
+
colliderM = blend['reactions'][idx]['collider-list'][0]
|
|
86
|
+
colliderMlist=[]
|
|
87
|
+
if mech_rxn['type'] == 'falloff' and 'Troe' in mech_rxn:
|
|
88
|
+
colliderMlist.append({
|
|
89
|
+
'collider': 'M',
|
|
90
|
+
'eps': {'A': 1, 'b': 0, 'Ea': 0},
|
|
91
|
+
'low-P-rate-constant': mech_rxn['low-P-rate-constant'],
|
|
92
|
+
'high-P-rate-constant': mech_rxn['high-P-rate-constant'],
|
|
93
|
+
'Troe': mech_rxn['Troe'],
|
|
94
|
+
})
|
|
95
|
+
elif mech_rxn['type'] == 'pressure-dependent-Arrhenius':
|
|
96
|
+
colliderMlist.append({
|
|
97
|
+
'collider': 'M',
|
|
98
|
+
'eps': {'A': 1, 'b': 0, 'Ea': 0},
|
|
99
|
+
'rate-constants': mech_rxn['rate-constants'],
|
|
100
|
+
})
|
|
101
|
+
elif mech_rxn['type'] == 'Chebyshev':
|
|
102
|
+
colliderMlist.append({
|
|
103
|
+
'collider': 'M',
|
|
104
|
+
'eps': {'A': 1, 'b': 0, 'Ea': 0},
|
|
105
|
+
'temperature-range': mech_rxn['temperature-range'],
|
|
106
|
+
'pressure-range': mech_rxn['pressure-range'],
|
|
107
|
+
'data': mech_rxn['data'],
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
# colliderList.append(blend['reactions'][idx]['collider-list'])
|
|
111
|
+
shortMechanism['reactions'].append({
|
|
112
|
+
'equation': mech_rxn['equation'],
|
|
113
|
+
'type': 'linear-burke',
|
|
114
|
+
'reference-collider': blend['reactions'][idx]['reference-collider'],
|
|
115
|
+
'collider-list': colliderMlist + blend['reactions'][idx]['collider-list']
|
|
116
|
+
})
|
|
117
|
+
else:
|
|
118
|
+
shortMechanism['reactions'].append(mech_rxn)
|
|
119
|
+
return shortMechanism
|
|
120
|
+
|
|
121
|
+
def blendedInput(self):
|
|
122
|
+
defaults2=self.deleteDuplicates()
|
|
123
|
+
blend = {'reactions': []}
|
|
124
|
+
speciesList = self.mech['phases'][0]['species']
|
|
125
|
+
defaultRxnNames = []
|
|
126
|
+
defaultColliderNames = []
|
|
127
|
+
for defaultRxn in defaults2['reactions']:
|
|
128
|
+
defaultRxnNames.append(defaultRxn['equation'])
|
|
129
|
+
for defaultCol in defaultRxn['collider-list']:
|
|
130
|
+
defaultColliderNames.append(defaultCol['collider'])
|
|
131
|
+
# first fill it with all of the default reactions and colliders (which have valid species)
|
|
132
|
+
for defaultRxn in defaults2['reactions']:
|
|
133
|
+
flag = True
|
|
134
|
+
for defaultCol in defaultRxn['collider-list']:
|
|
135
|
+
if defaultCol['collider'] not in speciesList:
|
|
136
|
+
flag = False
|
|
137
|
+
if flag == True:
|
|
138
|
+
blend['reactions'].append(defaultRxn)
|
|
139
|
+
|
|
140
|
+
blendRxnNames = []
|
|
141
|
+
for blendRxn in blend['reactions']:
|
|
142
|
+
blendRxnNames.append(blendRxn['equation'])
|
|
143
|
+
|
|
144
|
+
for inputRxn in self.input['reactions']:
|
|
145
|
+
if inputRxn['equation'] in blendRxnNames: #input reaction also exists in defaults file
|
|
146
|
+
idx = blendRxnNames.index(inputRxn['equation'])
|
|
147
|
+
# print(inputRxn['reference-collider'])
|
|
148
|
+
if inputRxn['reference-collider'] == blend['reactions'][idx]['reference-collider']: #no blending conflicts bc colliders have same ref
|
|
149
|
+
for inputCol in inputRxn['collider-list']:
|
|
150
|
+
if inputCol['collider'] in speciesList:
|
|
151
|
+
blend['reactions'][idx]['collider-list'].append(inputCol)
|
|
152
|
+
else: #blending conflict -> delete all default colliders and override with the user inputs
|
|
153
|
+
print(f"The user-provided reference collider for {inputRxn['equation']}, ({inputRxn['reference-collider']}) does not match the program default ({blend['reactions'][idx]['reference-collider']}).")
|
|
154
|
+
print(f"The default colliders have thus been deleted and the reaction has been completely overrided by (rather than blended with) the user's custom input values.")
|
|
155
|
+
blend['reactions'][idx]['collider-list'] = inputRxn['collider-list']
|
|
156
|
+
else:
|
|
157
|
+
flag = True
|
|
158
|
+
for inputCol in inputRxn['collider-list']:
|
|
159
|
+
if inputCol['collider'] not in speciesList:
|
|
160
|
+
flag = False
|
|
161
|
+
if flag == True:
|
|
162
|
+
blend['reactions'].append(inputRxn)
|
|
163
|
+
for reaction in blend['reactions']:
|
|
164
|
+
for col in reaction['collider-list']:
|
|
165
|
+
temperatures=np.array(col['temperatures'])
|
|
166
|
+
eps = np.array(col['eps'])
|
|
167
|
+
# epsLow=effs['epsLow']['A']
|
|
168
|
+
# epsHigh=effs['epsHigh']['A']
|
|
169
|
+
# rate_constants=np.array([epsLow,epsHigh])
|
|
170
|
+
def arrhenius_rate(T, A, beta, Ea):
|
|
171
|
+
# R = 8.314 # Gas constant in J/(mol K)
|
|
172
|
+
R = 1.987 # cal/molK
|
|
173
|
+
return A * T**beta * np.exp(-Ea / (R * T))
|
|
174
|
+
def fit_function(params, T, ln_rate_constants):
|
|
175
|
+
A, beta, Ea = params
|
|
176
|
+
return np.log(arrhenius_rate(T, A, beta, Ea)) - ln_rate_constants
|
|
177
|
+
initial_guess = [3, 0.5, 50.0]
|
|
178
|
+
result = least_squares(fit_function, initial_guess, args=(temperatures, np.log(eps)))
|
|
179
|
+
A_fit, beta_fit, Ea_fit = result.x
|
|
180
|
+
col['eps'] = {'A': round(float(A_fit),5),'b': round(float(beta_fit),5),'Ea': round(float(Ea_fit),5)}
|
|
181
|
+
del col['temperatures']
|
|
182
|
+
return blend
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def deleteDuplicates(self):
|
|
187
|
+
# defaults2 = {'reactions': []}
|
|
188
|
+
# defaultRxnNames=[]
|
|
189
|
+
# for defaultRxn in self.defaults['reactions']:
|
|
190
|
+
# defaultRxnNames.append(defaultRxn['equation'])
|
|
191
|
+
# for inputRxn in self.input['reactions']:
|
|
192
|
+
# if inputRxn['equation'] in defaultRxnNames:
|
|
193
|
+
# defaultRxnNames.remove(inputRxn['equation'])
|
|
194
|
+
# newReactionList = []
|
|
195
|
+
# for defaultRxn in self.defaults['reactions']:
|
|
196
|
+
# if defaultRxn['equation'] in defaultRxnNames:
|
|
197
|
+
# newReactionList.append(defaultRxn)
|
|
198
|
+
defaults2 = {'reactions': []}
|
|
199
|
+
inputRxnNames=[]
|
|
200
|
+
inputColliderNames=[]
|
|
201
|
+
for inputRxn in self.input['reactions']:
|
|
202
|
+
inputRxnNames.append(inputRxn['equation'])
|
|
203
|
+
inputRxnColliderNames=[]
|
|
204
|
+
for inputCol in inputRxn['collider-list']:
|
|
205
|
+
inputRxnColliderNames.append(inputCol['collider'])
|
|
206
|
+
inputColliderNames.append(inputRxnColliderNames)
|
|
207
|
+
for defaultRxn in self.defaults['reactions']:
|
|
208
|
+
if defaultRxn['equation'] in inputRxnNames:
|
|
209
|
+
idx = inputRxnNames.index(defaultRxn['equation'])
|
|
210
|
+
defaultColliderNames=[]
|
|
211
|
+
for defaultCol in defaultRxn['collider-list']:
|
|
212
|
+
defaultColliderNames.append(defaultCol['collider'])
|
|
213
|
+
# print(defaultRxn['equation'])
|
|
214
|
+
for defaultCol in defaultRxn['collider-list']:
|
|
215
|
+
if defaultCol['collider'] in inputColliderNames[idx]:
|
|
216
|
+
defaultColliderNames.remove(defaultCol['collider'])
|
|
217
|
+
# print(inputColliderNames[idx])
|
|
218
|
+
# print(defaultColliderNames)
|
|
219
|
+
newColliderList=[] #only contains colliders that aren't already in the input
|
|
220
|
+
for defaultCol in defaultRxn['collider-list']:
|
|
221
|
+
if defaultCol['collider'] in defaultColliderNames:
|
|
222
|
+
newColliderList.append(defaultCol)
|
|
223
|
+
if len(newColliderList)>0:
|
|
224
|
+
defaults2['reactions'].append({
|
|
225
|
+
'equation': defaultRxn['equation'],
|
|
226
|
+
'reference-collider': defaultRxn['reference-collider'],
|
|
227
|
+
'collider-list': newColliderList
|
|
228
|
+
})
|
|
229
|
+
else: # reaction isn't in input, so keep the entire default rxn
|
|
230
|
+
defaults2['reactions'].append(defaultRxn)
|
|
231
|
+
return defaults2
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def get_Xvec(self,reaction):
|
|
236
|
+
Prange = self.P_ls
|
|
237
|
+
Xvec=[]
|
|
238
|
+
for i,P in enumerate(Prange):
|
|
239
|
+
for j,T in enumerate(self.T_ls):
|
|
240
|
+
Xvec.append([P,T])
|
|
241
|
+
Xvec=np.array(Xvec)
|
|
242
|
+
return Xvec.T
|
|
243
|
+
|
|
244
|
+
def get_Xdict(self,reaction):
|
|
245
|
+
Prange = self.P_ls
|
|
246
|
+
Xdict={}
|
|
247
|
+
for i,P in enumerate(Prange):
|
|
248
|
+
Xdict[P]=self.T_ls
|
|
249
|
+
return Xdict
|
|
250
|
+
|
|
251
|
+
def get_YAML_kTP(self,reaction,collider):
|
|
252
|
+
gas = ct.Solution("shortMech.yaml")
|
|
253
|
+
k_TP = []
|
|
254
|
+
for T in self.T_ls:
|
|
255
|
+
temp = []
|
|
256
|
+
for P in self.P_ls:
|
|
257
|
+
gas.TPX = T, P*ct.one_atm, {collider:1.0}
|
|
258
|
+
val=gas.forward_rate_constants[gas.reaction_equations().index(reaction['equation'])]
|
|
259
|
+
temp.append(val)
|
|
260
|
+
k_TP.append(temp)
|
|
261
|
+
return np.array(k_TP)
|
|
262
|
+
|
|
263
|
+
def first_cheby_poly(self, x, n):
|
|
264
|
+
'''Generate n-th order Chebyshev ploynominals of first kind.'''
|
|
265
|
+
if n == 0: return 1
|
|
266
|
+
elif n == 1: return x
|
|
267
|
+
result = 2. * x * self.first_cheby_poly(x, 1) - self.first_cheby_poly(x, 0)
|
|
268
|
+
m = 0
|
|
269
|
+
while n - m > 2:
|
|
270
|
+
result = 2. * x * result - self.first_cheby_poly(x, m+1)
|
|
271
|
+
m += 1
|
|
272
|
+
# print(result)
|
|
273
|
+
return result
|
|
274
|
+
def reduced_P(self,P):
|
|
275
|
+
'''Calculate the reduced pressure.'''
|
|
276
|
+
P_tilde = 2. * np.log10(P) - np.log10(self.P_min) - np.log10(self.P_max)
|
|
277
|
+
P_tilde /= (np.log10(self.P_max) - np.log10(self.P_min))
|
|
278
|
+
return P_tilde
|
|
279
|
+
def reduced_T(self,T):
|
|
280
|
+
'''Calculate the reduced temperature.'''
|
|
281
|
+
T_tilde = 2. * T ** (-1) - self.T_min ** (-1) - self.T_max ** (-1)
|
|
282
|
+
T_tilde /= (self.T_max ** (-1) - self.T_min ** (-1))
|
|
283
|
+
return T_tilde
|
|
284
|
+
def cheby_poly(self,reaction,collider):
|
|
285
|
+
'''Fit the Chebyshev polynominals to rate constants.
|
|
286
|
+
Input rate constants vector k should be arranged based on pressure.'''
|
|
287
|
+
k_TP = self.get_YAML_kTP(reaction,collider)
|
|
288
|
+
cheb_mat = np.zeros((len(k_TP.flatten()), self.n_T * self.n_P))
|
|
289
|
+
for m, T in enumerate(self.T_ls):
|
|
290
|
+
for n, P in enumerate(self.P_ls):
|
|
291
|
+
for i in range(self.n_T):
|
|
292
|
+
for j in range(self.n_P):
|
|
293
|
+
T_tilde = self.reduced_T(T)
|
|
294
|
+
P_tilde = self.reduced_P(P)
|
|
295
|
+
T_cheb = self.first_cheby_poly(T_tilde, i)
|
|
296
|
+
P_cheb = self.first_cheby_poly(P_tilde, j)
|
|
297
|
+
cheb_mat[m * len(self.P_ls) + n, i * self.n_P + j] = T_cheb * P_cheb
|
|
298
|
+
coef = np.linalg.lstsq(cheb_mat, np.log10(k_TP.flatten()),rcond=None)[0].reshape((self.n_T, self.n_P))
|
|
299
|
+
return coef
|
|
300
|
+
def get_cheb_table(self,reaction,collider,label,epsilon,kTP='off'):
|
|
301
|
+
coef = self.cheby_poly(reaction,collider)
|
|
302
|
+
if kTP=='on':
|
|
303
|
+
colDict = {
|
|
304
|
+
'collider': label,
|
|
305
|
+
'eps': epsilon,
|
|
306
|
+
'temperature-range': [float(self.T_min), float(self.T_max)],
|
|
307
|
+
'pressure-range': [f'{self.P_min:.3e} atm', f'{self.P_max:.3e} atm'],
|
|
308
|
+
'data': []
|
|
309
|
+
}
|
|
310
|
+
for i in range(len(coef)):
|
|
311
|
+
row=[]
|
|
312
|
+
for j in range(len(coef[0])):
|
|
313
|
+
# row.append(f'{coef[i,j]:.4e}')
|
|
314
|
+
row.append(float(coef[i,j]))
|
|
315
|
+
colDict['data'].append(row)
|
|
316
|
+
else:
|
|
317
|
+
colDict = {
|
|
318
|
+
'collider': label,
|
|
319
|
+
'eps': epsilon,
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return colDict
|
|
323
|
+
|
|
324
|
+
def get_PLOG_table(self,reaction,collider,label,epsilon,kTP='off'):
|
|
325
|
+
if kTP=='on':
|
|
326
|
+
colDict = {
|
|
327
|
+
'collider': label,
|
|
328
|
+
'eps': epsilon,
|
|
329
|
+
'rate-constants': []
|
|
330
|
+
}
|
|
331
|
+
def arrhenius(T, A, n, Ea):
|
|
332
|
+
return np.log(A) + n*np.log(T)+ (-Ea/(1.987*T))
|
|
333
|
+
gas = ct.Solution('shortMech.yaml')
|
|
334
|
+
Xdict = self.get_Xdict(reaction)
|
|
335
|
+
for i,P in enumerate(Xdict.keys()):
|
|
336
|
+
k_list = []
|
|
337
|
+
for j,T in enumerate(Xdict[P]):
|
|
338
|
+
gas.TPX = T, P*ct.one_atm, {collider:1}
|
|
339
|
+
k_T = gas.forward_rate_constants[gas.reaction_equations().index(reaction['equation'])]
|
|
340
|
+
k_list.append(k_T)
|
|
341
|
+
k_list=np.array(k_list)
|
|
342
|
+
popt, pcov = curve_fit(arrhenius, self.T_ls, np.log(k_list),maxfev = 2000)
|
|
343
|
+
newDict = {
|
|
344
|
+
'P': f'{P:.3e} atm',
|
|
345
|
+
'A': float(popt[0]),
|
|
346
|
+
'b': float(popt[1]),
|
|
347
|
+
'Ea': float(popt[2]),
|
|
348
|
+
}
|
|
349
|
+
colDict['rate-constants'].append(newDict)
|
|
350
|
+
else:
|
|
351
|
+
colDict = {
|
|
352
|
+
'collider': label,
|
|
353
|
+
'eps': epsilon,
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return colDict
|
|
357
|
+
|
|
358
|
+
def get_Troe_table(self,reaction,collider,label,epsilon,kTP='off'):
|
|
359
|
+
def f(X,a0,n0,ea0,ai,ni,eai,Fcent):
|
|
360
|
+
N= 0.75 - 1.27 * np.log10(Fcent)
|
|
361
|
+
c= -0.4 - 0.67 * np.log10(Fcent)
|
|
362
|
+
d=0.14
|
|
363
|
+
Rcal=1.987
|
|
364
|
+
Rjoule=8.3145
|
|
365
|
+
M = X[0]*ct.one_atm/Rjoule/X[1]/1000000.0
|
|
366
|
+
k0 = a0 * (X[1] ** n0) * np.exp(-ea0 / (Rcal * X[1]))
|
|
367
|
+
ki = ai * (X[1] ** ni) * np.exp(-eai / (Rcal * X[1]))
|
|
368
|
+
logps = np.log10(k0) + np.log10(M) - np.log10(ki)
|
|
369
|
+
den = logps + c
|
|
370
|
+
den = den / (N - d * den)
|
|
371
|
+
den = np.power(den, 2) + 1.0
|
|
372
|
+
logF = np.log10(Fcent) / den
|
|
373
|
+
logk_fit = np.log10(k0) + np.log10(M) + np.log10(ki) + logF - np.log10(ki + k0 * M)
|
|
374
|
+
return logk_fit
|
|
375
|
+
Xdict=self.get_Xdict(reaction)
|
|
376
|
+
gas = ct.Solution('shortMech.yaml')
|
|
377
|
+
logk_list=[]
|
|
378
|
+
for i,P in enumerate(Xdict.keys()):
|
|
379
|
+
for j,T in enumerate(Xdict[P]):
|
|
380
|
+
gas.TPX=T,P*ct.one_atm,{collider:1.0}
|
|
381
|
+
k_TP=gas.forward_rate_constants[gas.reaction_equations().index(reaction['equation'])]
|
|
382
|
+
logk_list.append(np.log10(k_TP))
|
|
383
|
+
# NEED TO GENERALIZE THE FOLLOWING LINES
|
|
384
|
+
# if "H + OH (+M)" in reaction:
|
|
385
|
+
k0_g = [4.5300E+21, -1.8100E+00, 4.9870E+02]
|
|
386
|
+
ki_g = [2.5100E+13, 0.234, -114.2]
|
|
387
|
+
# # elif "H + O2 (+M)" in reaction:
|
|
388
|
+
# k0_g = [6.366e+20, -1.72, 524.8]
|
|
389
|
+
# ki_g = [4.7e+12,0.44,0.0]
|
|
390
|
+
# # elif "H2O2 (+M)" in reaction:
|
|
391
|
+
# k0_g = [2.5e+24,-2.3, 4.8749e+04]
|
|
392
|
+
# ki_g = [2.0e+12,0.9,4.8749e+04]
|
|
393
|
+
# # elif "NH2 (+M)" in reaction:
|
|
394
|
+
# k0_g = [1.6e+34,-5.49,1987.0]
|
|
395
|
+
# ki_g = [5.6e+14,-0.414,66.0]
|
|
396
|
+
# # elif "NH3 <=>" in reaction:
|
|
397
|
+
# k0_g = [2.0e+16, 0.0, 9.315e+04]
|
|
398
|
+
# ki_g = [9.0e+16, -0.39, 1.103e+05]
|
|
399
|
+
# # elif "HNO" in reaction:
|
|
400
|
+
# k0_g = [2.4e+14, 0.206, -1550.0]
|
|
401
|
+
# ki_g = [1.5e+15, -0.41, 0.0]
|
|
402
|
+
guess = k0_g+ki_g+[1]
|
|
403
|
+
bounds = (
|
|
404
|
+
[1e-100, -np.inf, -np.inf, 1e-100, -np.inf, -np.inf, 1e-100], # Lower bounds
|
|
405
|
+
[np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, 1] # Upper bounds
|
|
406
|
+
)
|
|
407
|
+
Xvec=self.get_Xvec(reaction)
|
|
408
|
+
popt, pcov = curve_fit(f,Xvec,logk_list,p0=guess,maxfev=1000000,bounds=bounds)
|
|
409
|
+
a0,n0,ea0,ai,ni,eai=popt[0],popt[1],popt[2],popt[3],popt[4],popt[5]
|
|
410
|
+
def numFmt(val):
|
|
411
|
+
return round(float(val),3)
|
|
412
|
+
if kTP=='on':
|
|
413
|
+
colDict = {
|
|
414
|
+
'collider': label,
|
|
415
|
+
'eps': epsilon,
|
|
416
|
+
'low-P-rate-constant': {'A':numFmt(a0), 'b': numFmt(n0), 'Ea': numFmt(ea0)},
|
|
417
|
+
'high-P-rate-constant': {'A': numFmt(ai), 'b': numFmt(ni), 'Ea': numFmt(eai)},
|
|
418
|
+
'Troe': {'A': round(float(popt[6]),3), 'T3': 1.0e-30, 'T1': 1.0e+30}
|
|
419
|
+
}
|
|
420
|
+
else:
|
|
421
|
+
colDict = {
|
|
422
|
+
'collider': label,
|
|
423
|
+
'eps': epsilon,
|
|
424
|
+
}
|
|
425
|
+
return colDict
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def final_yaml(self,foutName,fit_fxn): # returns PLOG in LMRR YAML format
|
|
429
|
+
newMechanism={
|
|
430
|
+
'units': self.mech['units'],
|
|
431
|
+
'phases': self.mech['phases'],
|
|
432
|
+
'species': self.mech['species'],
|
|
433
|
+
'reactions': []
|
|
434
|
+
}
|
|
435
|
+
for reaction in self.shortMech['reactions']:
|
|
436
|
+
if reaction.get('type')=='linear-burke':
|
|
437
|
+
colliderList=[]
|
|
438
|
+
for i, col in enumerate(reaction['collider-list']):
|
|
439
|
+
if i == 0:
|
|
440
|
+
colliderList.append(fit_fxn(reaction,reaction['reference-collider'],"M",col['eps'],kTP='on'))
|
|
441
|
+
elif len(list(reaction['collider-list'][i].keys()))>3:
|
|
442
|
+
colliderList.append(fit_fxn(reaction,col['collider'],col['collider'],col['eps'],kTP='on'))
|
|
443
|
+
else:
|
|
444
|
+
colliderList.append(fit_fxn(reaction,col['collider'],col['collider'],col['eps'],kTP='off'))
|
|
445
|
+
newMechanism['reactions'].append({
|
|
446
|
+
'equation': reaction['equation'],
|
|
447
|
+
'type': 'linear-burke',
|
|
448
|
+
'reference-collider': reaction['reference-collider'],
|
|
449
|
+
'collider-list': colliderList
|
|
450
|
+
})
|
|
451
|
+
else:
|
|
452
|
+
newMechanism['reactions'].append(reaction)
|
|
453
|
+
with open(foutName, 'w') as outfile:
|
|
454
|
+
yaml.dump(newMechanism, outfile, default_flow_style=None,sort_keys=False)
|
|
455
|
+
|
|
456
|
+
def Troe(self,foutName): # returns PLOG in LMRR YAML format
|
|
457
|
+
self.final_yaml(foutName,self.get_Troe_table)
|
|
458
|
+
def PLOG(self,foutName): # returns PLOG in LMRR YAML format
|
|
459
|
+
self.final_yaml(foutName,self.get_PLOG_table)
|
|
460
|
+
def cheb2D(self,foutName): # returns Chebyshev in LMRR YAML format
|
|
461
|
+
self.final_yaml(foutName,self.get_cheb_table)
|
|
462
|
+
|
|
463
|
+
# # INPUTS
|
|
464
|
+
T_list=np.linspace(200,2000,100)
|
|
465
|
+
# P_list=np.logspace(-12,12,num=120)
|
|
466
|
+
P_list=np.logspace(-1,2,num=5)
|
|
467
|
+
|
|
468
|
+
mF = masterFitter(T_list,P_list,"LMRR-generator//test//inputs//testinput.yaml",n_P=7,n_T=7,M_only=True)
|
|
469
|
+
|
|
470
|
+
mF.Troe("LMRtest_Troe_M")
|
|
471
|
+
mF.PLOG("LMRtest_PLOG_M")
|
|
472
|
+
mF.cheb2D("LMRtest_cheb_M")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: LMRRfactory
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Auto-generate LMR-R reactions and mechanisms
|
|
5
|
+
Author: Patrick Singal
|
|
6
|
+
Author-email: p.singal@columbia.edu
|
|
7
|
+
Keywords: python,first package
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Education
|
|
10
|
+
Classifier: Programming Language :: Python :: 2
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: cantera
|
|
16
|
+
Requires-Dist: numpy
|
|
17
|
+
Requires-Dist: pyyaml
|
|
18
|
+
Requires-Dist: scipy
|
|
19
|
+
|
|
20
|
+
Auto-generates LMR-R reactions (and mechanisms) according to the user’s choice of Plog, Troe, or Chebyshev sub-formats
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
LMRRfactory/__init__.py
|
|
5
|
+
LMRRfactory/masterFitter.py
|
|
6
|
+
LMRRfactory.egg-info/PKG-INFO
|
|
7
|
+
LMRRfactory.egg-info/SOURCES.txt
|
|
8
|
+
LMRRfactory.egg-info/dependency_links.txt
|
|
9
|
+
LMRRfactory.egg-info/requires.txt
|
|
10
|
+
LMRRfactory.egg-info/top_level.txt
|
|
11
|
+
LMRRfactory/ext/__init__.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
LMRRfactory
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: LMRRfactory
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Auto-generate LMR-R reactions and mechanisms
|
|
5
|
+
Author: Patrick Singal
|
|
6
|
+
Author-email: p.singal@columbia.edu
|
|
7
|
+
Keywords: python,first package
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Education
|
|
10
|
+
Classifier: Programming Language :: Python :: 2
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: cantera
|
|
16
|
+
Requires-Dist: numpy
|
|
17
|
+
Requires-Dist: pyyaml
|
|
18
|
+
Requires-Dist: scipy
|
|
19
|
+
|
|
20
|
+
Auto-generates LMR-R reactions (and mechanisms) according to the user’s choice of Plog, Troe, or Chebyshev sub-formats
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
VERSION = '0.0.1'
|
|
4
|
+
DESCRIPTION = 'Auto-generate LMR-R reactions and mechanisms'
|
|
5
|
+
LONG_DESCRIPTION = 'Auto-generates LMR-R reactions (and mechanisms) according to the user’s choice of Plog, Troe, or Chebyshev sub-formats'
|
|
6
|
+
|
|
7
|
+
# Setting up
|
|
8
|
+
setup(
|
|
9
|
+
# the name must match the folder name 'verysimplemodule'
|
|
10
|
+
name="LMRRfactory",
|
|
11
|
+
version=VERSION,
|
|
12
|
+
author="Patrick Singal",
|
|
13
|
+
author_email="p.singal@columbia.edu",
|
|
14
|
+
description=DESCRIPTION,
|
|
15
|
+
long_description=LONG_DESCRIPTION,
|
|
16
|
+
packages=find_packages(),
|
|
17
|
+
install_requires=['cantera','numpy','pyyaml','scipy'],
|
|
18
|
+
|
|
19
|
+
keywords=['python', 'first package'],
|
|
20
|
+
classifiers= [
|
|
21
|
+
"Development Status :: 3 - Alpha",
|
|
22
|
+
"Intended Audience :: Education",
|
|
23
|
+
"Programming Language :: Python :: 2",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Operating System :: MacOS :: MacOS X",
|
|
26
|
+
"Operating System :: Microsoft :: Windows",
|
|
27
|
+
]
|
|
28
|
+
)
|