Skip to content

Timestep regridding for 1D solver#2074

Open
wandadars wants to merge 2 commits intoCantera:mainfrom
wandadars:timestep_regridding
Open

Timestep regridding for 1D solver#2074
wandadars wants to merge 2 commits intoCantera:mainfrom
wandadars:timestep_regridding

Conversation

@wandadars
Copy link
Contributor

@wandadars wandadars commented Jan 11, 2026

During a discussion on the users group, Ray mentioned that Cantera doesn't perform any regridding if timestepping fails during a solve. This pull request is an attempt to add a capability to the 1D solver that would allow for a regrid to happen if there is a failure in the timestepping. This feature allows for a set number of regridding steps to be taken during a failure of the timestepping. The test case that we were looking at was that of a pressure field being stepped up incrementally on a counterflow diffusion flame domain. The highest pressure possible was increased when the timestep regridding is allowed. This happens when cases have a bad initial condition or a coarse grid and fails right at the start during the timestepping phase.

This is Ray's script that I tweaked a bit and used to test the regridding.

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

mech_path = 'h2o2.yaml'


# Inlet parameters
fuel_X = "H2:1.0"
oxidizer_X = "O2:1.0"
fuel_T = 800.0
oxidizer_T = 711.0
mdot_fuel = 0.3  # kg/m^2/s
width = 30e-3
P_start = 10e6
P_end = 60e6
n_pressures = 100
pressure_levels = np.linspace(P_start, P_end, n_pressures)

oxidizer_mdot_factor = 3.0
n_points = 50

# Adaptive stepping with backtracking on failures
loglevel = 1

gas = ct.Solution(mech_path)
#gas = ct.Solution(mech_path, 'ohmech-RK')

def make_flame(gas):
    flame = ct.CounterflowDiffusionFlame(gas, grid=np.linspace(0, width, n_points))
    flame.max_time_step_count = 1000
    #flame.set_time_step(1.0e-5, [10,20,30,50])

    flame.set_refine_criteria(ratio=2.0, slope=0.06, curve=0.08, prune=0.02)
    flame.fuel_inlet.mdot = mdot_fuel
    flame.fuel_inlet.X = fuel_X
    flame.fuel_inlet.T = fuel_T
    flame.oxidizer_inlet.X = oxidizer_X
    flame.oxidizer_inlet.T = oxidizer_T
    return flame

def update_inlet_mdot(flame):
    rho_f = flame.fuel_inlet.phase.density
    rho_o = flame.oxidizer_inlet.phase.density
    flame.fuel_inlet.mdot = mdot_fuel
    flame.oxidizer_inlet.mdot = (mdot_fuel / rho_f) * rho_o * oxidizer_mdot_factor

flame = make_flame(gas)
flame.set_time_step_regrid(15)

def plot_diagnostics(flame, pressure, step_index, total_pressures):
    z = flame.grid
    temperature = flame.T
    velocity = flame.velocity
    spread_rate = flame.spread_rate
    heat_release = flame.heat_release_rate
    density = flame.density
    mixture_fraction = flame.mixture_fraction('Bilger')

    fig, axes = plt.subplots(3, 2, figsize=(14, 10), sharex=True)
    axes = axes.ravel()

    line_kwargs = dict(linewidth=2.0, marker='o', markersize=3,
                       markerfacecolor='none', markeredgewidth=0.6)
    axes[0].plot(z, temperature, **line_kwargs)
    axes[0].set_title('Temperature')
    axes[0].set_ylabel('T [K]')

    axes[1].plot(z, heat_release, **line_kwargs)
    axes[1].set_title('Heat Release Rate')
    axes[1].set_ylabel('HRR [W/m^3]')

    axes[2].plot(z, velocity, **line_kwargs)
    axes[2].set_title('Velocity')
    axes[2].set_ylabel('u [m/s]')

    axes[3].plot(z, spread_rate, **line_kwargs)
    axes[3].set_title('Spread Rate')
    axes[3].set_ylabel('V [1/s]')

    axes[4].plot(z, density, **line_kwargs)
    axes[4].set_title('Density')
    axes[4].set_ylabel('rho [kg/m^3]')

    axes[5].plot(z, mixture_fraction, **line_kwargs)
    axes[5].set_title('Bilger Mixture Fraction')
    axes[5].set_ylabel('Z [-]')

    for ax in axes:
        ax.grid(True, alpha=0.3)
    for ax in axes[-2:]:
        ax.set_xlabel('z [m]')

    fig.suptitle(f'Diagnostics at P = {pressure:.3e} Pa '
                 f'({step_index}/{total_pressures})')
    fig.tight_layout(rect=[0, 0.03, 1, 0.95])
    plt.show()
    plt.close(fig)

