Analyze and enforce numerical stability for time-dependent PDE simulations. Use when selecting time steps, choosing explicit/implicit schemes, diagnosing numerical blow-up, checking CFL/Fourier/reaction criteria, performing von Neumann analysis, assessing matrix conditioning, or detecting stiffness in advection/diffusion/reaction problems.
Provide a repeatable checklist and script-driven checks to keep time-dependent simulations stable and defensible.
scripts/requirements.txt for dependencies| Input | Description | Example |
|-------|-------------|---------|
| Grid spacing dx | Spatial discretization | 0.01 m |
| Time step dt | Temporal discretization | 1e-4 s |
| Velocity v | Advection speed | 1.0 m/s |
| Diffusivity D | Thermal/mass diffusivity | 1e-5 m²/s |
| Reaction rate k | First-order rate constant | 100 s⁻¹ |
| Dimensions | 1D, 2D, or 3D | 2 |
| Scheme type | Explicit or implicit | explicit |
Is the problem stiff (fast + slow dynamics)?
├── YES → Use implicit or IMEX scheme
│ └── Check conditioning with matrix_condition.py
└── NO → Is CFL/Fourier satisfied with reasonable dt?
├── YES → Use explicit scheme (cheaper per step)
└── NO → Consider implicit or reduce dx
| Physics | Number | Explicit Limit (1D) | Formula |
|---------|--------|---------------------|---------|
| Advection | CFL | C ≤ 1 | C = v·dt/dx |
| Diffusion | Fourier | Fo ≤ 0.5 | Fo = D·dt/dx² |
| Reaction | Reaction | R ≤ 1 | R = k·dt |
Multi-dimensional correction: For d dimensions, diffusion limit is Fo ≤ 1/(2d).
| Script | Key Outputs |
|--------|-------------|
| scripts/cfl_checker.py | metrics.cfl, metrics.fourier, recommended_dt, stable |
| scripts/von_neumann_analyzer.py | results.max_amplification, results.stable |
| scripts/matrix_condition.py | results.condition_number, results.is_symmetric |
| scripts/stiffness_detector.py | results.stiffness_ratio, results.real_part_stiffness_ratio, results.imag_dominated, results.stiff, results.recommendation, results.warning |
scripts/cfl_checker.pydt if neededscripts/stiffness_detector.py if multiple scalesscripts/von_neumann_analyzer.pyscripts/matrix_condition.py for implicit solvesUser: My phase-field simulation is blowing up after 100 steps. I'm using explicit Euler with dx=0.01, dt=1e-4, and diffusivity D=1.0.
Agent workflow:
python3 scripts/cfl_checker.py --dx 0.01 --dt 1e-4 --diffusivity 1.0 --dimensions 2 --json
Fo = 1.0 × 1e-4 / (0.01)² = 1.0Fo ≤ 0.252.5e-5 (the tool's recommended_dt, giving Fo = 0.25)cfl_checker.pydt or change scheme# Check CFL/Fourier for 2D diffusion-advection
python3 scripts/cfl_checker.py --dx 0.1 --dt 0.01 --velocity 1.0 --diffusivity 0.1 --dimensions 2 --json
# Von Neumann analysis for custom 3-point stencil
python3 scripts/von_neumann_analyzer.py --coeffs 0.2,0.6,0.2 --dx 1.0 --nk 128 --json
# Detect stiffness from eigenvalue estimates
python3 scripts/stiffness_detector.py --eigs=-1,-1000 --json
# Check matrix conditioning for implicit system
python3 scripts/matrix_condition.py --matrix A.npy --norm 2 --json
| Error | Cause | Resolution |
|-------|-------|------------|
| dx and dt must be positive | Zero or negative values | Provide valid positive numbers |
| No stability criteria applied | Missing velocity/diffusivity | Provide at least one physics parameter |
| Matrix not found: <path> | Invalid path | Check matrix file exists |
| Could not compute eigenvalues | Singular or ill-formed matrix | Check matrix validity |
| Scenario | Meaning | Action |
|----------|---------|--------|
| stable: true | All checked criteria satisfied | Proceed with simulation |
| stable: false | At least one limit violated | Reduce dt or change scheme |
| stable: null | No criteria could be applied | Provide more physics inputs |
| Stiffness ratio > 1000 | Problem is stiff | Use implicit integrator |
| Condition number > 10⁸ | Poorly-conditioned (status: poorly-conditioned) | Preconditioning likely needed |
| Condition number > 10¹⁰ | Ill-conditioned (status: ill-conditioned) | Use scaling/preconditioning |
Conditioning thresholds assume IEEE double precision: solving loses roughly
log10(κ)significant digits, and a matrix becomes numerically singular nearκ ≈ 1/eps ≈ 4.5e15. The> 10⁸/> 10¹⁰cutoffs (matchingmatrix_condition.pystatus) leave ample margin; well-conditioned-for-double FEM matrices (κ up to ~10⁶–10⁷) reportstatus: ok.
Do not declare a scheme/time step "stable" until each applicable item below is satisfied with a recorded value from the scripts, not a mental estimate.
cfl_checker.py --json and recorded metrics.cfl, metrics.fourier, and/or metrics.reaction against the reported limits.* (explicit defaults: CFL ≤ 1, Fo ≤ 1/(2d), R ≤ 1), with stable: true and the intended criteria present in criteria_applied.criteria_applied is non-empty and stable is not null — i.e. at least one physics parameter (--velocity/--diffusivity/--reaction-rate) was actually supplied so a real check ran, not a silent no-op.--dx (anisotropic grids: smallest dx/dy/dz) and re-ran cfl_checker.py after any mesh refinement, since Fo ∝ dt/dx² makes the limit highly sensitive to dx.dt is below the limit, recorded the tool's recommended_dt (and the --safety factor used) so the margin to the stability boundary is explicit and reproducible.von_neumann_analyzer.py --json and confirmed results.max_amplification ≤ 1 (stable: true); noted k_at_max and resolved any even-length-stencil warning.stiffness_detector.py --json and recorded real_part_stiffness_ratio, imag_dominated, and stiff; only chose BDF/Radau when stiff: true on the real-part ratio (not on magnitude alone) and there is no warning.matrix_condition.py --json and recorded condition_number and status; treated poorly-conditioned (>1e8) / ill-conditioned (>1e10) as a flag to scale/precondition before trusting the solve.| Tempting shortcut | Why it's wrong / what to do |
|-------------------|-----------------------------|
| "Implicit scheme, so any dt is fine." | Unconditional stability is not accuracy. cfl_checker.py reports stable: true with relaxed (infinite) limits for --scheme implicit and even adds a note to "check accuracy" — a large dt still ruins temporal error. Size dt for accuracy, not just stability. |
| "It ran 100 steps without crashing, so the setup is stable." | Late-time blow-up from round-off, conservation loss, or marginal Fo is common. Completion ≠ correctness — record metrics.fourier/metrics.cfl vs limits.* and confirm stable: true before trusting the run. |
| "The 1D Fourier limit is 0.5, so Fo ≤ 0.5 is safe." | The explicit diffusion limit is Fo ≤ 1/(2d) — 0.25 in 2D, 0.167 in 3D. Pass the real --dimensions; cfl_checker.py tightens diffusion_limit automatically, and using 0.5 in 2D/3D is an instability. |
| "Stiffness ratio is huge, so use BDF/Radau." | The magnitude stiffness_ratio is misleading for oscillatory/advective/Hamiltonian spectra. Check imag_dominated and real_part_stiffness_ratio: if imag_dominated: true the detector returns stiff: false with a warning — prefer symplectic/leapfrog or a CFL-sized A-stable scheme, not implicit stiff solvers. |
| "I tightened dt, so the old mesh's dt still works after refining dx." | Fo ∝ dt/dx²: halving dx quadruples Fo. Reusing a pre-refinement dt reintroduces a violation. Recompute with cfl_checker.py after every mesh change. |
| "The matrix solved, so its conditioning is fine." | A solve can return numbers while silently losing ~log10(κ) digits. Record condition_number/status from matrix_condition.py; poorly-conditioned/ill-conditioned means scale or precondition before trusting the result. |
dx, dt, and safety are validated as finite positive numbers before any computation; velocity, diffusivity, and reaction_rate, when supplied, are validated as finite--dimensions is restricted to {1, 2, 3} (cfl_checker.py raises and exits 2 otherwise)--eigs) are capped at 10,000 entries and validated as finite numbers--coeffs) are capped at 10,000 entries and validated as finite floatsmatrix_condition.py reads a single matrix file (.npy or text) specified by --matrix; no directory traversal beyond the given pathnp.load() is called with allow_pickle=False to prevent arbitrary code execution via crafted .npy files (matrix_condition.py, stiffness_detector.py)cfl_checker.py, von_neumann_analyzer.py, matrix_condition.py, stiffness_detector.py) with explicit argument listseval(), exec(), or dynamic code generationshell=True)--json) produces structured, parseable results without shell-interpretable contentstiff verdict is based on scale separation among genuinely decaying modes (Re(λ) < 0); the magnitude ratio is still reported as stiffness_ratio. Imaginary-axis-dominated spectra (oscillatory/advection/Hamiltonian) are flagged via imag_dominated and a warning, and are NOT classified as stiff — for those, prefer symplectic/leapfrog or an A-stable scheme sized by the CFL limit, not BDF/Radau.references/stability_criteria.md - Decision thresholds and formulasreferences/common_pitfalls.md - Frequent failure modes and fixesreferences/scheme_catalog.md - Stability properties of common schemesSearch for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer