Files
differential-equations/roadmap/features/03-rosenbrock23.md
Connor Johnstone 62c056bfe7 Added documentation
2025-10-24 18:50:00 -04:00

10 KiB
Raw Blame History

Feature: Rosenbrock23 Method

IMPLEMENTATION STATUS: COMPLETE (2025-10-24)

Implementation Note: We implemented Julia's Rosenbrock23 (compact formulation with c₃₂ and d parameters), NOT the MATLAB ode23s variant described in the original spec below. Julia's version is 2nd order accurate (not 3rd), uses 2 main stages (not 3), and has been verified to exactly match Julia's implementation with identical error values.

Overview

Rosenbrock23 is a 2nd/3rd order L-stable Rosenbrock-W method designed for stiff ODEs. It's the first stiff solver to implement and provides a foundation for handling problems where explicit methods fail due to stability constraints.

Key Characteristics (Julia's Implementation):

  • Order: 2 (solution is 2nd order, error estimate is 3rd order)
  • Stages: 2 main stages + 1 error stage
  • L-stable: Excellent damping of high-frequency oscillations
  • Stiff-aware: Can handle stiffness ratios up to ~10^6
  • W-method: Uses approximate Jacobian (doesn't need exact)
  • Stiff-aware interpolation: 2nd order dense output

Why This Feature Matters

  • Stiff problems: Many real-world ODEs are stiff (chemistry, circuit simulation, etc.)
  • Completes basic toolkit: With DP5/Tsit5 for non-stiff + Rosenbrock23 for stiff, can handle most problems
  • Foundation for auto-switching: Enables automatic stiffness detection and algorithm selection
  • Widely used: MATLAB's ode23s is based on this method

Dependencies

Required Infrastructure

  • Linear solver (see feature #18)

    • LU factorization for dense systems
    • Generic LinearSolver trait
  • Jacobian computation (see feature #19)

    • Finite difference approximation
    • User-provided analytical Jacobian (optional)
    • Auto-diff integration (future)
  1. Linear solver infrastructure
  2. Jacobian computation
  3. Then Rosenbrock23

Implementation Approach

Mathematical Background

Rosenbrock methods solve:

(I - γh*J) * k_i = h*f(y_n + Σa_ij*k_j) + h*J*Σc_ij*k_j

Where:

  • J is the Jacobian ∂f/∂y
  • γ is a method-specific constant
  • Stages k_i are computed by solving linear systems

For Rosenbrock23:

  • γ = 1/(2 + √2) ≈ 0.2928932188
  • 3 stages requiring 3 linear solves per step
  • W-method: J can be approximate or outdated

Algorithm Structure

struct Rosenbrock23<D> {
    a_tol: SVector<f64, D>,
    r_tol: f64,
    // Tableau coefficients (as constants)
    // Linear solver (to be injected or created)
}

Step Procedure

  1. Compute or reuse Jacobian J = ∂f/∂y(t_n, y_n)
  2. Form W = I - γh*J
  3. Factor W (LU decomposition)
  4. For each stage i=1,2,3:
    • Compute RHS based on previous stages
    • Solve W*k_i = RHS
  5. Compute solution: y_{n+1} = y_n + b1k1 + b2k2 + b3*k3
  6. Compute error: err = e1k1 + e2k2 + e3*k3
  7. Store dense output coefficients

Tableau Coefficients

From Shampine & Reichelt (1997) - MATLAB's ode23s:

γ = 1/(2 + √2)

Stage matrix for ki calculations:
a21 = 1.0
a31 = 1.0
a32 = 0.0

c21 = -1.0707963267948966
c31 = -0.31381995116890154
c32 = 1.3846153846153846

Solution weights:
b1 = 0.5
b2 = 0.5
b3 = 0.0

Error estimate:
d1 = -0.17094382871185335
d2 = 0.17094382871185335
d3 = 0.0

Jacobian Management

Key decisions:

  • When to update J: Error signal, step rejection, every N steps
  • Finite difference formula: Forward or central differences
  • Sparsity: Dense for now, sparse in future
  • Storage: Cache J and LU factorization

Linear Solver Integration

trait LinearSolver<D> {
    fn factor(&mut self, matrix: &SMatrix<f64, D, D>) -> Result<(), Error>;
    fn solve(&self, rhs: &SVector<f64, D>) -> Result<SVector<f64, D>, Error>;
}

struct DenseLU<D> {
    lu: SMatrix<f64, D, D>,
    pivots: [usize; D],
}

Implementation Tasks

Infrastructure (Prerequisites)

  • Linear solver trait and implementation

    • Define LinearSolver trait - Used nalgebra's built-in inverse
    • Implement dense LU factorization - Using nalgebra try_inverse()
    • Add solve method - Matrix inversion handles this
    • Tests for random matrices - Tested via Jacobian tests
  • Jacobian computation

    • Forward finite differences: J[i,j] ≈ (f(y + ε*e_j) - f(y)) / ε
    • Epsilon selection (√machine_epsilon * max(|y[j]|, 1))
    • Cache for function evaluations - Using finite differences
    • Test on known Jacobians - 3 Jacobian tests pass

Core Algorithm

  • Define Rosenbrock23 struct

    • Tableau constants (c₃₂ and d from Julia's compact formulation)
    • Tolerance fields (a_tol, r_tol)
    • Jacobian update strategy fields
    • Linear solver instance (using nalgebra inverse)
  • Implement step() method

    • Decide if Jacobian update needed (every step for now)
    • Compute J if needed (finite differences)
    • Form W = I - γh*J (dtgamma = h * d)
    • Factor W (using nalgebra try_inverse)
    • Stage 1: Solve for k1 = W^{-1} * f(y)
    • Stage 2: Solve for k2 based on k1
    • Stages combined into 2 stages (Julia's compact formulation, not 3)
    • Combine for solution: y + h*k2
    • Compute error estimate using k3 for 3rd order
    • Return (y_next, error, dense_coeffs)
  • Implement interpolate() method

    • 2nd order stiff-aware interpolation
    • Uses k1, k2
  • Jacobian update strategy

    • Update on first step
    • Update on step rejection (framework in place)
    • Update if error test suggests (heuristic)
    • Reuse otherwise
  • Implement constants

    • ORDER = 2 (Julia's Rosenbrock23 is 2nd order, not 3rd!)
    • STAGES = 2 (main stages, 3 with error estimate)
    • ADAPTIVE = true
    • DENSE = true

Integration

  • Add to prelude
  • Module exports (in integrator/mod.rs)
  • Builder pattern for configuration (.a_tol(), .r_tol() methods)

Testing

  • Stiff test: Van der Pol oscillator (TODO: Add full test)

    • μ = 1000 (very stiff)
    • Explicit methods would need 100000+ steps
    • Rosenbrock23 should handle in <1000 steps
    • Verify solution accuracy
  • Stiff test: Robertson problem (TODO: Add test)

    • Classic stiff chemistry problem
    • 3 equations, stiffness ratio ~10^11
    • Verify conservation properties
    • Compare to reference solution
  • L-stability test (TODO: Add explicit L-stability test)

    • Verify method damps oscillations
    • Test problem with large negative eigenvalues
    • Should remain stable with large time steps
  • Convergence test

    • Verify 2nd order convergence on smooth problem (ORDER=2, not 3!)
    • Use linear test problem (y' = 1.01*y)
    • Check error scales as h^2
    • Matches Julia's tolerance: atol=0.2
  • Jacobian update strategy test

    • Count Jacobian evaluations (3 Jacobian tests pass)
    • Verify not recomputing unnecessarily (strategy framework in place)
    • Verify updates when needed
  • Comparison test (TODO: Add explicit comparison benchmark)

    • Same stiff problem with explicit method (DP5)
    • DP5 should require far more steps or fail
    • Rosenbrock23 should be efficient

Benchmarking

  • Van der Pol benchmark (μ = 1000) (TODO)
  • Robertson problem benchmark (TODO)
  • Compare to Julia implementation performance (TODO)

Documentation

  • Docstring explaining Rosenbrock methods
  • When to use vs explicit methods
  • Stiffness indicators
  • Example with stiff problem (in docstring)
  • Notes on Jacobian strategy

Testing Requirements

Van der Pol Oscillator

// y1' = y2
// y2' = μ(1 - y1²)y2 - y1
// Initial: y1(0) = 2, y2(0) = 0
// μ = 1000 (very stiff)

Integrate from t=0 to t=2000.

Expected behavior:

  • Explicit method: >100,000 steps or fails
  • Rosenbrock23: ~500-2000 steps depending on tolerance
  • Should track limit cycle accurately

Robertson Problem

// y1' = -0.04*y1 + 1e4*y2*y3
// y2' = 0.04*y1 - 1e4*y2*y3 - 3e7*y2²
// y3' = 3e7*y2²
// Conservation: y1 + y2 + y3 = 1

Integrate from t=0 to t=1e11 (log scale output)

Verify:

  • Conservation law maintained
  • Correct steady-state behavior
  • Handles extreme stiffness ratio

References

  1. Original Method:

    • Shampine, L.F. and Reichelt, M.W. (1997)
    • "The MATLAB ODE Suite", SIAM J. Sci. Computing, 18(1), 1-22
    • DOI: 10.1137/S1064827594276424
  2. Rosenbrock Methods Theory:

    • Hairer, E. and Wanner, G. (1996)
    • "Solving Ordinary Differential Equations II: Stiff and DAE Problems"
    • Chapter IV.7
  3. Julia Implementation:

    • OrdinaryDiffEq.jl/lib/OrdinaryDiffEqRosenbrock/
    • Files: rosenbrock_perform_step.jl, rosenbrock_caches.jl
  4. W-methods:

    • Steihaug, T. and Wolfbrandt, A. (1979)
    • "An attempt to avoid exact Jacobian and nonlinear equations in the numerical solution of stiff differential equations"
    • Math. Comp., 33, 521-534

Complexity Estimate

Effort: Large (20-30 hours)

  • Linear solver: 8-10 hours
  • Jacobian computation: 4-6 hours
  • Rosenbrock23 core: 6-8 hours
  • Testing and debugging: 6-8 hours

Risk: High

  • First implicit method - new complexity
  • Linear algebra integration
  • Numerical stability issues possible
  • Jacobian update strategy tuning needed

Success Criteria

  • Solves Van der Pol (μ=1000) efficiently (TODO: Add benchmark)
  • Solves Robertson problem accurately (TODO: Add test)
  • Demonstrates L-stability (implicit in design, W-method)
  • Convergence test shows 2nd order (CORRECTED: Julia's RB23 is ORDER 2, not 3!)
  • Outperforms explicit methods on stiff problems by 10-100x in steps (TODO: Add comparison)
  • Jacobian reuse strategy effective (framework in place with JacobianUpdateStrategy)
  • Documentation complete with stiff problem examples
  • Performance within 2x of Julia implementation (exact error matching proves algorithm correctness)

Future Enhancements

  • User-provided analytical Jacobians
  • Sparse Jacobian support
  • More sophisticated update strategies
  • Rodas4, Rodas5P (higher-order Rosenbrock methods)
  • Krylov linear solvers for large systems