pressure_outfile = 'pressure_solutions.h5'
total_pressures = len(pressure_levels)
for step_index, pressure in enumerate(pressure_levels, start=1):
    print(f'\n=== Solving at P = {pressure:.3e} Pa ({step_index}/{total_pressures}) ===')
    flame.P = pressure
    flame.fuel_inlet.X = fuel_X
    flame.fuel_inlet.T = fuel_T
    flame.oxidizer_inlet.X = oxidizer_X
    flame.oxidizer_inlet.T = oxidizer_T
    update_inlet_mdot(flame)
    flame.solve(loglevel=loglevel, auto=False)
    flame.save(pressure_outfile, overwrite=True)
    print(f'Saved solution to {pressure_outfile}')
    plot_diagnostics(flame, pressure, step_index, total_pressures)

print(f'Number of debug outputs = {i}')
flame.show_stats()

AI Statement (required)

-Extensive use of generative AI.
Significant portions of code or documentation were generated with AI, including
logic and implementation decisions. All generated code and documentation were
reviewed and understood by the contributor. Examples: Output from agentic coding
tools and/or substantial refactoring by LLMs (web-based or local).

Checklist

  • The pull request includes a clear description of this code change
  • Commit messages have short titles and reference relevant issues
  • Build passes (scons build & scons test) and unit tests address code coverage
  • Style & formatting of contributed code follows contributing guidelines
  • AI Statement is included
  • The pull request is ready for review

@wandadars
Copy link
Contributor Author

I don't know how to edit the PR header, but this isn't a WIP anymore.

@speth speth changed the title WIP: Timestep regridding for 1D solver Timestep regridding for 1D solver Feb 3, 2026
@speth
Copy link
Member

speth commented Feb 11, 2026

Based on the tests that are failing and timing out, it seems like this is affecting the solution of steady-state reactor networks, which also use the SteadyStateSystem solver, even though those systems don't use regridding.

@codecov
Copy link

codecov bot commented Feb 12, 2026

Codecov Report

❌ Patch coverage is 48.48485% with 51 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.42%. Comparing base (630ba99) to head (bbd68d4).
⚠️ Report is 68 commits behind head on main.

Files with missing lines Patch % Lines
src/numerics/SteadyStateSystem.cpp 29.78% 32 Missing and 1 partial ⚠️
src/oneD/Sim1D.cpp 25.00% 12 Missing ⚠️
include/cantera/oneD/MultiNewton.h 0.00% 2 Missing ⚠️
include/cantera/oneD/Sim1D.h 0.00% 2 Missing ⚠️
include/cantera/numerics/SteadyStateSystem.h 93.75% 0 Missing and 1 partial ⚠️
interfaces/cython/cantera/_onedim.pyx 92.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2074      +/-   ##
==========================================
+ Coverage   77.22%   77.42%   +0.20%     
==========================================
  Files         456      451       -5     
  Lines       53026    52832     -194     
  Branches     8998     8845     -153     
==========================================
- Hits        40947    40904      -43     
+ Misses       9035     8959      -76     
+ Partials     3044     2969      -75     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants