Linear Solvers
Goal
Provide a universal workflow to select a solver, assess conditioning, and diagnose convergence for linear systems arising in numerical simulations.
Requirements
- Python 3.10+
- NumPy, SciPy (for matrix operations)
- See individual scripts for dependencies
Inputs to Gather
| Input |
Description |
Example |
| Matrix size |
Dimension of system |
n = 1000000 |
| Sparsity |
Fraction of nonzeros |
0.01% |
| Symmetry |
Is A = Aᵀ? |
yes |
| Definiteness |
Is A positive definite? |
yes (SPD) |
| Conditioning |
Estimated condition number |
10⁶ |
Decision Guidance
Solver Selection Flowchart
Is matrix dense and small enough to factor in memory (dense float64
storage n²·8 bytes < ~2 GB, i.e. n ≲ 16000)?
├── YES → Use direct solver (Cholesky/LDLᵀ/LU by symmetry)
└── NO → Is matrix symmetric?
├── YES → Is it positive definite?
│ ├── YES → Use CG with AMG/IC preconditioner
│ └── NO → Use MINRES
└── NO → Is it nearly symmetric?
├── YES → Use BiCGSTAB
└── NO → Use GMRES with ILU/AMG
Quick Reference
| Matrix Type |
Solver |
Preconditioner |
| SPD, sparse |
CG |
AMG, IC |
| Symmetric indefinite |
MINRES |
SPD preconditioner (SSOR, symmetric block-diagonal, or AMG on SPD part) |
| Nonsymmetric |
GMRES, BiCGSTAB |
ILU, AMG |
| Dense |
LU, Cholesky |
None |
| Saddle point |
Schur complement, Uzawa |
Block preconditioner |
Script Outputs (JSON Fields)
| Script |
Key Outputs |
scripts/solver_selector.py |
recommended, alternatives, notes |
scripts/convergence_diagnostics.py |
rate, asymptotic_rate, stagnation, recommended_action |
scripts/sparsity_stats.py |
nnz, density, bandwidth, symmetry |
scripts/preconditioner_advisor.py |
suggested, notes |
scripts/scaling_equilibration.py |
row_scale, col_scale, notes |
scripts/residual_norms.py |
residual_norms, relative_norms, converged |
Workflow
- Characterize matrix - symmetry, definiteness, sparsity
- Analyze sparsity - Run
scripts/sparsity_stats.py
- Select solver - Run
scripts/solver_selector.py
- Choose preconditioner - Run
scripts/preconditioner_advisor.py
- Apply scaling - If ill-conditioned, use
scripts/scaling_equilibration.py
- Monitor convergence - Use
scripts/convergence_diagnostics.py
- Diagnose issues - Check residual history with
scripts/residual_norms.py
Conversational Workflow Example
User: My GMRES solver is stagnating after 50 iterations. The residual drops to 1e-3 then stops improving.
Agent workflow:
- Diagnose convergence:
python3 scripts/convergence_diagnostics.py --residuals 1,0.1,0.01,0.005,0.003,0.002,0.002,0.002 --json
- Check for preconditioning advice:
python3 scripts/preconditioner_advisor.py --matrix-type nonsymmetric --sparse --ill-conditioned --json
- Recommend: Increase restart parameter, try ILU(k) with higher k, or switch to AMG.
Pre-Solve Checklist
CLI Examples
# Analyze sparsity pattern
python3 scripts/sparsity_stats.py --matrix A.npy --json
# Select solver for SPD sparse system
python3 scripts/solver_selector.py --symmetric --positive-definite --sparse --size 1000000 --json
# Get preconditioner recommendation
python3 scripts/preconditioner_advisor.py --matrix-type spd --sparse --json
# Diagnose convergence from residual history
python3 scripts/convergence_diagnostics.py --residuals 1,0.2,0.05,0.01 --json
# Apply scaling
python3 scripts/scaling_equilibration.py --matrix A.npy --symmetric --json
# Compute residual norms
python3 scripts/residual_norms.py --residual 1,0.1,0.01 --rhs 1,0,0 --json
Error Handling
| Error |
Cause |
Resolution |
Matrix file not found |
Invalid path |
Check file exists |
Matrix must be square |
Non-square input |
Verify matrix dimensions |
Residuals must be positive |
Invalid residual data |
Check input format |
Interpretation Guidance
Convergence Rate
convergence_diagnostics.py reports two rates: rate (mean of all per-iteration
residual ratios over the full history) and asymptotic_rate (mean over a short
trailing window). The stagnation flag is driven by asymptotic_rate (> 0.95),
because stagnation is a tail property — early fast drops can hide a flat tail.
Read asymptotic_rate when judging the regime below:
| Asymptotic rate |
Meaning |
Action |
| < 0.1 |
Excellent |
Current setup optimal |
| 0.1 - 0.5 |
Good |
Acceptable for most problems |
| 0.5 - 0.95 |
Slow |
Consider better preconditioner |
| > 0.95 |
Stagnation |
Change solver or preconditioner |
Stagnation Diagnosis
| Pattern |
Likely Cause |
Fix |
| Flat residual |
Poor preconditioner |
Improve preconditioner |
| Oscillating |
Near-singular or indefinite |
Check matrix, try different solver |
| Very slow decay |
Ill-conditioned |
Apply scaling, use AMG |
Verification checklist
Do not trust a solve until each of these is satisfied with a recorded value, not a "looks fine":
Common pitfalls & rationalizations
| Tempting shortcut |
Why it's wrong / what to do |
"The mean rate is low, so it converged." |
rate is the whole-history mean and is dominated by early fast drops; stagnation is a tail property. Read asymptotic_rate and confirm it is below 0.95. |
| "The absolute residual is tiny, so we're done." |
A small absolute norm can be meaningless if the RHS is large or unscaled. Check the relative_norms / relative_value against a physics-scaled --rel-tol. |
| "It's symmetric, so just use CG." |
CG requires symmetric AND positive-definite. A symmetric-indefinite matrix needs MINRES (with an SPD preconditioner); using CG can break down or stall. Confirm definiteness before selecting. |
| "Large system, so factor it directly." |
solver_selector.py gates dense direct solvers on dense float64 storage (n²·8 bytes < ~2 GB, n ≈ 16384); above that a dense Cholesky/LU is infeasible and you must route to an iterative method. |
| "Scaling is just dividing each row by its max." |
One-sided row scaling does not equilibrate. For nonsymmetric matrices derive col_scale from the row-scaled matrix and apply both; for symmetric matrices use the symmetric D A D scale or you destroy symmetry. |
| "GMRES stagnates, so add more iterations." |
A flat tail means the preconditioner or restart length is the problem, not iteration count. Strengthen the preconditioner (higher ILU fill / AMG), increase the restart parameter, or switch methods. |
Security
Input Validation
- All numeric inputs (residuals, tolerances, matrix entries) are validated as finite numbers
- Comma-separated residual/vector inputs are capped at 100,000 entries
- The
solver_selector.py --size parameter is bounded at 10 billion
--matrix-type is validated against a fixed allowlist (spd, symmetric-indefinite, nonsymmetric)
- Boolean flags (
--symmetric, --positive-definite, --sparse, --ill-conditioned) are type-safe argparse flags
File Access
sparsity_stats.py and scaling_equilibration.py read a single matrix file (.npy format) specified by --matrix
np.load() is called with allow_pickle=False to prevent arbitrary code execution via crafted .npy files
- Matrix files are rejected if they exceed 500 MB before any parsing occurs
- Matrix dimension limits (100,000 per dimension) prevent memory exhaustion
- All other scripts read no external files; inputs are provided via CLI arguments
Tool Restrictions
- Read: Used to inspect script source, references, and matrix files
- Write: Used to save analysis results or solver recommendations; writes are scoped to the user's working directory
- Grep/Glob: Used to locate relevant files and search references
- The skill's
allowed-tools excludes Bash to prevent the agent from executing arbitrary commands when processing untrusted matrix files or numeric inputs
Safety Measures
- No
eval(), exec(), or dynamic code generation
- All subprocess calls use explicit argument lists (no
shell=True)
- Reduced tool surface (no Bash) limits the agent to read/write operations only
- JSON output mode produces structured, parseable results without shell-interpretable content
Limitations
- Large dense matrices: Direct solvers may run out of memory
- Highly indefinite: Standard preconditioners may fail
- Saddle-point: Requires specialized block preconditioners
References
references/solver_decision_tree.md - Selection logic
references/preconditioner_catalog.md - Preconditioner options
references/convergence_patterns.md - Diagnosing failures
references/scaling_guidelines.md - Equilibration guidance
Version History
- v1.2.0 (2026-06-23): Fixed asymptotic stagnation detection, dense-feasibility solver gating, saddle-point/small-dense direct-solver routing, equilibrating two-sided scaling, CG iteration-bound table, and doc/eval consistency
- v1.1.0 (2024-12-24): Enhanced documentation, decision guidance, examples
- v1.0.0: Initial release with 6 solver analysis scripts
1---2name: linear-solvers3description: Select and configure linear solvers for Ax=b systems arising in numerical simulations — choose between direct (LU, Cholesky) and iterative (CG, GMRES, BiCGSTAB, MINRES) methods, analyze sparsity patterns and matrix conditioning, recommend preconditioners (AMG, ILU, IC), apply row/column scaling, and diagnose convergence stagnation from residual histories. Use when setting up a linear solve for FEM/FVM assembly, debugging slow or stalled Krylov iterations, choosing a preconditioner for SPD or nonsymmetric systems, or investigating ill-conditioning, even if the user only says "my solver is slow" or "GMRES won't converge."4---56# Linear Solvers78## Goal910Provide a universal workflow to select a solver, assess conditioning, and diagnose convergence for linear systems arising in numerical simulations.1112## Requirements1314- Python 3.10+15- NumPy, SciPy (for matrix operations)16- See individual scripts for dependencies1718## Inputs to Gather1920| Input | Description | Example |21|-------|-------------|---------|22| Matrix size | Dimension of system | `n = 1000000` |23| Sparsity | Fraction of nonzeros | `0.01%` |24| Symmetry | Is A = Aᵀ? | `yes` |25| Definiteness | Is A positive definite? | `yes (SPD)` |26| Conditioning | Estimated condition number | `10⁶` |2728## Decision Guidance2930### Solver Selection Flowchart3132```33Is matrix dense and small enough to factor in memory (dense float6434storage n²·8 bytes < ~2 GB, i.e. n ≲ 16000)?35├── YES → Use direct solver (Cholesky/LDLᵀ/LU by symmetry)36└── NO → Is matrix symmetric?37 ├── YES → Is it positive definite?38 │ ├── YES → Use CG with AMG/IC preconditioner39 │ └── NO → Use MINRES40 └── NO → Is it nearly symmetric?41 ├── YES → Use BiCGSTAB42 └── NO → Use GMRES with ILU/AMG43```4445### Quick Reference4647| Matrix Type | Solver | Preconditioner |48|-------------|--------|----------------|49| SPD, sparse | CG | AMG, IC |50| Symmetric indefinite | MINRES | SPD preconditioner (SSOR, symmetric block-diagonal, or AMG on SPD part) |51| Nonsymmetric | GMRES, BiCGSTAB | ILU, AMG |52| Dense | LU, Cholesky | None |53| Saddle point | Schur complement, Uzawa | Block preconditioner |5455## Script Outputs (JSON Fields)5657| Script | Key Outputs |58|--------|-------------|59| `scripts/solver_selector.py` | `recommended`, `alternatives`, `notes` |60| `scripts/convergence_diagnostics.py` | `rate`, `asymptotic_rate`, `stagnation`, `recommended_action` |61| `scripts/sparsity_stats.py` | `nnz`, `density`, `bandwidth`, `symmetry` |62| `scripts/preconditioner_advisor.py` | `suggested`, `notes` |63| `scripts/scaling_equilibration.py` | `row_scale`, `col_scale`, `notes` |64| `scripts/residual_norms.py` | `residual_norms`, `relative_norms`, `converged` |6566## Workflow67681. **Characterize matrix** - symmetry, definiteness, sparsity692. **Analyze sparsity** - Run `scripts/sparsity_stats.py`703. **Select solver** - Run `scripts/solver_selector.py`714. **Choose preconditioner** - Run `scripts/preconditioner_advisor.py`725. **Apply scaling** - If ill-conditioned, use `scripts/scaling_equilibration.py`736. **Monitor convergence** - Use `scripts/convergence_diagnostics.py`747. **Diagnose issues** - Check residual history with `scripts/residual_norms.py`7576## Conversational Workflow Example7778**User**: My GMRES solver is stagnating after 50 iterations. The residual drops to 1e-3 then stops improving.7980**Agent workflow**:811. Diagnose convergence:82 ```bash83 python3 scripts/convergence_diagnostics.py --residuals 1,0.1,0.01,0.005,0.003,0.002,0.002,0.002 --json84 ```852. Check for preconditioning advice:86 ```bash87 python3 scripts/preconditioner_advisor.py --matrix-type nonsymmetric --sparse --ill-conditioned --json88 ```893. Recommend: Increase restart parameter, try ILU(k) with higher k, or switch to AMG.9091## Pre-Solve Checklist9293- [ ] Confirm matrix symmetry/definiteness94- [ ] Decide direct vs iterative based on size and sparsity95- [ ] Set residual tolerance relative to physics scale96- [ ] Choose preconditioner appropriate to matrix structure97- [ ] Apply scaling/equilibration if needed98- [ ] Track convergence and adjust if stagnation occurs99100## CLI Examples101102```bash103# Analyze sparsity pattern104python3 scripts/sparsity_stats.py --matrix A.npy --json105106# Select solver for SPD sparse system107python3 scripts/solver_selector.py --symmetric --positive-definite --sparse --size 1000000 --json108109# Get preconditioner recommendation110python3 scripts/preconditioner_advisor.py --matrix-type spd --sparse --json111112# Diagnose convergence from residual history113python3 scripts/convergence_diagnostics.py --residuals 1,0.2,0.05,0.01 --json114115# Apply scaling116python3 scripts/scaling_equilibration.py --matrix A.npy --symmetric --json117118# Compute residual norms119python3 scripts/residual_norms.py --residual 1,0.1,0.01 --rhs 1,0,0 --json120```121122## Error Handling123124| Error | Cause | Resolution |125|-------|-------|------------|126| `Matrix file not found` | Invalid path | Check file exists |127| `Matrix must be square` | Non-square input | Verify matrix dimensions |128| `Residuals must be positive` | Invalid residual data | Check input format |129130## Interpretation Guidance131132### Convergence Rate133134`convergence_diagnostics.py` reports two rates: `rate` (mean of all per-iteration135residual ratios over the full history) and `asymptotic_rate` (mean over a short136trailing window). The `stagnation` flag is driven by `asymptotic_rate` (> 0.95),137because stagnation is a tail property — early fast drops can hide a flat tail.138Read `asymptotic_rate` when judging the regime below:139140| Asymptotic rate | Meaning | Action |141|-----------------|---------|--------|142| < 0.1 | Excellent | Current setup optimal |143| 0.1 - 0.5 | Good | Acceptable for most problems |144| 0.5 - 0.95 | Slow | Consider better preconditioner |145| > 0.95 | Stagnation | Change solver or preconditioner |146147### Stagnation Diagnosis148149| Pattern | Likely Cause | Fix |150|---------|--------------|-----|151| Flat residual | Poor preconditioner | Improve preconditioner |152| Oscillating | Near-singular or indefinite | Check matrix, try different solver |153| Very slow decay | Ill-conditioned | Apply scaling, use AMG |154155## Verification checklist156157Do not trust a solve until each of these is satisfied with a recorded value, not a "looks fine":158159- [ ] Recorded `asymptotic_rate` from `convergence_diagnostics.py` and confirmed it is below the 0.95 stagnation threshold (and ideally < 0.5); a low whole-history `rate` alone does not rule out a flat tail.160- [ ] Checked the relative residual from `residual_norms.py` against the physics-scaled `--rel-tol` (default 1e-6), not just the absolute norm; for unscaled RHS use `--require-both` so an undersized `rhs` cannot fake convergence.161- [ ] Confirmed `solver_selector.py` `recommended` matches the actual matrix properties recorded from `sparsity_stats.py` (`symmetry`, and definiteness if known) — e.g. CG only when symmetric AND positive-definite, MINRES for symmetric-indefinite, GMRES/BiCGSTAB for nonsymmetric.162- [ ] For systems flagged ill-conditioned, ran `scaling_equilibration.py` and recorded `row_scale_max/row_scale_min` and `col_scale_max/col_scale_min`; for symmetric matrices used `--symmetric` (D A D) so symmetry is preserved, and applied row_scale THEN col_scale for nonsymmetric two-sided scaling.163- [ ] Reviewed `sparsity_stats.py` `notes`/`zero_rows`/`zero_cols` from `scaling_equilibration.py` — any zero row or column means the system is structurally singular and the scale-of-1 fallback is not a fix.164- [ ] Confirmed the preconditioner from `preconditioner_advisor.py` is admissible for the chosen Krylov method — in particular a MINRES preconditioner must be SPD (an indefinite incomplete LDLᵀ is invalid).165166## Common pitfalls & rationalizations167168| Tempting shortcut | Why it's wrong / what to do |169|-------------------|-----------------------------|170| "The mean `rate` is low, so it converged." | `rate` is the whole-history mean and is dominated by early fast drops; stagnation is a tail property. Read `asymptotic_rate` and confirm it is below 0.95. |171| "The absolute residual is tiny, so we're done." | A small absolute norm can be meaningless if the RHS is large or unscaled. Check the `relative_norms` / `relative_value` against a physics-scaled `--rel-tol`. |172| "It's symmetric, so just use CG." | CG requires symmetric AND positive-definite. A symmetric-indefinite matrix needs MINRES (with an SPD preconditioner); using CG can break down or stall. Confirm definiteness before selecting. |173| "Large system, so factor it directly." | `solver_selector.py` gates dense direct solvers on dense float64 storage (n²·8 bytes < ~2 GB, n ≈ 16384); above that a dense Cholesky/LU is infeasible and you must route to an iterative method. |174| "Scaling is just dividing each row by its max." | One-sided row scaling does not equilibrate. For nonsymmetric matrices derive `col_scale` from the row-scaled matrix and apply both; for symmetric matrices use the symmetric D A D scale or you destroy symmetry. |175| "GMRES stagnates, so add more iterations." | A flat tail means the preconditioner or restart length is the problem, not iteration count. Strengthen the preconditioner (higher ILU fill / AMG), increase the restart parameter, or switch methods. |176177## Security178179### Input Validation180- All numeric inputs (residuals, tolerances, matrix entries) are validated as finite numbers181- Comma-separated residual/vector inputs are capped at 100,000 entries182- The `solver_selector.py` `--size` parameter is bounded at 10 billion183- `--matrix-type` is validated against a fixed allowlist (`spd`, `symmetric-indefinite`, `nonsymmetric`)184- Boolean flags (`--symmetric`, `--positive-definite`, `--sparse`, `--ill-conditioned`) are type-safe argparse flags185186### File Access187- `sparsity_stats.py` and `scaling_equilibration.py` read a single matrix file (`.npy` format) specified by `--matrix`188- `np.load()` is called with `allow_pickle=False` to prevent arbitrary code execution via crafted `.npy` files189- Matrix files are rejected if they exceed 500 MB before any parsing occurs190- Matrix dimension limits (100,000 per dimension) prevent memory exhaustion191- All other scripts read no external files; inputs are provided via CLI arguments192193### Tool Restrictions194- **Read**: Used to inspect script source, references, and matrix files195- **Write**: Used to save analysis results or solver recommendations; writes are scoped to the user's working directory196- **Grep/Glob**: Used to locate relevant files and search references197- The skill's `allowed-tools` excludes `Bash` to prevent the agent from executing arbitrary commands when processing untrusted matrix files or numeric inputs198199### Safety Measures200- No `eval()`, `exec()`, or dynamic code generation201- All subprocess calls use explicit argument lists (no `shell=True`)202- Reduced tool surface (no Bash) limits the agent to read/write operations only203- JSON output mode produces structured, parseable results without shell-interpretable content204205## Limitations206207- **Large dense matrices**: Direct solvers may run out of memory208- **Highly indefinite**: Standard preconditioners may fail209- **Saddle-point**: Requires specialized block preconditioners210211## References212213- `references/solver_decision_tree.md` - Selection logic214- `references/preconditioner_catalog.md` - Preconditioner options215- `references/convergence_patterns.md` - Diagnosing failures216- `references/scaling_guidelines.md` - Equilibration guidance217218## Version History219220- **v1.2.0** (2026-06-23): Fixed asymptotic stagnation detection, dense-feasibility solver gating, saddle-point/small-dense direct-solver routing, equilibrating two-sided scaling, CG iteration-bound table, and doc/eval consistency221- **v1.1.0** (2024-12-24): Enhanced documentation, decision guidance, examples222- **v1.0.0**: Initial release with 6 solver analysis scripts