
Nonlinear Solvers
- 17 installs
- 869 repo stars
- Updated June 8, 2026
- beita6969/scienceclaw
nonlinear-solvers is a skill that helps select and configure nonlinear solvers and diagnose convergence for root-finding, optimization, and least-squares problems.
About
This skill helps select and configure nonlinear solvers for root-finding, optimization, and least-squares problems. A developer characterizes the problem, then runs scripts to recommend a solver, choose a globalization strategy, diagnose Jacobian quality, and analyze convergence. It covers Newton, quasi-Newton, Broyden, and Anderson acceleration. It matters for debugging slow or diverging solvers in scientific simulations.
- Solver selection flowchart for root-finding, optimization, and least-squares
- Six diagnostic scripts: solver selector, convergence, Jacobian, globalization, residuals, step quality
- Guidance on line search vs trust region and convergence-type interpretation
Nonlinear Solvers by the numbers
- 17 all-time installs (skills.sh)
- Ranked #1,286 of 2,065 Data Science & ML skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
nonlinear-solvers capabilities & compatibility
Free; needs Python with NumPy and optionally SciPy.
- Capabilities
- data analysis · debugging
- Use cases
- data analysis · debugging
- Pricing
- Free
What nonlinear-solvers says it does
Select and configure nonlinear solvers for f(x)=0 or min F(x).
Provide a universal workflow to select a nonlinear solver, configure globalization strategies, and diagnose convergence for root-finding, optimization, and least-squares problems.
Switch to trust region with Levenberg-Marquardt regularization, or use Newton-Krylov with better preconditioning.
npx skills add https://github.com/beita6969/scienceclaw --skill nonlinear-solversAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 17 |
|---|---|
| repo stars | ★ 869 |
| Last updated | June 8, 2026 |
| Repository | beita6969/scienceclaw ↗ |
What it does
Select and configure a nonlinear solver (Newton, BFGS, Broyden) and diagnose its convergence.
Who is it for?
Choosing between Newton, quasi-Newton, Broyden, and Anderson methods and diagnosing convergence and Jacobian quality.
Skip if: Non-numerical tasks; it targets nonlinear solver selection and diagnosis.
When should I use this skill?
You need to pick a nonlinear solver or diagnose why one converges slowly or diverges.
What you get
The developer gets a recommended solver, globalization strategy, and a convergence diagnosis.
By the numbers
- 6 diagnostic scripts
- 6-item pre-solve checklist
Files
Nonlinear Solvers
Goal
Provide a universal workflow to select a nonlinear solver, configure globalization strategies, and diagnose convergence for root-finding, optimization, and least-squares problems.
Requirements
- Python 3.8+
- NumPy (for Jacobian diagnostics)
- SciPy (optional, for advanced analysis)
Inputs to Gather
| Input | Description | Example |
|---|---|---|
| Problem type | Root-finding, optimization, least-squares | root-finding |
| Problem size | Number of unknowns | n = 10000 |
| Jacobian availability | Analytic, finite-diff, unavailable | analytic |
| Jacobian cost | Cheap or expensive to compute | expensive |
| Constraints | None, bounds, equality, inequality | none |
| Smoothness | Is objective/residual smooth? | yes |
| Residual history | Sequence of residual norms | 1,0.1,0.01,... |
Decision Guidance
Solver Selection Flowchart
Is Jacobian available and cheap?
├── YES → Problem size?
│ ├── Small (n < 1000) → Newton (full)
│ └── Large (n ≥ 1000) → Newton-Krylov
└── NO → Is objective smooth?
├── YES → Memory limited?
│ ├── YES → L-BFGS or Broyden
│ └── NO → BFGS
└── NO → Anderson acceleration or PicardQuick Reference
| Problem Type | First Choice | Alternative | Globalization |
|---|---|---|---|
| Small root-finding | Newton | Broyden | Line search |
| Large root-finding | Newton-Krylov | Anderson | Trust region |
| Optimization | L-BFGS | BFGS | Wolfe line search |
| Least-squares | Levenberg-Marquardt | Gauss-Newton | Trust region |
| Bound constrained | L-BFGS-B | Trust-region reflective | Projected |
Script Outputs (JSON Fields)
| Script | Key Outputs |
|---|---|
scripts/solver_selector.py | recommended, alternatives, notes |
scripts/convergence_analyzer.py | converged, convergence_type, estimated_rate, diagnosis |
scripts/jacobian_diagnostics.py | condition_number, jacobian_quality, rank_deficient |
scripts/globalization_advisor.py | strategy, line_search_type, trust_region_type, parameters |
scripts/residual_monitor.py | patterns_detected, alerts, recommendations |
scripts/step_quality.py | ratio, step_quality, accept_step, trust_radius_action |
Workflow
1. Characterize problem - Identify type, size, Jacobian availability 2. Select solver - Run scripts/solver_selector.py 3. Choose globalization - Run scripts/globalization_advisor.py 4. Analyze Jacobian - If available, run scripts/jacobian_diagnostics.py 5. Monitor residuals - During solve, use scripts/residual_monitor.py 6. Analyze convergence - Run scripts/convergence_analyzer.py 7. Evaluate steps - For trust region, use scripts/step_quality.py
Conversational Workflow Example
User: My Newton solver for a phase-field simulation is converging very slowly. After 50 iterations, the residual only dropped from 1 to 0.1.
Agent workflow: 1. Analyze convergence:
python3 scripts/convergence_analyzer.py --residuals 1,0.8,0.6,0.5,0.4,0.3,0.2,0.15,0.12,0.1 --json2. Check globalization strategy:
python3 scripts/globalization_advisor.py --problem-type root-finding --jacobian-quality ill-conditioned --previous-failures 0 --json3. Recommend: Switch to trust region with Levenberg-Marquardt regularization, or use Newton-Krylov with better preconditioning.
Pre-Solve Checklist
- [ ] Confirm problem type (root-finding, optimization, least-squares)
- [ ] Assess Jacobian availability and cost
- [ ] Check initial guess quality
- [ ] Set appropriate tolerances
- [ ] Choose globalization strategy
- [ ] Prepare to monitor convergence
CLI Examples
# Select solver for large unconstrained optimization
python3 scripts/solver_selector.py --size 50000 --smooth --memory-limited --json
# Analyze convergence from residual history
python3 scripts/convergence_analyzer.py --residuals 1,0.1,0.01,0.001,0.0001 --tolerance 1e-6 --json
# Diagnose Jacobian quality
python3 scripts/jacobian_diagnostics.py --matrix jacobian.txt --json
# Get globalization recommendation
python3 scripts/globalization_advisor.py --problem-type optimization --jacobian-quality good --json
# Monitor residual patterns
python3 scripts/residual_monitor.py --residuals 1,0.8,0.9,0.7,0.75,0.6 --target-tolerance 1e-8 --json
# Evaluate step quality for trust region
python3 scripts/step_quality.py --predicted-reduction 0.5 --actual-reduction 0.4 --step-norm 0.8 --gradient-norm 1.0 --trust-radius 1.0 --jsonError Handling
| Error | Cause | Resolution |
|---|---|---|
problem_size must be positive | Invalid size | Check problem dimension |
constraint_type must be one of... | Unknown constraint | Use: none, bound, equality, inequality |
residuals must be non-negative | Invalid residual data | Check residual computation |
Matrix file not found | Invalid path | Verify Jacobian file exists |
Interpretation Guidance
Convergence Type
| Type | Meaning | Action |
|---|---|---|
| quadratic | Optimal Newton | Continue, near solution |
| superlinear | Quasi-Newton working | Monitor for stagnation |
| linear | Acceptable | May improve with preconditioner |
| sublinear | Too slow | Change method or formulation |
| stagnated | No progress | Check Jacobian, preconditioner |
| diverged | Increasing residual | Add globalization, check Jacobian |
Jacobian Quality
| Quality | Condition Number | Action |
|---|---|---|
| good | < 10⁶ | Standard Newton works |
| moderately-conditioned | 10⁶ - 10¹⁰ | Consider scaling |
| ill-conditioned | > 10¹⁰ | Use regularization |
| near-singular | ∞ | Reformulate or use LM |
Step Quality (Trust Region)
| Ratio ρ | Quality | Trust Radius |
|---|---|---|
| ρ < 0 | very_poor | Shrink aggressively |
| ρ < 0.25 | marginal | Shrink |
| 0.25 ≤ ρ < 0.75 | good | Maintain |
| ρ ≥ 0.75 | excellent | Expand if at boundary |
Limitations
- No global convergence guarantee: All methods may fail for pathological problems
- Jacobian accuracy: Finite-difference Jacobian may be inaccurate near discontinuities
- Large dense problems: May require specialized solvers not covered here
- Constrained optimization: Complex constraints need SQP or interior point methods
References
references/solver_decision_tree.md- Problem-based solver selectionreferences/method_catalog.md- Method details and parametersreferences/convergence_diagnostics.md- Diagnosing convergence issuesreferences/globalization_strategies.md- Line search and trust region
Version History
- v1.0.0 : Initial release with 6 analysis scripts
Convergence Diagnostics
Guide to analyzing and diagnosing convergence behavior of nonlinear solvers.
Convergence Types
Quadratic Convergence
Definition:
||e_{k+1}|| ≤ C ||e_k||²Log-linear plot behavior:
log||r_k|| vs k:
|
0 |*
| *
-2 | *
| *
-4 | *
| **
-8 | ****
+------------- k
0 2 4 6 8
Residual drops faster each iterationCharacteristics:
- Number of correct digits doubles each iteration
- Typical of Newton's method near solution
- Requires good initial guess and accurate Jacobian
Expected behavior:
| Iteration | Residual | Digits |
|---|---|---|
| 0 | 10⁻¹ | 1 |
| 1 | 10⁻² | 2 |
| 2 | 10⁻⁴ | 4 |
| 3 | 10⁻⁸ | 8 |
| 4 | 10⁻¹⁶ | 16 |
Superlinear Convergence
Definition:
||e_{k+1}|| ≤ C_k ||e_k|| where C_k → 0Log-linear plot behavior:
log||r_k|| vs k:
|
0 |*
| *
-2 | *
| *
-4 | *
| *
-6 | *
| *
-9 | *
+------------- k
0 2 4 6 8
Steady accelerationCharacteristics:
- Faster than linear, slower than quadratic
- Typical of quasi-Newton methods (BFGS, Broyden)
- Usually order 1 < p < 2
Linear Convergence
Definition:
||e_{k+1}|| ≤ ρ ||e_k|| where 0 < ρ < 1 constantLog-linear plot behavior:
log||r_k|| vs k:
|
0 |*
| *
-1 | *
| *
-2 | *
| *
-3 | *
| *
-4 | *
+------------- k
0 2 4 6 8
Constant slope (straight line on log scale)Rate classification:
| Rate ρ | Quality | Iterations for 10⁻⁸ |
|---|---|---|
| < 0.1 | Fast | < 10 |
| 0.1 - 0.5 | Good | 10-40 |
| 0.5 - 0.9 | Slow | 40-200 |
| > 0.9 | Very slow | > 200 |
Sublinear Convergence
Definition:
||e_{k+1}|| ≤ ||e_k|| / k^α for some α > 0Log-linear plot behavior:
log||r_k|| vs k:
|
0 |*
|*
-0.5| *
| *
-1 | *
| **
-1.5| ***
| ****
-2 | *****
+------------------- k
0 10 20 30
Very slow, diminishing returnsCharacteristics:
- Common in ill-conditioned problems
- May indicate near-singularity
- Often needs reformulation or preconditioning
Stagnation
Pattern:
log||r_k|| vs k:
|
0 |*
| *
-2 | *
| *
-3 | ***************
|
|
+------------------- k
0 10 20 30
Residual stops decreasingCommon causes: 1. Tolerance of inner solve too loose 2. Near-singular Jacobian 3. Loss of precision (round-off) 4. Wrong solution branch 5. Inconsistent constraints
Divergence
Pattern:
log||r_k|| vs k:
| *
| *
| *
| *
0 |* *
| * *
-2 | **
|
+------------------- k
0 5 10
Residual increasesCommon causes: 1. Step too large (need line search/trust region) 2. Bad initial guess 3. Jacobian inaccurate or wrong sign 4. Problem has no solution 5. Numerical overflow
Diagnostic Procedures
Step 1: Plot Residual History
Always start by plotting log||r_k|| vs iteration k.
What to look for:
Pattern → Diagnosis
─────────────────────────────
Straight line → Linear convergence (estimate ρ from slope)
Curving down → Superlinear/quadratic
Curving up → Sublinear or approaching stagnation
Flat → Stagnated
Increasing → Diverging
Oscillating → Step size issues or saddle pointStep 2: Compute Convergence Rate
For linear convergence:
# Estimate linear convergence rate
rates = [r[k+1] / r[k] for k in range(len(r)-1)]
avg_rate = geometric_mean(rates)For superlinear/quadratic:
# Look at log-log behavior
log_r = [log(r_k) for r_k in r]
# For quadratic: log(r_{k+1}) ≈ 2 * log(r_k) + const
ratios = [log_r[k+1] / log_r[k] for k in range(len(r)-2)]
# ratios ≈ 2 suggests quadraticStep 3: Check for Common Issues
Oscillation detection:
# Count sign changes in residual differences
oscillations = sum(1 for k in range(len(r)-2)
if (r[k+1]-r[k]) * (r[k+2]-r[k+1]) < 0)
if oscillations > len(r) / 3:
print("Oscillating - check step size or Jacobian")Stagnation detection:
# Check if recent residuals are nearly constant
recent = r[-5:]
rel_change = (max(recent) - min(recent)) / max(recent)
if rel_change < 0.01:
print("Stagnated - check preconditioner or Jacobian accuracy")Rate Estimation Methods
Ratio Method (Linear Rate)
ρ ≈ r_{k+1} / r_kAverage over several iterations:
ρ ≈ (r_n / r_0)^(1/n)Log-Log Method (Order Estimation)
For convergence order p where ||e_{k+1}|| ≈ C ||e_k||^p:
log||e_{k+1}|| ≈ log(C) + p * log||e_k||Estimate p from slope of log||e_{k+1}|| vs log||e_k||:
p ≈ (log||e_{k+1}|| - log||e_k||) / (log||e_k|| - log||e_{k-1}||)Using Residuals as Error Proxy
When exact error unknown, use residual ratios:
p ≈ log(r_{k+1}/r_k) / log(r_k/r_{k-1})Note: This assumes ||r|| ~ ||e||, which holds near the solution.
Troubleshooting Table
Slow Linear Convergence (ρ > 0.5)
| Cause | Indicator | Fix |
|---|---|---|
| Poor preconditioner | High Krylov iterations | Better preconditioner |
| Ill-conditioned problem | Large condition number | Scaling, better formulation |
| Inexact Newton too loose | Inner residual high | Tighten forcing sequence |
| Far from solution | Large initial residual | Better initial guess |
Stagnation
| Cause | Indicator | Fix |
|---|---|---|
| Inner solve tolerance | Residual stuck at ηr_0 | Decrease η |
| Near-singular Jacobian | Condition number huge | Regularization |
| Round-off limitation | Residual ≈ machine ε × scale | Problem solved |
| Wrong formulation | Constraints violated | Check problem setup |
Divergence
| Cause | Indicator | Fix |
|---|---|---|
| No globalization | First step increases r | Add line search |
| Bad Jacobian | Compare to finite diff | Fix Jacobian code |
| Wrong sign | Jacobian has wrong sign | Check derivatives |
| No solution | Physical insight | Reformulate problem |
Oscillation
| Cause | Indicator | Fix |
|---|---|---|
| Overshoot | Step size > optimal | Reduce step, add damping |
| Saddle point | Jacobian indefinite | Trust region, different direction |
| Coupled oscillation | Physics coupling | Under-relaxation |
Practical Convergence Criteria
Standard Criteria
Absolute: ||f(x_k)|| < τ_a
Relative: ||f(x_k)|| < τ_r ||f(x_0)||
Step: ||x_{k+1} - x_k|| < τ_x (1 + ||x_k||)
Combined: Absolute OR RelativeRecommended Tolerances
| Problem Type | τ_a | τ_r |
|---|---|---|
| Engineering | 1e-6 | 1e-4 |
| Scientific | 1e-10 | 1e-8 |
| Inner solve | 1e-3 × outer | 1e-2 |
Safeguards
Max iterations: Prevent infinite loops
Min step: ||δ|| > 1e-14 × ||x|| (progress check)
Function value: For optimization, F decreases
Gradient: For optimization, ||∇F|| smallConvergence Monitoring Example
Healthy Newton Convergence
Iter ||r|| ||r||/||r_0|| Rate
---- ----- ------------- ----
0 1.0e+00 1.0e+00 -
1 2.3e-01 2.3e-01 0.23
2 1.1e-02 1.1e-02 0.05
3 3.2e-05 3.2e-05 0.003
4 8.1e-11 8.1e-11 3e-6
5 1.2e-16 1.2e-16 converged
Analysis: Quadratic convergence established after iteration 2Problematic Convergence
Iter ||r|| ||r||/||r_0|| Rate Alert
---- ----- ------------- ---- -----
0 1.0e+00 1.0e+00 -
1 8.5e-01 8.5e-01 0.85 slow
2 7.3e-01 7.3e-01 0.86 slow
3 6.2e-01 6.2e-01 0.85 STAGNATING
4 5.4e-01 5.4e-01 0.87
5 4.6e-01 4.6e-01 0.85
Analysis: Linear convergence with rate ≈ 0.85
Action: Improve preconditioner, check Jacobian accuracyDivergent Case
Iter ||r|| ||r||/||r_0|| Alert
---- ----- ------------- -----
0 1.0e+00 1.0e+00
1 3.2e+00 3.2e+00 INCREASING
2 1.5e+01 1.5e+01 DIVERGING
3 8.7e+02 8.7e+02 ABORT
Analysis: Divergence from iteration 1
Action: Add line search, check Jacobian, try smaller stepGlobalization Strategies
Guide to line search, trust region, and damping strategies for nonlinear solvers.
Overview
Why Globalization?
Newton's method converges quadratically near the solution, but may:
- Diverge if initial guess is far
- Overshoot if step is too large
- Fail on singular or near-singular Jacobians
Globalization ensures convergence from "any" starting point.
Strategy Comparison
| Aspect | Line Search | Trust Region |
|---|---|---|
| Step control | Scale full Newton step | Constrain step norm |
| Implementation | Simpler | More robust |
| Cost per iter | Multiple f evaluations | One subproblem solve |
| Best for | Smooth, well-conditioned | Ill-conditioned, near-singular |
| Typical methods | Armijo, Wolfe | Dogleg, Steihaug |
Line Search Methods
Backtracking Line Search
Algorithm:
Given: step p_k, initial α = 1, ρ ∈ (0,1), c ∈ (0,1)
while f(x_k + α p_k) > f(x_k) + c α ∇f^T p_k:
α = ρ α
x_{k+1} = x_k + α p_kParameters:
| Parameter | Typical | Range | Notes |
|---|---|---|---|
| c | 1e-4 | (0, 0.5) | Sufficient decrease |
| ρ | 0.5 | (0.1, 0.9) | Backtrack factor |
| α_min | 1e-10 | - | Safety bound |
| max_backtracks | 20 | 10-50 | Iteration limit |
Advantages:
- Simple to implement
- Low overhead per backtrack
- Works for most problems
Disadvantages:
- May take many backtracks
- No curvature information
- Can be inefficient for quasi-Newton
Armijo Condition
The Armijo (sufficient decrease) condition:
f(x_k + α p_k) ≤ f(x_k) + c₁ α ∇f(x_k)^T p_kGeometric interpretation:
f(x)
|
| * Armijo condition requires staying
| / \ below the dashed line
|/ \____
|-------- αWolfe Conditions
Strong Wolfe conditions:
Sufficient decrease: f(x_k + α p_k) ≤ f(x_k) + c₁ α ∇f^T p_k
Curvature: |∇f(x_k + α p_k)^T p_k| ≤ c₂ |∇f(x_k)^T p_k|Weak Wolfe (curvature):
∇f(x_k + α p_k)^T p_k ≥ c₂ ∇f(x_k)^T p_kParameters:
| Parameter | Typical | Range | Notes |
|---|---|---|---|
| c₁ | 1e-4 | (0, 0.5) | Sufficient decrease |
| c₂ (Newton) | 0.9 | (c₁, 1) | Less restrictive |
| c₂ (quasi-Newton) | 0.5 | (c₁, 1) | More restrictive for BFGS |
| c₂ (CG) | 0.1 | (c₁, 0.5) | Very restrictive |
When to use Wolfe:
- BFGS and L-BFGS (curvature condition ensures valid update)
- Conjugate gradient methods
- When backtracking is expensive
Interpolation Line Search
Quadratic interpolation: Given f(0), f'(0), f(α):
α_new = -f'(0) α² / (2 (f(α) - f(0) - f'(0) α))Cubic interpolation: Given f(α₀), f(α₁), f'(0):
Better approximation using two function values and derivativeAdvantages:
- Fewer function evaluations than backtracking
- Better estimate of optimal step
Safeguards:
if α_new < 0.1 α or α_new > 0.9 α:
α_new = 0.5 α # Bisection fallbackTrust Region Methods
Basic Trust Region Framework
Subproblem:
Minimize m_k(p) = f_k + ∇f_k^T p + ½ p^T B_k p
subject to ||p|| ≤ Δ_kAlgorithm:
1. Compute step p_k by solving subproblem
2. Compute ratio ρ_k = (f(x_k) - f(x_k + p_k)) / (m_k(0) - m_k(p_k))
3. Update trust radius based on ρ_k:
- ρ_k < 0.25: shrink Δ
- ρ_k > 0.75 and ||p_k|| = Δ_k: expand Δ
- otherwise: keep Δ
4. If ρ_k > η: accept step x_{k+1} = x_k + p_kParameters:
| Parameter | Typical | Range | Notes |
|---|---|---|---|
| Δ_0 | 1.0 | (0.1, 10) | Initial radius |
| Δ_max | 100 | > Δ_0 | Maximum radius |
| η | 0.1 | (0, 0.25) | Accept threshold |
| η₁ | 0.25 | (0, 0.5) | Shrink threshold |
| η₂ | 0.75 | (0.5, 1) | Expand threshold |
| γ₁ | 0.25 | (0, 0.5) | Shrink factor |
| γ₂ | 2.0 | (1, 4) | Expand factor |
Cauchy Point
The Cauchy point minimizes along steepest descent:
p_C = -α_C ∇f where α_C minimizes m_k(-α ∇f) for ||−α∇f|| ≤ ΔComputation:
If ∇f^T B ∇f ≤ 0: (negative curvature)
α_C = Δ / ||∇f||
Else:
α_C = min(||∇f||² / (∇f^T B ∇f), Δ / ||∇f||)
p_C = -α_C ∇fProperties:
- Always well-defined
- Provides sufficient decrease guarantee
- Usually not optimal step
Dogleg Method
Combines Cauchy and Newton steps:
1. Compute Cauchy point p_C
2. Compute Newton step p_N = -B^{-1} ∇f
3. If ||p_N|| ≤ Δ: return p_N (Newton step in region)
4. If ||p_C|| ≥ Δ: return scaled p_C (Cauchy on boundary)
5. Otherwise: interpolate between p_C and p_N to hit boundaryThe dogleg path:
p_N *
/
/
/
/ <- leg 2
*----
p_C ^
|
leg 1
|
-----O (origin)Advantages:
- Simple closed-form solution
- Works well when B is positive definite
- Good balance of cost and quality
Disadvantages:
- Requires B positive definite
- May not be optimal for indefinite B
Steihaug-CG (Truncated Conjugate Gradient)
For large-scale or indefinite problems:
1. Start CG iteration for B p = -∇f
2. If direction of negative curvature detected:
- Extend current direction to trust region boundary
3. If CG step exits trust region:
- Interpolate to boundary
4. If converged within region:
- Return CG solutionAdvantages:
- Handles indefinite B
- Efficient for large problems
- Exploits Hessian structure
Disadvantages:
- More complex implementation
- May terminate early
Levenberg-Marquardt (Damped Newton)
Modified Newton step:
(J^T J + λ I) p = -J^T r (least-squares)
(B + λ I) p = -∇f (general optimization)λ interpretation:
- λ = 0: Pure Newton/Gauss-Newton
- λ → ∞: Steepest descent
- λ interpolates between them
λ update strategy:
ρ = actual_reduction / predicted_reduction
if ρ < 0.25:
λ = λ × 4
elif ρ > 0.75:
λ = λ / 2Advantages:
- Regularizes singular/ill-conditioned Jacobian
- Smooth transition from steepest descent to Newton
- Standard for nonlinear least-squares
Damping Strategies
Constant Damping
Simple damped Newton:
x_{k+1} = x_k - ω J^{-1} f(x_k)Typical ω values:
| Situation | ω | Notes |
|---|---|---|
| Initial iterations | 0.5-0.7 | Avoid overshoot |
| Near solution | 1.0 | Full Newton |
| Very nonlinear | 0.1-0.3 | Conservative |
Adaptive Damping
Increase when successful, decrease on failure:
if ||f(x_{k+1})|| < ||f(x_k)||:
ω = min(1.0, ω × 1.1) # Gradually increase
else:
ω = ω × 0.5 # Reduce on failureUnder-relaxation for Coupled Systems
For multiphysics coupling:
x_1^{n+1} = x_1^n + ω_1 (x̃_1 - x_1^n)
x_2^{n+1} = x_2^n + ω_2 (x̃_2 - x_2^n)Aitken acceleration:
ω_{k+1} = ω_k × (r_k^T (r_k - r_{k-1})) / ||r_k - r_{k-1}||²Parameter Selection Guidelines
Line Search Selection
| Problem Type | Recommended | Parameters |
|---|---|---|
| Newton, root-finding | Armijo backtracking | c=1e-4, ρ=0.5 |
| BFGS optimization | Wolfe | c₁=1e-4, c₂=0.9 |
| L-BFGS large-scale | Wolfe with interpolation | c₁=1e-4, c₂=0.9 |
| CG optimization | Strong Wolfe | c₁=1e-4, c₂=0.1 |
Trust Region Selection
| Problem Type | Recommended | Initial Δ |
|---|---|---|
| Small, well-conditioned | Dogleg | 1.0 |
| Large scale | Steihaug-CG | 1.0 |
| Near-singular Jacobian | LM | 1.0 with λ=0.01 |
| Least-squares | LM | Based on problem scale |
Switching Strategy
When to switch from line search to trust region:
- Multiple failed line searches
- Oscillating residuals
- Very ill-conditioned Jacobian
- Near-singular system detected
Practical Recommendations
Starting Points
1. Conservative start:
- Trust region: Δ₀ = 0.1 × ||x₀||
- Line search: α₀ = 0.5 (not full Newton)
2. Aggressive start:
- Trust region: Δ₀ = 1.0
- Line search: α₀ = 1.0 (full Newton)
Monitoring
Track these quantities:
- ρ = actual/predicted reduction (trust region)
- α = step size taken (line search)
- ||p|| / Δ (fraction of trust region used)
- ||∇f|| (gradient norm)
- ||f|| (residual/objective)Common Adjustments
| Observation | Adjustment |
|---|---|
| Many failed steps | Increase c₁, decrease Δ₀ |
| Very small steps | Check Jacobian accuracy |
| Always at boundary | Increase Δ_max |
| Slow decrease | Improve preconditioner |
Failure Recovery
1. Failed line search:
- Try trust region instead
- Check Jacobian sign
- Reduce initial α
2. Failed trust region:
- Decrease Δ more aggressively
- Add LM regularization
- Check for local minimum
3. Repeated failures:
- Problem may be infeasible
- Try different formulation
- Use continuation methodsNonlinear Solver Method Catalog
Comprehensive catalog of nonlinear solver methods with parameters and guidance.
Newton-Type Methods
Newton's Method (Full Newton)
Update formula:
x_{k+1} = x_k - J(x_k)^{-1} f(x_k)Requirements:
- Jacobian J(x) = ∂f/∂x available
- J(x) nonsingular
Convergence:
- Quadratic near solution: ||e_{k+1}|| ≤ C ||e_k||²
- Requires good initial guess
Parameters:
| Parameter | Typical Value | Notes |
|---|---|---|
| max_iter | 50-100 | Depends on problem |
| tol_abs | 1e-10 | Absolute residual tolerance |
| tol_rel | 1e-8 | Relative tolerance |
When to use:
- Small to medium problems (n < 5000)
- Jacobian cheap to compute and factor
- Quadratic convergence needed
When to avoid:
- Large problems (use Newton-Krylov)
- Expensive Jacobian (use modified Newton)
- Far from solution (add globalization)
Modified Newton
Update formula:
x_{k+1} = x_k - J(x_0)^{-1} f(x_k) (frozen Jacobian)
x_{k+1} = x_k - J(x_m)^{-1} f(x_k) (recompute every m steps)Convergence:
- Linear when Jacobian frozen
- Faster than Picard if Jacobian is good
Parameters:
| Parameter | Typical Value | Notes |
|---|---|---|
| update_frequency | 3-5 | Iterations between Jacobian updates |
| max_frozen_steps | 10 | Force update after this many |
When to use:
- Jacobian expensive to compute
- Jacobian changes slowly
- Initial iterations where exact Jacobian less critical
Inexact Newton
Idea: Solve J(x_k) δ = -f(x_k) approximately
Inner solve criterion:
||J(x_k) δ_k + f(x_k)|| ≤ η_k ||f(x_k)||Forcing sequence η_k:
| Choice | η_k | Convergence |
|---|---|---|
| Constant | η = 0.1 | Linear |
| Type 1 | min(0.5, | |
| Type 2 | min(0.5, | |
| Eisenstat-Walker | Safeguarded version | Practical choice |
When to use:
- Large problems where exact solve too expensive
- Combined with Krylov inner solver (Newton-Krylov)
Newton-Krylov Methods
Idea: Use Krylov method (GMRES, BiCGSTAB) for inner linear solve
Key methods:
| Inner solver | Best for |
|---|---|
| GMRES | General nonsymmetric Jacobian |
| BiCGSTAB | When GMRES memory is issue |
| CG | Symmetric positive definite Jacobian |
| MINRES | Symmetric indefinite |
Parameters:
| Parameter | Typical Value | Notes |
|---|---|---|
| inner_tol | 1e-3 to 1e-6 | Decreases as outer converges |
| max_krylov | 30-100 | GMRES restart for memory |
| preconditioner | ILU, AMG | Critical for performance |
Jacobian-free variant:
J(x) v ≈ (f(x + εv) - f(x)) / εwhere ε ≈ √(machine_eps) × ||x|| / ||v||
When to use:
- Large-scale problems (n > 10000)
- Jacobian too expensive to form/store
- Sparse structure can be exploited
Quasi-Newton Methods
BFGS (Broyden-Fletcher-Goldfarb-Shanno)
Update formula (for B ≈ Hessian):
s_k = x_{k+1} - x_k
y_k = ∇F(x_{k+1}) - ∇F(x_k)
B_{k+1} = B_k - (B_k s_k)(B_k s_k)^T / (s_k^T B_k s_k) + y_k y_k^T / (y_k^T s_k)Properties:
- Maintains positive definiteness if B_0 is PD and y_k^T s_k > 0
- Superlinear convergence on smooth convex problems
- Self-correcting
Parameters:
| Parameter | Typical Value | Notes |
|---|---|---|
| B_0 | Identity | Initial Hessian approximation |
| skip_update | y^T s < 1e-10 | Skip if curvature info poor |
| damped_update | true | Modify for robustness |
When to use:
- Optimization when Hessian unavailable
- Moderate problem size (n < 1000)
- Smooth, convex objectives
L-BFGS (Limited-memory BFGS)
Idea: Store only last m pairs (s_k, y_k), reconstruct H_k v implicitly
Two-loop recursion:
Algorithm for computing H_k ∇F(x_k):
1. q = ∇F
2. For i = k-1, ..., k-m: α_i = ρ_i s_i^T q; q = q - α_i y_i
3. r = H_0 q
4. For i = k-m, ..., k-1: β = ρ_i y_i^T r; r = r + (α_i - β) s_i
5. Return rParameters:
| Parameter | Typical Value | Notes |
|---|---|---|
| m | 5-20 | Memory pairs stored |
| H_0 | γ_k I | Scale: γ_k = y_{k-1}^T s_{k-1} / y_{k-1}^T y_{k-1} |
Memory: O(mn) vs O(n²) for full BFGS
When to use:
- Large-scale optimization (n > 1000)
- Memory constrained
- Smooth objectives
Broyden's Method (Good and Bad)
Broyden's "good" update (for J ≈ Jacobian):
J_{k+1} = J_k + (y_k - J_k s_k) s_k^T / (s_k^T s_k)Broyden's "bad" update:
J_{k+1}^{-1} = J_k^{-1} + (s_k - J_k^{-1} y_k) y_k^T / (y_k^T y_k)Properties:
- Superlinear convergence for root-finding
- Does not maintain symmetry or positive definiteness
- "Good" is better for most problems
When to use:
- Root-finding without exact Jacobian
- Moderate problem size
- Jacobian is nearly constant
SR1 (Symmetric Rank-1)
Update formula:
B_{k+1} = B_k + (y_k - B_k s_k)(y_k - B_k s_k)^T / ((y_k - B_k s_k)^T s_k)Properties:
- Maintains symmetry
- Does NOT maintain positive definiteness
- Can capture indefinite Hessian
When to use:
- Saddle point problems
- When negative curvature information needed
- Often combined with trust region
Fixed-Point Methods
Picard Iteration (Fixed-Point)
Form: Rewrite f(x) = 0 as x = g(x)
Update:
x_{k+1} = g(x_k)Convergence:
- Linear: ||e_{k+1}|| ≤ L ||e_k|| where L = ||g'(x*)||
- Converges if L < 1
When to use:
- Natural fixed-point form available
- Stability more important than speed
- Initial guess may be far from solution
Anderson Acceleration
Idea: Accelerate fixed-point iteration using history
Algorithm:
1. Compute g_k = g(x_k)
2. Define f_k = g_k - x_k (residual)
3. Minimize ||Σ α_i f_{k-i}||² subject to Σ α_i = 1
4. x_{k+1} = Σ α_i g_{k-i}Parameters:
| Parameter | Typical Value | Notes |
|---|---|---|
| m | 3-10 | History depth |
| beta | 1.0 | Mixing parameter |
| regularization | 1e-10 | For least-squares solve |
Properties:
- Transforms linear convergence to superlinear (often)
- Robust when simple acceleration fails
- Works well for multiphysics coupling
When to use:
- Accelerating Picard iteration
- Coupled multiphysics problems
- When Jacobian unavailable or ill-conditioned
Specialized Methods
Levenberg-Marquardt
For least-squares: min ||r(x)||²
Update: Solve
(J^T J + λ D^T D) δ = -J^T rwhere D is scaling (often diagonal of J^T J or identity)
λ strategy:
| Ratio ρ | Action |
|---|---|
| ρ < 0.25 | Increase λ (more gradient-like) |
| ρ > 0.75 | Decrease λ (more Newton-like) |
| 0.25 ≤ ρ ≤ 0.75 | Keep λ |
Parameters:
| Parameter | Typical Value | Notes |
|---|---|---|
| λ_init | 1e-3 | Initial damping |
| λ_min | 1e-10 | Minimum damping |
| λ_max | 1e10 | Maximum damping |
| factor_up | 10 | Increase factor |
| factor_down | 10 | Decrease factor |
When to use:
- Nonlinear least-squares problems
- Small to medium problems
- Data fitting, parameter estimation
Gauss-Newton
Same as Levenberg-Marquardt with λ = 0:
J^T J δ = -J^T rProperties:
- Fast when residual is small at solution
- May fail when residual is large
- Requires J^T J invertible
When to use:
- Zero-residual or small-residual problems
- Well-conditioned J
- Combined with line search for robustness
Dogleg Method
For trust region: Combine Cauchy point and Newton step
1. Cauchy point: p_C = -α_C ∇F where α_C = ||∇F||² / (∇F^T B ∇F)
2. Newton step: p_N = -B^{-1} ∇F
3. Dogleg path: interpolate between origin, p_C, and p_N
4. Find intersection with trust region boundaryWhen to use:
- Trust region subproblem
- When exact subproblem solve too expensive
- Positive definite Hessian approximation
Steihaug-CG
For large trust region subproblem: Solve using CG
1. Start CG iteration for B p = -∇F
2. If direction of negative curvature, go to boundary
3. If CG step exits trust region, go to boundary
4. Otherwise, continue CG until convergenceWhen to use:
- Large-scale trust region
- Truncated Newton methods
- When B may be indefinite
Parameter Guidelines
Convergence Tolerances
| Problem Type | Absolute Tol | Relative Tol |
|---|---|---|
| Engineering | 1e-6 to 1e-8 | 1e-4 to 1e-6 |
| Scientific | 1e-10 to 1e-12 | 1e-8 to 1e-10 |
| Financial | 1e-8 to 1e-10 | 1e-6 to 1e-8 |
| Machine precision | 1e-14 | 1e-12 |
Maximum Iterations
| Method | Typical max_iter |
|---|---|
| Newton | 20-50 |
| Quasi-Newton | 50-200 |
| Anderson | 100-500 |
| Picard | 100-1000 |
Line Search Parameters
| Parameter | Conservative | Aggressive |
|---|---|---|
| c1 (Armijo) | 1e-4 | 1e-2 |
| c2 (Wolfe) | 0.9 | 0.5 |
| max_backtracks | 20 | 10 |
| α_init | 0.5 | 1.0 |
Trust Region Parameters
| Parameter | Conservative | Aggressive |
|---|---|---|
| Δ_init | 0.1 | 1.0 |
| Δ_max | 10 | 100 |
| η_1 | 0.1 | 0.25 |
| η_2 | 0.9 | 0.75 |
| γ_1 | 0.25 | 0.5 |
| γ_2 | 2.5 | 2.0 |
Nonlinear Solver Decision Tree
Comprehensive decision guide for selecting nonlinear solvers for f(x)=0 or min F(x).
Problem Classification
Key Properties to Determine
| Property | How to Check | Impact |
|---|---|---|
| Problem type | Root-finding, optimization, least-squares | Determines solver class |
| Jacobian availability | Analytic vs finite-difference | Newton vs quasi-Newton |
| Problem size | Number of unknowns | Memory and algorithm choice |
| Smoothness | Continuous derivatives | Enables fast convergence |
| Constraints | Bounds, equalities, inequalities | Specialized methods needed |
| Hessian SPD | Optimization: F''(x) > 0 | BFGS maintains this property |
Quick Classification
Problem type:
├── f(x) = 0 (root-finding/nonlinear equations)
├── min F(x) (unconstrained optimization)
├── min F(x) s.t. g(x) = 0 (equality constrained)
├── min F(x) s.t. l ≤ x ≤ u (bound constrained)
└── min ||r(x)||² (nonlinear least-squares)Primary Decision Tree
START: Need to solve nonlinear problem
│
├─ What type of problem?
│ │
│ ├─ ROOT-FINDING (f(x) = 0)
│ │ │
│ │ ├─ Is analytic Jacobian available?
│ │ │ │
│ │ │ ├── YES, cheap to compute
│ │ │ │ ├── Small problem (n < 1000) → Newton (full)
│ │ │ │ ├── Large problem → Newton-Krylov (GMRES/BiCGSTAB)
│ │ │ │ └── Sparse Jacobian → Newton-Krylov with ILU
│ │ │ │
│ │ │ ├── YES, expensive to compute
│ │ │ │ ├── Modified Newton (reuse Jacobian)
│ │ │ │ └── Broyden update
│ │ │ │
│ │ │ └── NO (finite-diff or unavailable)
│ │ │ ├── Smooth problem → Broyden (good/bad)
│ │ │ ├── Fixed-point form → Anderson acceleration
│ │ │ └── Very large → Newton-Krylov (matrix-free)
│ │ │
│ │ └─ Convergence issues?
│ │ ├── Diverging → Add line search or trust region
│ │ ├── Stagnating → Better preconditioner
│ │ └── Oscillating → Reduce step, add damping
│ │
│ ├─ UNCONSTRAINED OPTIMIZATION (min F(x))
│ │ │
│ │ ├─ Is Hessian available?
│ │ │ │
│ │ │ ├── YES → Newton with trust region
│ │ │ │ └── Large problem → Truncated Newton (CG)
│ │ │ │
│ │ │ └── NO → Use quasi-Newton
│ │ │ ├── Moderate size → BFGS
│ │ │ └── Large problem → L-BFGS
│ │ │
│ │ └─ Is objective smooth?
│ │ ├── YES → Standard quasi-Newton
│ │ └── NO → Subgradient methods, bundle methods
│ │
│ ├─ CONSTRAINED OPTIMIZATION
│ │ │
│ │ ├─ Bound constraints only
│ │ │ ├── Smooth → L-BFGS-B
│ │ │ └── General → Trust-region reflective
│ │ │
│ │ ├─ Equality constraints
│ │ │ ├── Few constraints → SQP
│ │ │ └── Many constraints → Augmented Lagrangian
│ │ │
│ │ └── Inequality constraints
│ │ ├── Smooth → SQP or Interior Point
│ │ └── Nonsmooth → Penalty methods
│ │
│ └─ NONLINEAR LEAST-SQUARES (min ||r(x)||²)
│ │
│ ├─ Is Jacobian of r(x) available?
│ │ ├── YES → Gauss-Newton or Levenberg-Marquardt
│ │ └── NO → Variable projection or L-BFGS
│ │
│ └─ Zero residual problem?
│ ├── YES → May converge faster (quadratic near solution)
│ └── NO → LM more robustMethod Selection by Problem Type
Root-Finding (f(x) = 0)
| Condition | Method | Notes |
|---|---|---|
| Small, Jacobian available | Newton | Quadratic convergence |
| Large, Jacobian available | Newton-Krylov | Matrix-free inner solve |
| Jacobian expensive | Modified Newton | Reuse J for k steps |
| No Jacobian, smooth | Broyden | Superlinear convergence |
| Fixed-point form | Anderson acceleration | Accelerates Picard |
| Very large, sparse | Newton-Krylov + ILU | Preconditioned |
Unconstrained Optimization (min F(x))
| Condition | Method | Notes |
|---|---|---|
| Hessian available | Newton-TR | Quadratic convergence |
| Gradient only | BFGS or L-BFGS | Superlinear |
| Large scale | L-BFGS | O(n) memory |
| Nonsmooth | Subgradient, Bundle | Slower convergence |
Least-Squares (min ||r(x)||²)
| Condition | Method | Notes |
|---|---|---|
| Small residual | Gauss-Newton | Fast near solution |
| Large residual | Levenberg-Marquardt | More robust |
| Very large | Variable projection | Separable structure |
| No Jacobian | L-BFGS on |
Application-Specific Recommendations
Phase-Field Simulations
| Equation Type | Recommended | Notes |
|---|---|---|
| Allen-Cahn | Newton-Krylov | Smooth, can be stiff |
| Cahn-Hilliard | Newton + preconditioner | 4th order, ill-conditioned |
| Crystal plasticity | Modified Newton | Expensive Jacobian |
| Multiphase | Anderson/Picard + acceleration | Fixed-point nature |
Navier-Stokes
| Formulation | Recommended | Notes |
|---|---|---|
| Steady | Newton-Krylov + block precond | Saddle point structure |
| Unsteady (implicit) | Modified Newton | Reuse Jacobian over time |
| Turbulent (RANS) | Under-relaxed Picard | Stability first |
| Large Reynolds | Continuation in Re | Globalization |
Solid Mechanics
| Problem | Recommended | Notes |
|---|---|---|
| Linear elasticity | Direct (if linear) | Not truly nonlinear |
| Hyperelasticity | Newton + line search | May need regularization |
| Plasticity | Modified Newton | Tangent expensive |
| Contact | SQP or Augmented Lagrangian | Inequality constraints |
Failure Modes and Remedies
Common Problems
| Symptom | Likely Cause | Remedy |
|---|---|---|
| No convergence | Poor initial guess | Continuation, better starting point |
| Divergence | Step too large | Line search, trust region |
| Slow convergence | Poor Jacobian | Better preconditioner, exact Jacobian |
| Oscillation | Step size issues | Damping, trust region |
| Stagnation | Singular Jacobian | Regularization, different formulation |
When Newton Fails
1. Check Jacobian accuracy: Compare with finite-difference 2. Add globalization: Line search or trust region 3. Try continuation: Gradually increase difficult parameters 4. Check conditioning: May need scaling or preconditioning 5. Reformulate: Sometimes a different formulation is better
When Quasi-Newton Fails
1. Reset Hessian approximation: Start fresh with identity 2. Switch to BFGS: More stable than SR1/Broyden for optimization 3. Try L-BFGS: Less aggressive updates 4. Use Newton: If Jacobian/Hessian is available
Globalization Summary
| Method | Use When |
|---|---|
| Line search (Armijo) | Standard, root-finding |
| Line search (Wolfe) | Optimization, quasi-Newton |
| Backtracking | Simple, when Armijo sufficient |
| Trust region | Ill-conditioned, near-singular |
| Levenberg-Marquardt | Least-squares |
| Damped Newton | Simple alternative to line search |
Quick Reference Table
| Problem | First Choice | Alternative | Globalization |
|---|---|---|---|
| Small root-finding | Newton | Broyden | Line search |
| Large root-finding | Newton-Krylov | Anderson | Trust region |
| Small optimization | BFGS | Newton | Wolfe line search |
| Large optimization | L-BFGS | Truncated Newton | Trust region |
| Least-squares | Levenberg-Marquardt | Gauss-Newton | Trust region |
| Bound constrained | L-BFGS-B | Trust-region reflective | Projected |
| General constrained | SQP | Interior Point | Merit function |
#!/usr/bin/env python3
"""Analyze residual history to classify convergence type."""
import argparse
import json
import math
import sys
from typing import Any, Dict, List, Optional
def analyze_convergence(
residuals: List[float],
tolerance: float = 1e-10,
) -> Dict[str, Any]:
"""Analyze residual history to classify convergence behavior.
Args:
residuals: List of residual norms from solver iterations
tolerance: Convergence tolerance
Returns:
Dictionary with convergence analysis results
"""
if not residuals:
raise ValueError("residuals must not be empty")
if any(r < 0 for r in residuals):
raise ValueError("residuals must be non-negative")
if tolerance <= 0:
raise ValueError("tolerance must be positive")
n = len(residuals)
final_residual = residuals[-1]
converged = final_residual <= tolerance
# Handle single residual case
if n == 1:
return {
"converged": converged,
"iterations": n,
"final_residual": final_residual,
"convergence_type": "unknown",
"estimated_rate": None,
"diagnosis": "Insufficient data for convergence analysis.",
"recommended_action": "Continue iterations to gather more data.",
}
# Check for divergence
if n >= 2 and residuals[-1] > residuals[0] * 1.1:
return {
"converged": False,
"iterations": n,
"final_residual": final_residual,
"convergence_type": "diverged",
"estimated_rate": None,
"diagnosis": "Residuals are increasing; solver is diverging.",
"recommended_action": "Reduce step size, add damping, or check Jacobian accuracy.",
}
# Check for stagnation
if n >= 3:
recent = residuals[-3:]
if len(recent) == 3:
rel_change = abs(recent[-1] - recent[0]) / (abs(recent[0]) + 1e-30)
if rel_change < 0.01 and not converged:
return {
"converged": False,
"iterations": n,
"final_residual": final_residual,
"convergence_type": "stagnated",
"estimated_rate": None,
"diagnosis": "Residual has stagnated without reaching tolerance.",
"recommended_action": "Improve preconditioner, check for near-singularity, or use different solver.",
}
# Estimate convergence rate from log-residuals
rates = []
for i in range(1, n):
if residuals[i - 1] > 1e-30 and residuals[i] > 1e-30:
ratio = residuals[i] / residuals[i - 1]
if ratio > 0 and ratio < 1:
rates.append(ratio)
if not rates:
convergence_type = "unknown"
estimated_rate = None
else:
avg_rate = sum(rates) / len(rates)
estimated_rate = avg_rate
# Try to detect quadratic convergence by looking at log-log behavior
# For quadratic: log(r_{k+1}) ≈ 2 * log(r_k)
if n >= 4 and all(r > 1e-30 for r in residuals):
log_residuals = [math.log10(r) for r in residuals if r > 0]
if len(log_residuals) >= 4:
# Check if log(r_{k+1}) / log(r_k) ≈ 2 for quadratic
log_ratios = []
for i in range(1, len(log_residuals)):
if abs(log_residuals[i - 1]) > 0.1:
log_ratios.append(log_residuals[i] / log_residuals[i - 1])
if log_ratios and all(1.5 < r < 2.5 for r in log_ratios[-3:]):
convergence_type = "quadratic"
elif avg_rate < 0.3:
convergence_type = "superlinear"
elif avg_rate < 0.9:
convergence_type = "linear"
else:
convergence_type = "sublinear"
else:
if avg_rate < 0.3:
convergence_type = "superlinear"
elif avg_rate < 0.9:
convergence_type = "linear"
else:
convergence_type = "sublinear"
else:
if avg_rate < 0.3:
convergence_type = "superlinear"
elif avg_rate < 0.9:
convergence_type = "linear"
else:
convergence_type = "sublinear"
# Generate diagnosis and recommendation
diagnosis_map = {
"quadratic": "Quadratic convergence indicates optimal Newton behavior.",
"superlinear": "Superlinear convergence; quasi-Newton methods working well.",
"linear": "Linear convergence; rate is acceptable but could be improved.",
"sublinear": "Sublinear convergence; solver is making slow progress.",
"unknown": "Could not determine convergence type.",
}
action_map = {
"quadratic": "Continue with current solver; convergence is optimal.",
"superlinear": "Current setup is effective; monitor for stagnation.",
"linear": "Consider stronger preconditioner or switch to Newton if Jacobian available.",
"sublinear": "Switch to Newton method, improve globalization, or check problem formulation.",
"unknown": "Gather more iterations for analysis.",
}
diagnosis = diagnosis_map.get(convergence_type, "Unknown convergence behavior.")
recommended_action = action_map.get(convergence_type, "Review solver configuration.")
if converged:
recommended_action = "Solver converged successfully."
return {
"converged": converged,
"iterations": n,
"final_residual": final_residual,
"convergence_type": convergence_type,
"estimated_rate": estimated_rate,
"diagnosis": diagnosis,
"recommended_action": recommended_action,
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Analyze convergence from residual history.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"--residuals",
type=str,
required=True,
help="Comma-separated residual values",
)
parser.add_argument(
"--tolerance",
type=float,
default=1e-10,
help="Convergence tolerance",
)
parser.add_argument("--json", action="store_true", help="Emit JSON output")
return parser.parse_args()
def main() -> None:
args = parse_args()
try:
residuals = [float(x.strip()) for x in args.residuals.split(",")]
except ValueError:
print("Error: residuals must be comma-separated numbers", file=sys.stderr)
sys.exit(2)
try:
result = analyze_convergence(
residuals=residuals,
tolerance=args.tolerance,
)
except ValueError as exc:
print(str(exc), file=sys.stderr)
sys.exit(2)
payload: Dict[str, Any] = {
"inputs": {
"residuals": residuals,
"tolerance": args.tolerance,
},
"results": result,
}
if args.json:
print(json.dumps(payload, indent=2, sort_keys=True))
return
print("Convergence analysis")
print(f" converged: {result['converged']}")
print(f" iterations: {result['iterations']}")
print(f" final_residual: {result['final_residual']:.2e}")
print(f" convergence_type: {result['convergence_type']}")
if result["estimated_rate"] is not None:
print(f" estimated_rate: {result['estimated_rate']:.4f}")
print(f" diagnosis: {result['diagnosis']}")
print(f" recommended_action: {result['recommended_action']}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Recommend line search vs trust region globalization strategy."""
import argparse
import json
import sys
from typing import Any, Dict, List
def advise_globalization(
problem_type: str,
jacobian_quality: str,
previous_failures: int,
oscillating_residual: bool,
step_rejection_rate: float,
) -> Dict[str, Any]:
"""Recommend globalization strategy for nonlinear solvers.
Args:
problem_type: Type of problem (root-finding, optimization, least-squares)
jacobian_quality: Quality of Jacobian (good, ill-conditioned, near-singular)
previous_failures: Number of previous solver failures
oscillating_residual: Whether residual history shows oscillation
step_rejection_rate: Fraction of rejected steps (0.0 to 1.0)
Returns:
Dictionary with globalization strategy recommendations
"""
valid_problem_types = {"root-finding", "optimization", "least-squares"}
if problem_type not in valid_problem_types:
raise ValueError(f"problem_type must be one of {valid_problem_types}")
valid_jacobian_quality = {"good", "ill-conditioned", "near-singular"}
if jacobian_quality not in valid_jacobian_quality:
raise ValueError(f"jacobian_quality must be one of {valid_jacobian_quality}")
if previous_failures < 0:
raise ValueError("previous_failures must be non-negative")
if not 0.0 <= step_rejection_rate <= 1.0:
raise ValueError("step_rejection_rate must be between 0.0 and 1.0")
notes: List[str] = []
# Decision logic for strategy selection
use_trust_region = False
# Trust region preferred for ill-conditioned or near-singular Jacobians
if jacobian_quality in {"ill-conditioned", "near-singular"}:
use_trust_region = True
notes.append("Trust region provides better stability for poor Jacobian quality.")
# Trust region preferred for high failure rate
if previous_failures >= 2:
use_trust_region = True
notes.append("Multiple failures suggest trust region for more robust steps.")
# Trust region preferred for oscillating residuals
if oscillating_residual:
use_trust_region = True
notes.append("Oscillating residuals indicate step size issues; trust region helps.")
# Trust region preferred for high step rejection
if step_rejection_rate > 0.3:
use_trust_region = True
notes.append("High step rejection rate favors trust region approach.")
# Problem-type specific adjustments
if problem_type == "least-squares":
use_trust_region = True
notes.append("Trust region is standard for least-squares (Levenberg-Marquardt).")
if use_trust_region:
strategy = "trust-region"
# Select trust region type
if jacobian_quality == "near-singular":
trust_region_type = "Levenberg-Marquardt"
notes.append("LM regularization handles near-singular Jacobian.")
elif problem_type == "optimization":
trust_region_type = "Steihaug-CG"
notes.append("Steihaug-CG efficient for large-scale optimization.")
else:
trust_region_type = "dogleg"
notes.append("Dogleg combines Cauchy and Newton steps effectively.")
# Initial damping parameter
if jacobian_quality == "near-singular":
initial_damping = 1.0
elif jacobian_quality == "ill-conditioned":
initial_damping = 0.1
else:
initial_damping = 0.01
parameters = {
"initial_radius": 1.0,
"max_radius": 100.0,
"eta1": 0.25,
"eta2": 0.75,
"gamma1": 0.25,
"gamma2": 2.0,
}
return {
"strategy": strategy,
"line_search_type": None,
"trust_region_type": trust_region_type,
"initial_damping": initial_damping,
"parameters": parameters,
"notes": notes,
}
else:
strategy = "line-search"
# Select line search type
if problem_type == "optimization":
line_search_type = "Wolfe"
notes.append("Wolfe conditions ensure sufficient decrease and curvature.")
elif previous_failures > 0:
line_search_type = "backtracking"
notes.append("Backtracking is simple and robust after failures.")
else:
line_search_type = "Armijo"
notes.append("Armijo sufficient decrease condition for root-finding.")
# Parameters for line search
if jacobian_quality == "ill-conditioned":
initial_damping = 0.5
notes.append("Starting with reduced step for ill-conditioned problem.")
else:
initial_damping = 1.0
parameters = {
"c1": 1e-4, # Armijo/Wolfe sufficient decrease
"c2": 0.9, # Wolfe curvature condition
"alpha_init": 1.0,
"rho": 0.5, # Backtracking factor
"max_backtracks": 20,
}
return {
"strategy": strategy,
"line_search_type": line_search_type,
"trust_region_type": None,
"initial_damping": initial_damping,
"parameters": parameters,
"notes": notes,
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Recommend globalization strategy for nonlinear solvers.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"--problem-type",
type=str,
required=True,
choices=["root-finding", "optimization", "least-squares"],
help="Type of problem",
)
parser.add_argument(
"--jacobian-quality",
type=str,
default="good",
choices=["good", "ill-conditioned", "near-singular"],
help="Quality of Jacobian",
)
parser.add_argument(
"--previous-failures",
type=int,
default=0,
help="Number of previous solver failures",
)
parser.add_argument(
"--oscillating",
action="store_true",
help="Residual history shows oscillation",
)
parser.add_argument(
"--step-rejection-rate",
type=float,
default=0.0,
help="Fraction of rejected steps (0.0 to 1.0)",
)
parser.add_argument("--json", action="store_true", help="Emit JSON output")
return parser.parse_args()
def main() -> None:
args = parse_args()
try:
result = advise_globalization(
problem_type=args.problem_type,
jacobian_quality=args.jacobian_quality,
previous_failures=args.previous_failures,
oscillating_residual=args.oscillating,
step_rejection_rate=args.step_rejection_rate,
)
except ValueError as exc:
print(str(exc), file=sys.stderr)
sys.exit(2)
payload: Dict[str, Any] = {
"inputs": {
"problem_type": args.problem_type,
"jacobian_quality": args.jacobian_quality,
"previous_failures": args.previous_failures,
"oscillating_residual": args.oscillating,
"step_rejection_rate": args.step_rejection_rate,
},
"results": result,
}
if args.json:
print(json.dumps(payload, indent=2, sort_keys=True))
return
print("Globalization strategy")
print(f" strategy: {result['strategy']}")
if result["line_search_type"]:
print(f" line_search_type: {result['line_search_type']}")
if result["trust_region_type"]:
print(f" trust_region_type: {result['trust_region_type']}")
print(f" initial_damping: {result['initial_damping']}")
for note in result["notes"]:
print(f" note: {note}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Analyze Jacobian matrix quality for nonlinear solvers."""
import argparse
import json
import sys
from typing import Any, Dict, List, Optional
import numpy as np
def diagnose_jacobian(
matrix: np.ndarray,
finite_diff_matrix: Optional[np.ndarray] = None,
tolerance: float = 1e-6,
) -> Dict[str, Any]:
"""Analyze Jacobian matrix quality.
Args:
matrix: The Jacobian matrix to analyze
finite_diff_matrix: Optional finite-difference approximation for comparison
tolerance: Tolerance for rank deficiency detection
Returns:
Dictionary with Jacobian diagnostics
"""
if matrix.ndim != 2:
raise ValueError("matrix must be 2-dimensional")
if matrix.size == 0:
raise ValueError("matrix must not be empty")
if tolerance <= 0:
raise ValueError("tolerance must be positive")
m, n = matrix.shape
notes: List[str] = []
# Compute SVD for condition number and rank analysis
try:
singular_values = np.linalg.svd(matrix, compute_uv=False)
except np.linalg.LinAlgError:
return {
"shape": [m, n],
"condition_number": float("inf"),
"rank_deficient": True,
"estimated_rank": 0,
"singular_value_min": 0.0,
"singular_value_max": 0.0,
"jacobian_quality": "singular",
"finite_diff_error": None,
"notes": ["SVD computation failed; matrix may be ill-formed."],
}
sv_max = float(singular_values[0])
sv_min = float(singular_values[-1])
# Condition number
if sv_min > 1e-30:
condition_number = sv_max / sv_min
else:
condition_number = float("inf")
# Estimate numerical rank
rank_tol = max(m, n) * sv_max * np.finfo(float).eps
estimated_rank = int(np.sum(singular_values > rank_tol))
rank_deficient = estimated_rank < min(m, n)
# Classify Jacobian quality
if condition_number == float("inf") or sv_min < 1e-14:
jacobian_quality = "near-singular"
notes.append("Near-singular Jacobian; regularization may be needed.")
elif condition_number > 1e10:
jacobian_quality = "ill-conditioned"
notes.append("Highly ill-conditioned; use iterative refinement or scaling.")
elif condition_number > 1e6:
jacobian_quality = "moderately-conditioned"
notes.append("Moderate conditioning; standard methods should work.")
else:
jacobian_quality = "good"
notes.append("Well-conditioned Jacobian.")
if rank_deficient:
notes.append(f"Rank deficient: estimated rank {estimated_rank} < min({m}, {n}).")
# Compare with finite difference approximation if provided
finite_diff_error = None
if finite_diff_matrix is not None:
if finite_diff_matrix.shape != matrix.shape:
notes.append("Finite-diff matrix shape mismatch; skipping comparison.")
else:
diff = matrix - finite_diff_matrix
relative_error = np.linalg.norm(diff) / (np.linalg.norm(matrix) + 1e-30)
finite_diff_error = float(relative_error)
if relative_error > 0.1:
notes.append(f"Large discrepancy with finite-diff ({relative_error:.2e}); check analytic Jacobian.")
elif relative_error > 0.01:
notes.append(f"Moderate discrepancy with finite-diff ({relative_error:.2e}).")
else:
notes.append("Jacobian matches finite-difference approximation well.")
return {
"shape": [m, n],
"condition_number": condition_number,
"rank_deficient": rank_deficient,
"estimated_rank": estimated_rank,
"singular_value_min": sv_min,
"singular_value_max": sv_max,
"jacobian_quality": jacobian_quality,
"finite_diff_error": finite_diff_error,
"notes": notes,
}
def load_matrix(path: str) -> np.ndarray:
"""Load matrix from text file."""
try:
return np.loadtxt(path)
except Exception as e:
raise ValueError(f"Failed to load matrix from {path}: {e}")
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Analyze Jacobian matrix quality.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"--matrix",
type=str,
required=True,
help="Path to Jacobian matrix file (text format)",
)
parser.add_argument(
"--finite-diff-matrix",
type=str,
default=None,
help="Path to finite-difference Jacobian for comparison",
)
parser.add_argument(
"--tolerance",
type=float,
default=1e-6,
help="Tolerance for rank deficiency detection",
)
parser.add_argument("--json", action="store_true", help="Emit JSON output")
return parser.parse_args()
def main() -> None:
args = parse_args()
try:
matrix = load_matrix(args.matrix)
if matrix.ndim == 1:
matrix = matrix.reshape(1, -1)
except ValueError as exc:
print(str(exc), file=sys.stderr)
sys.exit(2)
finite_diff_matrix = None
if args.finite_diff_matrix:
try:
finite_diff_matrix = load_matrix(args.finite_diff_matrix)
if finite_diff_matrix.ndim == 1:
finite_diff_matrix = finite_diff_matrix.reshape(1, -1)
except ValueError as exc:
print(str(exc), file=sys.stderr)
sys.exit(2)
try:
result = diagnose_jacobian(
matrix=matrix,
finite_diff_matrix=finite_diff_matrix,
tolerance=args.tolerance,
)
except ValueError as exc:
print(str(exc), file=sys.stderr)
sys.exit(2)
payload: Dict[str, Any] = {
"inputs": {
"matrix": args.matrix,
"finite_diff_matrix": args.finite_diff_matrix,
"tolerance": args.tolerance,
},
"results": result,
}
if args.json:
print(json.dumps(payload, indent=2, sort_keys=True))
return
print("Jacobian diagnostics")
print(f" shape: {result['shape']}")
print(f" condition_number: {result['condition_number']:.2e}")
print(f" rank_deficient: {result['rank_deficient']}")
print(f" estimated_rank: {result['estimated_rank']}")
print(f" singular_value_min: {result['singular_value_min']:.2e}")
print(f" singular_value_max: {result['singular_value_max']:.2e}")
print(f" jacobian_quality: {result['jacobian_quality']}")
if result["finite_diff_error"] is not None:
print(f" finite_diff_error: {result['finite_diff_error']:.2e}")
for note in result["notes"]:
print(f" note: {note}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Monitor residual patterns and detect failure modes."""
import argparse
import json
import sys
from typing import Any, Dict, List, Optional
def monitor_residuals(
residuals: List[float],
function_evals: Optional[List[int]] = None,
step_sizes: Optional[List[float]] = None,
target_tolerance: float = 1e-10,
) -> Dict[str, Any]:
"""Monitor residual patterns and detect failure modes.
Args:
residuals: List of residual norms
function_evals: Optional list of cumulative function evaluations
step_sizes: Optional list of step sizes taken
target_tolerance: Target convergence tolerance
Returns:
Dictionary with monitoring results and recommendations
"""
if not residuals:
raise ValueError("residuals must not be empty")
if any(r < 0 for r in residuals):
raise ValueError("residuals must be non-negative")
if target_tolerance <= 0:
raise ValueError("target_tolerance must be positive")
n = len(residuals)
patterns_detected: List[str] = []
alerts: List[str] = []
recommendations: List[str] = []
# Basic stats
initial_residual = residuals[0]
final_residual = residuals[-1]
if initial_residual > 1e-30:
residual_reduction = final_residual / initial_residual
else:
residual_reduction = 1.0
# Check for convergence
converged = final_residual <= target_tolerance
# Function evaluation efficiency
total_function_evals = None
efficiency = None
if function_evals and len(function_evals) == n:
total_function_evals = function_evals[-1]
if total_function_evals > 0:
# Orders of magnitude reduced per function eval
if residual_reduction > 0 and residual_reduction < 1:
import math
log_reduction = -math.log10(residual_reduction)
efficiency = log_reduction / total_function_evals
else:
efficiency = 0.0
# Step size analysis
step_size_trend = None
if step_sizes and len(step_sizes) >= 2:
avg_early = sum(step_sizes[:len(step_sizes)//2 + 1]) / (len(step_sizes)//2 + 1)
avg_late = sum(step_sizes[len(step_sizes)//2:]) / (len(step_sizes) - len(step_sizes)//2)
if avg_early > 1e-30:
ratio = avg_late / avg_early
if ratio < 0.5:
step_size_trend = "decreasing"
patterns_detected.append("step_size_decreasing")
elif ratio > 2.0:
step_size_trend = "increasing"
patterns_detected.append("step_size_increasing")
else:
step_size_trend = "stable"
# Pattern detection
if n >= 3:
# Check for oscillation
oscillations = 0
for i in range(1, n - 1):
if (residuals[i] > residuals[i-1] and residuals[i] > residuals[i+1]) or \
(residuals[i] < residuals[i-1] and residuals[i] < residuals[i+1]):
oscillations += 1
if oscillations >= n // 3:
patterns_detected.append("oscillating")
alerts.append("Residual oscillation detected; may indicate step size issues.")
recommendations.append("Consider trust region or reduced initial step size.")
# Check for plateau
recent = residuals[-min(5, n):]
if len(recent) >= 3:
rel_change = max(recent) / (min(recent) + 1e-30)
if rel_change < 1.1 and not converged:
patterns_detected.append("plateau")
alerts.append("Residual plateau detected; solver may be stagnating.")
recommendations.append("Try stronger preconditioner or different solver.")
# Check for divergence
if n >= 2 and residuals[-1] > residuals[0] * 1.5:
patterns_detected.append("diverging")
alerts.append("Residuals are increasing; solver is diverging!")
recommendations.append("Reduce step size, check Jacobian, or add regularization.")
# Check for very slow progress
if n >= 10:
halfway = n // 2
early_reduction = residuals[halfway] / (residuals[0] + 1e-30)
late_reduction = residuals[-1] / (residuals[halfway] + 1e-30)
if early_reduction > 0.9 and late_reduction > 0.9:
patterns_detected.append("slow_convergence")
alerts.append("Very slow convergence detected.")
recommendations.append("Consider Newton method with good Jacobian.")
# Check for initial spike (common with bad initial guess)
if n >= 2 and residuals[1] > residuals[0] * 2:
patterns_detected.append("initial_spike")
recommendations.append("Consider better initial guess or damped first step.")
# Final recommendations
if not patterns_detected:
patterns_detected.append("normal")
if converged:
recommendations.insert(0, "Solver converged successfully.")
elif not recommendations:
recommendations.append("Continue monitoring; no immediate issues detected.")
result: Dict[str, Any] = {
"residual_reduction": residual_reduction,
"iterations": n,
"converged": converged,
"patterns_detected": patterns_detected,
"alerts": alerts,
"recommendations": recommendations,
}
if total_function_evals is not None:
result["function_evals"] = total_function_evals
if efficiency is not None:
result["efficiency"] = efficiency
if step_size_trend is not None:
result["step_size_trend"] = step_size_trend
return result
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Monitor residual patterns and detect failure modes.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"--residuals",
type=str,
required=True,
help="Comma-separated residual values",
)
parser.add_argument(
"--function-evals",
type=str,
default=None,
help="Comma-separated cumulative function evaluation counts",
)
parser.add_argument(
"--step-sizes",
type=str,
default=None,
help="Comma-separated step sizes",
)
parser.add_argument(
"--target-tolerance",
type=float,
default=1e-10,
help="Target convergence tolerance",
)
parser.add_argument("--json", action="store_true", help="Emit JSON output")
return parser.parse_args()
def main() -> None:
args = parse_args()
try:
residuals = [float(x.strip()) for x in args.residuals.split(",")]
except ValueError:
print("Error: residuals must be comma-separated numbers", file=sys.stderr)
sys.exit(2)
function_evals = None
if args.function_evals:
try:
function_evals = [int(x.strip()) for x in args.function_evals.split(",")]
except ValueError:
print("Error: function-evals must be comma-separated integers", file=sys.stderr)
sys.exit(2)
step_sizes = None
if args.step_sizes:
try:
step_sizes = [float(x.strip()) for x in args.step_sizes.split(",")]
except ValueError:
print("Error: step-sizes must be comma-separated numbers", file=sys.stderr)
sys.exit(2)
try:
result = monitor_residuals(
residuals=residuals,
function_evals=function_evals,
step_sizes=step_sizes,
target_tolerance=args.target_tolerance,
)
except ValueError as exc:
print(str(exc), file=sys.stderr)
sys.exit(2)
payload: Dict[str, Any] = {
"inputs": {
"residuals": residuals,
"function_evals": function_evals,
"step_sizes": step_sizes,
"target_tolerance": args.target_tolerance,
},
"results": result,
}
if args.json:
print(json.dumps(payload, indent=2, sort_keys=True))
return
print("Residual monitoring")
print(f" residual_reduction: {result['residual_reduction']:.2e}")
print(f" iterations: {result['iterations']}")
print(f" converged: {result['converged']}")
print(f" patterns: {', '.join(result['patterns_detected'])}")
for alert in result["alerts"]:
print(f" ALERT: {alert}")
for rec in result["recommendations"]:
print(f" recommendation: {rec}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Select nonlinear solver based on problem characteristics."""
import argparse
import json
import sys
from typing import Any, Dict, List
def select_solver(
jacobian_available: bool,
jacobian_expensive: bool,
problem_size: int,
spd_hessian: bool,
smooth_objective: bool,
constraint_type: str,
memory_limited: bool,
high_accuracy: bool,
) -> Dict[str, List[str]]:
"""Select nonlinear solver based on problem characteristics.
Args:
jacobian_available: Whether analytic Jacobian is available
jacobian_expensive: Whether Jacobian computation is expensive
problem_size: Number of unknowns
spd_hessian: Whether Hessian is symmetric positive definite
smooth_objective: Whether objective/residual is smooth
constraint_type: Type of constraints (none, bound, equality, inequality)
memory_limited: Whether memory is constrained
high_accuracy: Whether high accuracy is required
Returns:
Dictionary with recommended solvers, alternatives, and notes
"""
if problem_size <= 0:
raise ValueError("problem_size must be positive")
valid_constraints = {"none", "bound", "equality", "inequality"}
if constraint_type not in valid_constraints:
raise ValueError(f"constraint_type must be one of {valid_constraints}")
recommended: List[str] = []
alternatives: List[str] = []
notes: List[str] = []
large_problem = problem_size >= 10_000
# Handle constrained optimization
if constraint_type == "inequality":
recommended.append("SQP (Sequential Quadratic Programming)")
alternatives.append("Interior Point")
notes.append("Inequality constraints require specialized solvers.")
if not jacobian_available:
notes.append("Consider finite-difference Jacobian or quasi-Newton Hessian.")
return {"recommended": recommended, "alternatives": alternatives, "notes": notes}
if constraint_type == "equality":
recommended.append("SQP")
alternatives.append("Augmented Lagrangian")
notes.append("Equality constraints: use Lagrange multipliers or penalty methods.")
return {"recommended": recommended, "alternatives": alternatives, "notes": notes}
if constraint_type == "bound":
if spd_hessian:
recommended.append("L-BFGS-B")
alternatives.append("Trust-Region Reflective")
else:
recommended.append("Trust-Region Reflective")
alternatives.append("L-BFGS-B")
notes.append("Bound constraints handled via projected methods.")
return {"recommended": recommended, "alternatives": alternatives, "notes": notes}
# Unconstrained case
if jacobian_available and not jacobian_expensive:
if high_accuracy:
recommended.append("Newton (full)")
alternatives.append("Modified Newton")
notes.append("Full Newton provides quadratic convergence near solution.")
elif large_problem:
recommended.append("Newton-Krylov (GMRES)")
alternatives.append("Newton-Krylov (BiCGSTAB)")
notes.append("Newton-Krylov avoids forming full Jacobian for large problems.")
else:
recommended.append("Newton (full)")
alternatives.append("Modified Newton")
elif jacobian_available and jacobian_expensive:
if memory_limited:
recommended.append("L-BFGS")
alternatives.append("Broyden")
notes.append("L-BFGS uses limited memory quasi-Newton updates.")
else:
recommended.append("Modified Newton")
alternatives.append("Broyden")
notes.append("Modified Newton reuses Jacobian for multiple iterations.")
else:
# No Jacobian available
if smooth_objective:
if memory_limited or large_problem:
recommended.append("L-BFGS")
alternatives.append("Broyden (good)")
notes.append("Quasi-Newton methods build approximate Jacobian.")
else:
recommended.append("BFGS")
alternatives.append("SR1")
notes.append("BFGS provides superlinear convergence for smooth problems.")
else:
recommended.append("Anderson Acceleration")
alternatives.append("Picard (fixed-point)")
notes.append("Non-smooth problems may benefit from fixed-point methods.")
# Additional recommendations based on problem characteristics
if spd_hessian and not jacobian_available:
notes.append("SPD Hessian: BFGS maintains positive definiteness.")
if large_problem and "Newton (full)" in recommended:
notes.append("For very large problems, consider Newton-Krylov instead.")
if not smooth_objective:
notes.append("Non-smooth: consider subgradient or bundle methods.")
return {"recommended": recommended, "alternatives": alternatives, "notes": notes}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Select a nonlinear solver based on problem characteristics.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"--jacobian-available",
action="store_true",
help="Analytic Jacobian is available",
)
parser.add_argument(
"--jacobian-expensive",
action="store_true",
help="Jacobian computation is expensive",
)
parser.add_argument(
"--size",
type=int,
required=True,
help="Problem size (number of unknowns)",
)
parser.add_argument(
"--spd-hessian",
action="store_true",
help="Hessian is symmetric positive definite",
)
parser.add_argument(
"--smooth",
action="store_true",
help="Objective/residual is smooth",
)
parser.add_argument(
"--constraints",
type=str,
default="none",
choices=["none", "bound", "equality", "inequality"],
help="Type of constraints",
)
parser.add_argument(
"--memory-limited",
action="store_true",
help="Memory is constrained",
)
parser.add_argument(
"--high-accuracy",
action="store_true",
help="High accuracy is required",
)
parser.add_argument("--json", action="store_true", help="Emit JSON output")
return parser.parse_args()
def main() -> None:
args = parse_args()
try:
result = select_solver(
jacobian_available=args.jacobian_available,
jacobian_expensive=args.jacobian_expensive,
problem_size=args.size,
spd_hessian=args.spd_hessian,
smooth_objective=args.smooth,
constraint_type=args.constraints,
memory_limited=args.memory_limited,
high_accuracy=args.high_accuracy,
)
except ValueError as exc:
print(str(exc), file=sys.stderr)
sys.exit(2)
payload: Dict[str, Any] = {
"inputs": {
"jacobian_available": args.jacobian_available,
"jacobian_expensive": args.jacobian_expensive,
"problem_size": args.size,
"spd_hessian": args.spd_hessian,
"smooth_objective": args.smooth,
"constraint_type": args.constraints,
"memory_limited": args.memory_limited,
"high_accuracy": args.high_accuracy,
},
"results": result,
}
if args.json:
print(json.dumps(payload, indent=2, sort_keys=True))
return
print("Solver selection")
print(f" recommended: {', '.join(result['recommended'])}")
if result["alternatives"]:
print(f" alternatives: {', '.join(result['alternatives'])}")
for note in result["notes"]:
print(f" note: {note}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Evaluate Newton/quasi-Newton step quality for trust region decisions."""
import argparse
import json
import sys
from typing import Any, Dict, List, Optional
def evaluate_step(
predicted_reduction: float,
actual_reduction: float,
step_norm: float,
gradient_norm: float,
trust_radius: Optional[float] = None,
) -> Dict[str, Any]:
"""Evaluate step quality for trust region management.
Args:
predicted_reduction: Model-predicted decrease in objective
actual_reduction: Actual decrease in objective
step_norm: Norm of the step taken
gradient_norm: Norm of gradient at current point
trust_radius: Current trust radius (if using trust region)
Returns:
Dictionary with step quality assessment and recommendations
"""
if predicted_reduction < 0:
raise ValueError("predicted_reduction must be non-negative (model should predict decrease)")
if step_norm < 0:
raise ValueError("step_norm must be non-negative")
if gradient_norm < 0:
raise ValueError("gradient_norm must be non-negative")
if trust_radius is not None and trust_radius <= 0:
raise ValueError("trust_radius must be positive")
notes: List[str] = []
# Compute the ratio (actual/predicted reduction)
if predicted_reduction > 1e-30:
ratio = actual_reduction / predicted_reduction
else:
# Near zero predicted reduction - step is near stationary point
if abs(actual_reduction) < 1e-30:
ratio = 1.0
notes.append("Near stationary point; both reductions negligible.")
else:
ratio = float("inf") if actual_reduction > 0 else float("-inf")
notes.append("Predicted reduction near zero but actual reduction nonzero.")
# Classify step quality based on ratio
# Standard thresholds: eta1 = 0.25, eta2 = 0.75
if ratio < 0:
step_quality = "very_poor"
accept_step = False
notes.append("Negative ratio: objective increased instead of decreased.")
elif ratio < 0.1:
step_quality = "poor"
accept_step = False
notes.append("Very poor agreement between model and actual behavior.")
elif ratio < 0.25:
step_quality = "marginal"
accept_step = True
notes.append("Marginal step quality; accepting but reducing trust radius.")
elif ratio < 0.75:
step_quality = "good"
accept_step = True
notes.append("Good agreement between model and actual behavior.")
else:
step_quality = "excellent"
accept_step = True
notes.append("Excellent step quality; model is accurate.")
# Trust radius adjustment recommendation
trust_radius_action = None
suggested_trust_radius = None
if trust_radius is not None:
if ratio < 0.1:
trust_radius_action = "shrink_aggressive"
suggested_trust_radius = trust_radius * 0.25
notes.append("Aggressively shrinking trust radius due to poor step.")
elif ratio < 0.25:
trust_radius_action = "shrink"
suggested_trust_radius = trust_radius * 0.5
notes.append("Shrinking trust radius.")
elif ratio > 0.75 and step_norm >= 0.9 * trust_radius:
trust_radius_action = "expand"
suggested_trust_radius = min(trust_radius * 2.0, 100.0)
notes.append("Expanding trust radius as step hit boundary.")
else:
trust_radius_action = "maintain"
suggested_trust_radius = trust_radius
notes.append("Maintaining current trust radius.")
else:
# Line search mode
if ratio < 0.25:
notes.append("Consider reducing initial step size for line search.")
elif ratio > 0.9:
notes.append("Full Newton step successful; line search may be unnecessary.")
# Check for potential issues
if step_norm < 1e-14:
notes.append("Warning: step norm is nearly zero; may be at local minimum or saddle.")
if gradient_norm < 1e-10 and not accept_step:
notes.append("Near-zero gradient with rejected step; check for saddle point.")
# Cauchy decrease check: should get at least some fraction of Cauchy decrease
cauchy_decrease_expected = gradient_norm * min(step_norm, gradient_norm)
if accept_step and actual_reduction < 0.1 * cauchy_decrease_expected and gradient_norm > 1e-10:
notes.append("Step achieved less than expected Cauchy decrease.")
return {
"ratio": ratio if not isinstance(ratio, float) or ratio != float("inf") else None,
"step_quality": step_quality,
"accept_step": accept_step,
"trust_radius_action": trust_radius_action,
"suggested_trust_radius": suggested_trust_radius,
"notes": notes,
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Evaluate step quality for trust region decisions.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"--predicted-reduction",
type=float,
required=True,
help="Model-predicted decrease in objective",
)
parser.add_argument(
"--actual-reduction",
type=float,
required=True,
help="Actual decrease in objective",
)
parser.add_argument(
"--step-norm",
type=float,
required=True,
help="Norm of the step taken",
)
parser.add_argument(
"--gradient-norm",
type=float,
required=True,
help="Norm of gradient at current point",
)
parser.add_argument(
"--trust-radius",
type=float,
default=None,
help="Current trust radius (optional)",
)
parser.add_argument("--json", action="store_true", help="Emit JSON output")
return parser.parse_args()
def main() -> None:
args = parse_args()
try:
result = evaluate_step(
predicted_reduction=args.predicted_reduction,
actual_reduction=args.actual_reduction,
step_norm=args.step_norm,
gradient_norm=args.gradient_norm,
trust_radius=args.trust_radius,
)
except ValueError as exc:
print(str(exc), file=sys.stderr)
sys.exit(2)
payload: Dict[str, Any] = {
"inputs": {
"predicted_reduction": args.predicted_reduction,
"actual_reduction": args.actual_reduction,
"step_norm": args.step_norm,
"gradient_norm": args.gradient_norm,
"trust_radius": args.trust_radius,
},
"results": result,
}
if args.json:
print(json.dumps(payload, indent=2, sort_keys=True))
return
print("Step quality evaluation")
if result["ratio"] is not None:
print(f" ratio: {result['ratio']:.4f}")
print(f" step_quality: {result['step_quality']}")
print(f" accept_step: {result['accept_step']}")
if result["trust_radius_action"]:
print(f" trust_radius_action: {result['trust_radius_action']}")
if result["suggested_trust_radius"] is not None:
print(f" suggested_trust_radius: {result['suggested_trust_radius']:.4f}")
for note in result["notes"]:
print(f" note: {note}")
if __name__ == "__main__":
main()
Related skills
FAQ
How do I choose between line search and trust region?
Use the globalization advisor script, which recommends a strategy based on problem type and Jacobian quality.
What does a linear convergence type mean?
It is acceptable and may improve with a preconditioner, per the interpretation guidance.