Numerical stability
Skill HeshamFS/materials-simulation-skills/skills/core-numerical/numerical-stability
Agent Skills for computational materials science -- numerical stability, solvers, meshing, convergence, and simulation workflows.
npx -y skills add HeshamFS/materials-simulation-skills --skill numerical-stabilityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
Analyze numerical stability for time-dependent PDE simulations — check CFL and Fourier criteria, perform von Neumann stability analysis, detect stiffness, evaluate matrix conditioning, and recommend explicit vs implicit time-stepping schemes. Use when selecting time steps, diagnosing numerical blow-up or solver divergence, checking convergence criteria, or evaluating scheme stability for advection, diffusion, or reaction problems, even if the user doesn't explicitly mention "stability" or "CFL."
SKILL.md
13.7 KB, as published. Nobody here has run it
Numerical Stability
Goal
Provide a repeatable checklist and script-driven checks to keep time-dependent simulations stable and defensible.
Requirements
- Python 3.10+
- NumPy (for matrix_condition.py and von_neumann_analyzer.py)
- See
scripts/requirements.txtfor dependencies
Inputs to Gather
| 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 |
Decision Guidance
Choosing Explicit vs Implicit
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
Stability Limit Quick Reference
| 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 Outputs (JSON Fields)
| 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 |
Workflow
- Identify dominant physics (advection vs diffusion vs reaction)
- Run CFL checker with
scripts/cfl_checker.py - Compare to limits and adjust
dtif needed - Check stiffness with
scripts/stiffness_detector.pyif multiple scales - Analyze custom schemes with
scripts/von_neumann_analyzer.py - Check conditioning with
scripts/matrix_condition.pyfor implicit solves - Document the stability verdict and recommended time step
Conversational Workflow Example
User: 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:
- Check stability criteria:
python3 scripts/cfl_checker.py --dx 0.01 --dt 1e-4 --diffusivity 1.0 --dimensions 2 --json - Interpret results:
- Fourier number:
Fo = 1.0 × 1e-4 / (0.01)² = 1.0 - 2D limit:
Fo ≤ 0.25 - Violation: Fo = 1.0 > 0.25, unstable!
- Fourier number:
- Recommend fix:
- Reduce dt to
2.5e-5(the tool'srecommended_dt, giving Fo = 0.25) - Or increase dx, or switch to implicit
- Reduce dt to
Pre-Simulation Stability Checklist
- Identify dominant physics and nondimensional groups
- Compute CFL/Fourier/Reaction numbers with
cfl_checker.py - If explicit and limit violated, reduce
dtor change scheme - If stiffness ratio > 1000, select implicit/stiff integrator
- For custom schemes, verify amplification factor ≤ 1
- Document stability reasoning with inputs and outputs
CLI Examples
# 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 Handling
| 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 |
Interpretation Guidance
| 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.
Verification checklist
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.
- Ran
cfl_checker.py --jsonand recordedmetrics.cfl,metrics.fourier, and/ormetrics.reactionagainst the reportedlimits.*(explicit defaults: CFL ≤ 1, Fo ≤ 1/(2d), R ≤ 1), withstable: trueand the intended criteria present incriteria_applied. - Confirmed
criteria_appliedis non-empty andstableis notnull— i.e. at least one physics parameter (--velocity/--diffusivity/--reaction-rate) was actually supplied so a real check ran, not a silent no-op. - Used the smallest grid spacing across all directions for
--dx(anisotropic grids: smallestdx/dy/dz) and re-rancfl_checker.pyafter any mesh refinement, sinceFo ∝ dt/dx²makes the limit highly sensitive todx. - If the chosen
dtis below the limit, recorded the tool'srecommended_dt(and the--safetyfactor used) so the margin to the stability boundary is explicit and reproducible. - For any custom/non-standard update stencil, ran
von_neumann_analyzer.py --jsonand confirmedresults.max_amplification ≤ 1(stable: true); notedk_at_maxand resolved any even-length-stencilwarning. - For multi-scale systems, ran
stiffness_detector.py --jsonand recordedreal_part_stiffness_ratio,imag_dominated, andstiff; only chose BDF/Radau whenstiff: trueon the real-part ratio (not on magnitude alone) and there is nowarning. - For implicit solves, ran
matrix_condition.py --jsonand recordedcondition_numberandstatus; treatedpoorly-conditioned(>1e8) /ill-conditioned(>1e10) as a flag to scale/precondition before trusting the solve.
Common pitfalls & rationalizations
| 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. |
Security
Input Validation
dx,dt, andsafetyare validated as finite positive numbers before any computation;velocity,diffusivity, andreaction_rate, when supplied, are validated as finite--dimensionsis restricted to{1, 2, 3}(cfl_checker.py raises and exits 2 otherwise)- Comma-separated eigenvalue lists (
--eigs) are capped at 10,000 entries and validated as finite numbers - Stencil coefficient lists (
--coeffs) are capped at 10,000 entries and validated as finite floats
File Access
matrix_condition.pyreads a single matrix file (.npyor text) specified by--matrix; no directory traversal beyond the given path- Matrix/Jacobian files are rejected if they exceed 500 MB before parsing (matrix_condition.py, stiffness_detector.py)
np.load()is called withallow_pickle=Falseto prevent arbitrary code execution via crafted.npyfiles (matrix_condition.py, stiffness_detector.py)- Scripts write only to stdout (JSON output); no files are created unless the agent explicitly uses the Write tool
Tool Restrictions
- Read: Used to inspect script source, references, and user configuration files
- Bash: Used to execute the four Python analysis scripts (
cfl_checker.py,von_neumann_analyzer.py,matrix_condition.py,stiffness_detector.py) with explicit argument lists - Write: Used to save analysis results or generated reports; writes are scoped to the user's working directory
- Grep/Glob: Used to locate relevant files and search references
Safety Measures
- No
eval(),exec(), or dynamic code generation - All subprocess calls use explicit argument lists (no
shell=True) - Matrix dimension limit (100,000 per side) in matrix_condition.py prevents memory exhaustion
- JSON output mode (
--json) produces structured, parseable results without shell-interpretable content
Limitations
- Explicit schemes only for CFL/Fourier checks (implicit is unconditionally stable)
- Von Neumann analysis assumes linear, constant-coefficient, periodic BCs
- Stiffness detection requires eigenvalue estimates from user. The
stiffverdict is based on scale separation among genuinely decaying modes (Re(λ) < 0); the magnitude ratio is still reported asstiffness_ratio. Imaginary-axis-dominated spectra (oscillatory/advection/Hamiltonian) are flagged viaimag_dominatedand awarning, 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
references/stability_criteria.md- Decision thresholds and formulasreferences/common_pitfalls.md- Frequent failure modes and fixesreferences/scheme_catalog.md- Stability properties of common schemes
Version History
- v1.2.2 (2026-06-24): Added a "Verification checklist" (7 evidence-based items tied to the four scripts' JSON outputs) and a "Common pitfalls & rationalizations" table (6 rows) to make stability verdicts reproducible and guard against false-confidence shortcuts
- v1.2.0 (2026-06-23): Corrected the phase-field worked example (genuinely unstable Fo=1.0 with D=1.0); fixed eval case 1 Fourier value; reconciled conditioning thresholds and error-message docs with the scripts; real-part-based stiffness classification with imaginary-dominated detection; enforced documented input/file-size/dimension safeguards
- v1.1.0 (2024-12-24): Enhanced documentation, decision guidance, examples
- v1.0.0: Initial release with 4 stability analysis scripts