Skip to content

Commit d08223f

Browse files
committed
Full update for example_data submodule pr nakamura mech
1 parent 3e83530 commit d08223f

File tree

4 files changed

+169
-3
lines changed

4 files changed

+169
-3
lines changed

AUTHORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ update, please report on Cantera's
4444
- **Cory Kinney** [@corykinney](https://github.com/corykinney)
4545
- **Gandhali Kogekar** [@gkogekar](https://github.com/gkogekar)
4646
- **Daniel Korff** [@korffdm](https://github.com/korffdm) - Colorado School of Mines
47+
- **Marina Kovaleva** [@marina8888](https://github.com/marina8888) - Tohoku University
4748
- **Ashwin Kumar** [@mgashwinkumar](https://github.com/mgashwinkumar) - Virginia Tech
4849
- **Jon Kristofer**
4950
- **Samesh Lakothia** [@sameshl](https://github.com/sameshl)

interfaces/cython/cantera/onedim.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,34 @@ def set_profile(self, component, positions, values):
172172
"""
173173
super().set_profile(self.flame, component, positions, values)
174174

175+
def get_species_reaction_sensitivities(self, species: str, ix: int) -> np.ndarray:
176+
r"""
177+
Compute the normalized sensitivities of a species,
178+
:math:`X_i` with respect to the reaction rate constants :math:`k_i`:
179+
.. math::
180+
s_i = \frac{k_i}{X_i} \frac{dX_i}{dk_i}
181+
:param species:
182+
Species of interest for sensitivity calculation (i.e "NO")
183+
:param distance:
184+
Index of flame grid point for which the sensitivity analysis is undertaken
185+
"""
186+
187+
def g(sim):
188+
return sim.X[sim.gas.species_index(species), ix]
189+
190+
n_vars = sum(dd.n_components * dd.n_points for dd in self.domains)
191+
192+
i_X = self.flame.component_index(species) + self.domains[1].n_components * ix
193+
194+
dgdx = np.zeros(n_vars)
195+
dgdx[i_X] = 1
196+
X0 = g(self)
197+
198+
def perturb(sim, i, dp):
199+
sim.gas.set_multiplier(1 + dp, i)
200+
201+
return self.solve_adjoint(perturb, self.gas.n_reactions, dgdx) / X0
202+
175203
@property
176204
def max_grid_points(self):
177205
"""
@@ -773,12 +801,12 @@ def get_flame_speed_reaction_sensitivities(self):
773801
def g(sim):
774802
return sim.velocity[0]
775803

776-
Nvars = sum(D.n_components * D.n_points for D in self.domains)
804+
n_vars = sum(dd.n_components * dd.n_points for dd in self.domains)
777805

778806
# Index of u[0] in the global solution vector
779807
i_Su = self.inlet.n_components + self.flame.component_index('velocity')
780808

781-
dgdx = np.zeros(Nvars)
809+
dgdx = np.zeros(n_vars)
782810
dgdx[i_Su] = 1
783811

784812
Su0 = g(self)
@@ -1539,4 +1567,4 @@ def set_initial_guess(self, data=None, group=None):
15391567

15401568
self.set_profile('velocity', [0.0, 1.0], [uu, 0])
15411569
self.set_profile('spread_rate', [0.0, 1.0], [0.0, a])
1542-
self.set_profile("lambda", [0.0, 1.0], [L, L])
1570+
self.set_profile("lambda", [0.0, 1.0], [L, L])
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
r"""
2+
Sensitivity analysis
3+
========================================
4+
In this example we simulate an impinging jet flame, premixed ammonia/hydrogen-air flame,
5+
calculating the sensitivity of N2O to the A-factor reaction rate constants.
6+
Requires: cantera >= 3.0.0, pandas
7+
.. tags:: Python, combustion, 1D flow, species reaction, premixed flame,
8+
sensitivity analysis, plotting
9+
"""
10+
import cantera as ct
11+
import numpy as np
12+
import pandas as pd
13+
import matplotlib.pyplot as plt
14+
15+
# Define the gas mixture
16+
gas = ct.Solution("example_data/nakamura.yaml") # Use the Nakamura mechanism for ammonia combustion blends
17+
gas.TP = 295, ct.one_atm
18+
air = "O2:0.21,N2:0.79"
19+
gas.set_equivalence_ratio(phi=0.6, fuel="NH3:0.7, H2:0.3", oxidizer=air)
20+
flame = ct.ImpingingJet(gas=gas, width=0.02)
21+
flame.inlet.mdot = 0.255 * gas.density
22+
flame.surface.T = 493.5
23+
flame.set_initial_guess("equil")
24+
25+
26+
# Refine grid to improve accuracy
27+
flame.set_refine_criteria(ratio=3, slope=0.025, curve=0.05)
28+
29+
# Solve the flame
30+
flame.solve(loglevel=1, auto=True) # Increase loglevel for more output
31+
32+
# Plot temperature profile
33+
plt.figure(figsize=(8, 6))
34+
plt.plot(flame.grid * 1e3, flame.T, label="Flame Temperature", color="red")
35+
plt.xlabel("Distance (mm)")
36+
plt.ylabel("Temperature (K)")
37+
plt.title("Temperature Profile of a Flame")
38+
plt.grid(True, linestyle='--', alpha=0.7)
39+
plt.legend()
40+
plt.tight_layout()
41+
plt.show()
42+
43+
# Create a DataFrame to store sensitivity-analysis data
44+
sens = pd.DataFrame(index=gas.reaction_equations(), columns=["sensitivity"])
45+
46+
# Use the adjoint method to calculate species sensitivities at a set distance in the flame domain
47+
distance = 0.02
48+
species = "N2O"
49+
sens.sensitivity = flame.get_species_reaction_sensitivities(species, distance)
50+
51+
sens = sens.iloc[(-sens['sensitivity'].abs()).argsort()]
52+
fig, ax = plt.subplots()
53+
54+
sens.head(15).plot.barh(ax=ax, legend=None)
55+
ax.invert_yaxis() # put the largest sensitivity on top
56+
ax.set_title(f"Sensitivities for {species} Using the Nakamura mechanism")
57+
ax.set_xlabel(r"Sensitivity: $\frac{\partial\:\ln X_{N2O}}{\partial\:\ln k}$")
58+
ax.grid(axis='x')
59+
plt.tight_layout()
60+
plt.show()
61+
62+
63+
# Forward sensitivities
64+
dk = 3e-4
65+
66+
# get index in the grid at distance
67+
ix = np.argmin(np.abs(flame.grid - distance))
68+
69+
Su0 = flame.X[gas.species_index(species), ix]
70+
fwd = []
71+
for m in range(flame.gas.n_reactions):
72+
flame.gas.set_multiplier(1.0) # reset all multipliers
73+
flame.gas.set_multiplier(1 + dk, m) # perturb reaction m
74+
flame.solve(loglevel=0, refine_grid=False)
75+
Suplus = flame.X[gas.species_index(species), ix]
76+
flame.gas.set_multiplier(1 - dk, m) # perturb reaction m
77+
flame.solve(loglevel=0, refine_grid=False)
78+
Suminus = flame.X[gas.species_index(species), ix]
79+
fwd.append((Suplus - Suminus) / (2 * Su0 * dk))
80+
sens = pd.DataFrame(index=gas.reaction_equations(), columns=["sensitivity with forward"], data=fwd)
81+
sens = sens.iloc[(-sens['sensitivity with forward'].abs()).argsort()]
82+
fig, ax = plt.subplots()
83+
84+
sens.head(15).plot.barh(ax=ax, legend=None)
85+
ax.invert_yaxis() # put the largest sensitivity on top
86+
ax.set_title(f"Sensitivities for {species} Using Nakamura Mech")
87+
ax.set_xlabel(r"Sensitivity: $\frac{\partial\:\ln X_{i}}{\partial\:\ln k}$")
88+
ax.grid(axis='x')
89+
plt.tight_layout()
90+
plt.show()

test/python/test_onedim.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,53 @@ def test_adjoint_sensitivities(self):
530530
fwd = (Suplus-Suminus)/(2*Su0*dk)
531531
assert fwd == approx(dSdk_adj[m], rel=5e-3, abs=1e-7)
532532

533+
def test_adjoint_sensitivities2(self):
534+
self.gas = ct.Solution('gri30.yaml')
535+
self.gas.TP = 300, 101325
536+
self.gas.set_equivalence_ratio(1.0, 'CH4', {"O2": 1.0, "N2": 3.76})
537+
538+
self.flame = ct.FreeFlame(self.gas, width=0.014)
539+
self.flame.set_refine_criteria(ratio=3, slope=0.07, curve=0.14)
540+
self.flame.solve(loglevel=0, refine_grid=False)
541+
542+
# Adjoint sensitivities
543+
ix = -1
544+
species = "NO"
545+
dk = 1e-4
546+
547+
adjoint_sensitivities = self.flame.get_species_reaction_sensitivities(species, ix)
548+
reaction_equations = self.gas.reaction_equations()
549+
adjoint_sens = [(reaction, sensitivity) for reaction, sensitivity in
550+
zip(reaction_equations, adjoint_sensitivities)]
551+
adjoint_sens_sorted = sorted(adjoint_sens, key=lambda x: abs(x[1]), reverse=True)
552+
553+
Su0 = self.flame.X[self.gas.species_index(species), ix]
554+
fwd_sensitivities = []
555+
for m in range(self.flame.gas.n_reactions):
556+
self.flame.gas.set_multiplier(1.0) # reset all multipliers
557+
self.flame.gas.set_multiplier(1 + dk, m) # perturb reaction m
558+
self.flame.solve(loglevel=0, refine_grid=False)
559+
Suplus = self.flame.X[self.gas.species_index(species), ix]
560+
self.flame.gas.set_multiplier(1 - dk, m) # perturb reaction m
561+
self.flame.solve(loglevel=0, refine_grid=False)
562+
Suminus = self.flame.X[self.gas.species_index(species), ix]
563+
fwd_sensitivities.append((Suplus - Suminus) / (2 * Su0 * dk))
564+
fwd_sens = [(reaction, sensitivity) for reaction, sensitivity in zip(reaction_equations, fwd_sensitivities)]
565+
fwd_sens_sorted = sorted(fwd_sens, key=lambda x: abs(x[1]), reverse=True)
566+
567+
# Extract top 10 reactions from both lists
568+
top_reactions_adjoint = [reaction for reaction, _ in adjoint_sens_sorted[:10]]
569+
top_reactions_fwd = [reaction for reaction, _ in fwd_sens_sorted[:10]]
570+
571+
# Count common reactions
572+
common_reactions = list(set(top_reactions_fwd) & set(top_reactions_adjoint))
573+
574+
# Assert that at least 8 reactions match
575+
assert len(
576+
common_reactions) >= 8, f"Only {len(common_reactions)} Fwd and adjoint based species sensitivities do not match"
577+
578+
579+
533580
def test_jacobian_options(self):
534581
reactants = {'H2': 0.65, 'O2': 0.5, 'AR': 2}
535582
self.create_sim(p=ct.one_atm, Tin=300, reactants=reactants, width=0.03)

0 commit comments

Comments
 (0)