From fbd5b405a27453936695324be1c96242e8fff1e5 Mon Sep 17 00:00:00 2001 From: Christopher Neal Date: Thu, 29 Jan 2026 17:21:25 -0500 Subject: [PATCH 1/3] first pass at adding linear initial condition to counterflow flame --- doc/sphinx/python/onedim.rst | 5 ++++ interfaces/cython/cantera/onedim.py | 42 ++++++++++++++++++++++------ interfaces/cython/cantera/onedim.pyi | 1 + test/python/test_onedim.py | 26 +++++++++++++++++ 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/doc/sphinx/python/onedim.rst b/doc/sphinx/python/onedim.rst index f6cce89f3aa..396f8f6d273 100644 --- a/doc/sphinx/python/onedim.rst +++ b/doc/sphinx/python/onedim.rst @@ -23,6 +23,11 @@ CounterflowDiffusionFlame ^^^^^^^^^^^^^^^^^^^^^^^^^ .. autoclass:: CounterflowDiffusionFlame + .. note:: + ``CounterflowDiffusionFlame.set_initial_guess`` accepts ``mode="linear"`` + to initialize profiles by linearly interpolating inlet temperature and + composition. + CounterflowPremixedFlame ^^^^^^^^^^^^^^^^^^^^^^^^ .. autoclass:: CounterflowPremixedFlame diff --git a/interfaces/cython/cantera/onedim.py b/interfaces/cython/cantera/onedim.py index e403718c209..1298ef49cd3 100644 --- a/interfaces/cython/cantera/onedim.py +++ b/interfaces/cython/cantera/onedim.py @@ -925,16 +925,24 @@ def __init__(self, gas, grid=None, width=None): super().__init__((self.fuel_inlet, self.flame, self.oxidizer_inlet), gas, grid) - def set_initial_guess(self, data=None, group=None): + def set_initial_guess(self, data=None, group=None, mode="stoich"): """ Set the initial guess for the solution. By default, the initial guess is generated by assuming infinitely-fast chemistry. Alternatively, a previously calculated result can be supplied as an initial guess via 'data' and 'key' inputs (see `FlameBase.set_initial_guess`). + + :param mode: + Initial guess mode. ``'stoich'`` (default) uses the stoichiometric, + infinitely-fast chemistry guess. ``'linear'`` linearly interpolates + inlet temperature and composition profiles. + If ``data`` is provided, this option is ignored. """ super().set_initial_guess(data=data, group=group) if data: return + if mode not in ("stoich", "linear"): + raise ValueError("mode must be 'stoich' or 'linear'") # Compute stoichiometric mixture composition Yin_f = self.fuel_inlet.Y @@ -955,6 +963,31 @@ def set_initial_guess(self, data=None, group=None): raise CanteraError("Mass flow for fuel and/or oxidizer " "must be positive") + zz = self.flame.grid + dz = zz[-1] - zz[0] + a = (u0o + u0f)/dz + L = - 0.5 * (rho0o + rho0f) * a**2 + + x0 = np.sqrt(mdotf*u0f) * dz / (np.sqrt(mdotf*u0f) + np.sqrt(mdoto*u0o)) + nz = len(zz) + + if mode == "linear": + zrel = (zz - zz[0])/dz + T = (1.0 - zrel) * T0f + zrel * T0o + Y = np.outer((1.0 - zrel), Yin_f) + np.outer(zrel, Yin_o) + T[0] = T0f + T[-1] = T0o + Y[0] = Yin_f + Y[-1] = Yin_o + + self.flame.set_profile('velocity', [0.0, 1.0], [u0f, -u0o]) + self.flame.set_profile('spreadRate', [0.0, x0/dz, 1.0], [0.0, a, 0.0]) + self.flame.set_profile("Lambda", [0.0, 1.0], [L, L]) + self.flame.set_profile('T', zrel, T) + for k, spec in enumerate(self.gas.species_names): + self.flame.set_profile(spec, zrel, Y[:,k]) + return + zst = 1 / (1 + self.gas.stoich_air_fuel_ratio(Yin_f, Yin_o, 'mass')) Yst = zst * Yin_f + (1.0 - zst) * Yin_o @@ -966,16 +999,9 @@ def set_initial_guess(self, data=None, group=None): Yeq = self.gas.Y # estimate strain rate - zz = self.flame.grid - dz = zz[-1] - zz[0] - a = (u0o + u0f)/dz kOx = (self.gas.species_index('O2') if 'O2' in self.gas.species_names else self.gas.species_index('o2')) f = np.sqrt(a / (2.0 * self.gas.mix_diff_coeffs[kOx])) - L = - 0.5 * (rho0o + rho0f) * a**2 - - x0 = np.sqrt(mdotf*u0f) * dz / (np.sqrt(mdotf*u0f) + np.sqrt(mdoto*u0o)) - nz = len(zz) Y = np.zeros((nz, self.gas.n_species)) T = np.zeros(nz) diff --git a/interfaces/cython/cantera/onedim.pyi b/interfaces/cython/cantera/onedim.pyi index 620a942603e..fe37652aef7 100644 --- a/interfaces/cython/cantera/onedim.pyi +++ b/interfaces/cython/cantera/onedim.pyi @@ -346,6 +346,7 @@ class BurnerFlame(FlameBase): self, data: SolutionArray[Solution] | DataFrame | str | Path | None = None, group: str | None = None, + mode: Literal["stoich", "linear"] = "stoich", ) -> None: ... def solve( self, diff --git a/test/python/test_onedim.py b/test/python/test_onedim.py index 07fbadb1b3e..5e64cafe8c6 100644 --- a/test/python/test_onedim.py +++ b/test/python/test_onedim.py @@ -1295,6 +1295,32 @@ def test_bad_boundary_conditions(self): sim.fuel_inlet.X = "H2: 1.0" sim.set_initial_guess() + def test_linear_initial_guess_inert(self): + gas = ct.Solution("h2o2.yaml") + gas.TP = 300, ct.one_atm + sim = ct.CounterflowDiffusionFlame(gas, width=0.02) + sim.fuel_inlet.mdot = 0.2 + sim.oxidizer_inlet.mdot = 0.2 + sim.fuel_inlet.X = "AR:1.0" + sim.oxidizer_inlet.X = "N2:1.0" + sim.fuel_inlet.T = 300 + sim.oxidizer_inlet.T = 600 + + sim.set_initial_guess(mode="linear") + + zrel = (sim.grid - sim.grid[0]) / (sim.grid[-1] - sim.grid[0]) + k_ar = gas.species_index("AR") + k_n2 = gas.species_index("N2") + + assert sim.T[0] == approx(sim.fuel_inlet.T) + assert sim.T[-1] == approx(sim.oxidizer_inlet.T) + assert sim.Y[k_ar, 0] == approx(1.0) + assert sim.Y[k_n2, 0] == approx(0.0) + assert sim.Y[k_ar, -1] == approx(0.0) + assert sim.Y[k_n2, -1] == approx(1.0) + assert np.allclose(sim.Y[k_ar], 1.0 - zrel) + assert np.allclose(sim.Y[k_n2], zrel) + def run_restore_diffusionflame(self, fname): gas = ct.Solution("h2o2.yaml") sim = ct.CounterflowDiffusionFlame(gas) From 01a932215961b099b21a6d106ca83aa95fb19f75 Mon Sep 17 00:00:00 2001 From: Christopher Neal Date: Tue, 3 Feb 2026 16:15:20 -0500 Subject: [PATCH 2/3] error check added and test updated --- interfaces/cython/cantera/onedim.py | 7 ++++++- test/python/test_onedim.py | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/interfaces/cython/cantera/onedim.py b/interfaces/cython/cantera/onedim.py index 1298ef49cd3..967a2b86578 100644 --- a/interfaces/cython/cantera/onedim.py +++ b/interfaces/cython/cantera/onedim.py @@ -988,7 +988,12 @@ def set_initial_guess(self, data=None, group=None, mode="stoich"): self.flame.set_profile(spec, zrel, Y[:,k]) return - zst = 1 / (1 + self.gas.stoich_air_fuel_ratio(Yin_f, Yin_o, 'mass')) + afr = self.gas.stoich_air_fuel_ratio(Yin_f, Yin_o, 'mass') + zst = 1 / (1 + afr) + if not np.isfinite(zst) or zst <= 0.0 or zst >= 1.0: + raise CanteraError( + "Stoichiometric initial guess requires a reactive fuel/oxidizer " + "pair; use mode='linear'.") Yst = zst * Yin_f + (1.0 - zst) * Yin_o # get adiabatic flame temperature and composition diff --git a/test/python/test_onedim.py b/test/python/test_onedim.py index 5e64cafe8c6..899a00cc90d 100644 --- a/test/python/test_onedim.py +++ b/test/python/test_onedim.py @@ -1306,6 +1306,9 @@ def test_linear_initial_guess_inert(self): sim.fuel_inlet.T = 300 sim.oxidizer_inlet.T = 600 + with pytest.raises(ct.CanteraError, match="Stoichiometric initial guess"): + sim.set_initial_guess() + sim.set_initial_guess(mode="linear") zrel = (sim.grid - sim.grid[0]) / (sim.grid[-1] - sim.grid[0]) @@ -1321,6 +1324,9 @@ def test_linear_initial_guess_inert(self): assert np.allclose(sim.Y[k_ar], 1.0 - zrel) assert np.allclose(sim.Y[k_n2], zrel) + sim.energy_enabled = False + sim.solve(loglevel=0, refine_grid=False) + def run_restore_diffusionflame(self, fname): gas = ct.Solution("h2o2.yaml") sim = ct.CounterflowDiffusionFlame(gas) From 082fcdecfd4191650e8f9638059a219f7d3de80f Mon Sep 17 00:00:00 2001 From: Christopher Neal Date: Tue, 3 Feb 2026 17:45:29 -0500 Subject: [PATCH 3/3] added check for invalid initial guess mode --- test/python/test_onedim.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/python/test_onedim.py b/test/python/test_onedim.py index 899a00cc90d..53878bde2e3 100644 --- a/test/python/test_onedim.py +++ b/test/python/test_onedim.py @@ -1306,6 +1306,9 @@ def test_linear_initial_guess_inert(self): sim.fuel_inlet.T = 300 sim.oxidizer_inlet.T = 600 + with pytest.raises(ValueError, match="mode must be 'stoich' or 'linear'"): + sim.set_initial_guess(mode="bad-mode") + with pytest.raises(ct.CanteraError, match="Stoichiometric initial guess"): sim.set_initial_guess()