# Solver Configuration

Entropyk provides multiple solver strategies for thermodynamic system simulation.

## Overview

| Solver | Convergence | Use Case |
|--------|-------------|----------|
| **Newton-Raphson** | Quadratic | Well-conditioned systems with good initial guess |
| **Picard** | Linear | Strongly nonlinear systems, more robust |
| **Fallback** | Adaptive | Automatic Newton→Picard fallback on divergence |

## Newton-Raphson Solver

The Newton-Raphson method solves F(x) = 0 by iteratively computing:

```
x_{n+1} = x_n - J^{-1} * F(x_n)
```

Where J is the Jacobian matrix of partial derivatives.

### Configuration

```rust
use entropyk_solver::{NewtonConfig, NewtonSolver, ConvergenceCriteria};

let config = NewtonConfig {
    max_iterations: 100,
    tolerance: 1e-6,
    relaxation: 1.0,  // Damping factor (0 < α ≤ 1)
    ..Default::default()
};

let solver = NewtonSolver::new(config);
```

### With Convergence Criteria

```rust
let criteria = ConvergenceCriteria {
    pressure_tolerance: 100.0,     // Pa
    enthalpy_tolerance: 1000.0,    // J/kg
    residual_tolerance: 1e-6,
    ..Default::default()
};

let solver = NewtonSolver::new(NewtonConfig::default())
    .with_convergence_criteria(criteria);
```

### With Initial State

```rust
// Provide initial guess for state vector
let initial_state = vec![
    1e6,    // P_edge0 (Pa)
    400e3,  // h_edge0 (J/kg)
    0.9e6,  // P_edge1 (Pa)
    250e3,  // h_edge1 (J/kg)
];

let solver = NewtonSolver::new(NewtonConfig::default())
    .with_initial_state(initial_state);
```

### Jacobian Freezing

For performance, the Jacobian can be reused across iterations:

```rust
use entropyk_solver::JacobianFreezingConfig;

let freeze_config = JacobianFreezingConfig {
    max_frozen_iterations: 5,  // Reuse J for up to 5 iterations
    recompute_on_divergence: true,
};

let solver = NewtonSolver::new(NewtonConfig {
    jacobian_freezing: Some(freeze_config),
    ..Default::default()
});
```

## Picard Solver

The Picard (fixed-point) iteration is more robust for strongly nonlinear systems:

```
x_{n+1} = G(x_n)
```

### Configuration

```rust
use entropyk_solver::{PicardConfig, PicardSolver};

let config = PicardConfig {
    max_iterations: 200,
    tolerance: 1e-5,
    relaxation: 0.8,  // Under-relaxation for stability
    ..Default::default()
};

let solver = PicardSolver::new(config);
```

### When to Use Picard

- **Phase change problems**: Evaporation/condensation with sharp property changes
- **Poor initial guess**: When Newton diverges
- **Near singularities**: Close to critical points

## Fallback Solver

The Fallback solver automatically switches from Newton to Picard on divergence:

```rust
use entropyk_solver::{FallbackConfig, FallbackSolver};

let config = FallbackConfig {
    newton_max_iterations: 50,
    picard_max_iterations: 100,
    switch_on_newton_divergence: true,
    ..Default::default()
};

let solver = FallbackSolver::new(config);
```

### Fallback Behavior

1. Start with Newton-Raphson
2. If Newton diverges (residual increases), switch to Picard
3. If Picard converges, optionally try Newton again for faster convergence

## Convergence Criteria

### Basic Tolerance

```rust
let criteria = ConvergenceCriteria {
    pressure_tolerance: 100.0,     // |ΔP| < 100 Pa
    enthalpy_tolerance: 1000.0,    // |Δh| < 1000 J/kg
    residual_tolerance: 1e-6,      // ||F(x)|| < 1e-6
    max_iterations: 100,
};
```

### Per-Circuit Convergence

For multi-circuit systems, convergence is tracked per circuit:

```rust
// After solving, check the convergence report
if let Some(report) = converged_state.convergence_report() {
    for circuit in &report.circuits {
        println!(
            "Circuit {}: converged={}, max_ΔP={:.1} Pa, max_Δh={:.1} J/kg",
            circuit.circuit_id,
            circuit.converged,
            circuit.max_pressure_change,
            circuit.max_enthalpy_change
        );
    }
}
```

## Smart Initialization

Good initial guess is critical for convergence. Use `SmartInitializer`:

```rust
use entropyk_solver::{SmartInitializer, InitializerConfig};

let config = InitializerConfig {
    fluid: "R134a".to_string(),
    source_temperature_celsius: 40.0,  // Condensing temperature
    sink_temperature_celsius: 5.0,     // Evaporating temperature
    superheat_kelvin: 5.0,
    subcool_kelvin: 3.0,
};

let initializer = SmartInitializer::new(config);

// Estimate saturation pressures
let (p_high, p_low) = initializer.estimate_pressures(
    40.0,  // Source temperature (°C)
    5.0,   // Sink temperature (°C)
);

// Populate state vector
let state = initializer.populate_state(&system, 40.0, 5.0)?;
```

## Running the Solver

### Basic Solve

```rust
use entropyk_solver::{System, NewtonSolver, NewtonConfig};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut system = build_system();
    system.finalize()?;
    
    let solver = NewtonSolver::new(NewtonConfig::default());
    let result = solver.solve(&system)?;
    
    println!("Converged in {} iterations", result.iterations);
    println!("Final state: {:?}", result.state);
    
    Ok(())
}
```

### With Timeout

```rust
use entropyk_solver::TimeoutConfig;

let config = NewtonConfig {
    timeout: Some(TimeoutConfig {
        max_time_seconds: 10.0,
        return_best_on_timeout: true,
    }),
    ..Default::default()
};
```

## Error Handling

```rust
use entropyk_solver::SolverError;

match solver.solve(&system) {
    Ok(result) => {
        println!("Converged: {:?}", result.status);
    }
    Err(SolverError::MaxIterationsExceeded { iterations, residual }) => {
        eprintln!("Failed to converge after {} iterations", iterations);
        eprintln!("Final residual: {}", residual);
    }
    Err(SolverError::JacobianSingular { condition_number }) => {
        eprintln!("Jacobian is singular (condition number: {})", condition_number);
        eprintln!("Check system topology and initial guess");
    }
    Err(e) => {
        eprintln!("Solver error: {:?}", e);
    }
}
```

## Solver Strategy Selection

```rust
use entropyk_solver::SolverStrategy;

let strategy = SolverStrategy::Fallback;  // Recommended for production

match strategy {
    SolverStrategy::Newton => {
        let solver = NewtonSolver::new(NewtonConfig::default());
    }
    SolverStrategy::Picard => {
        let solver = PicardSolver::new(PicardConfig::default());
    }
    SolverStrategy::Fallback => {
        let solver = FallbackSolver::new(FallbackConfig::default());
    }
}
```

## Best Practices

1. **Always use SmartInitializer** - Good initial guess is critical
2. **Use Fallback solver** - More robust for production use
3. **Set appropriate tolerances** - Balance accuracy vs. convergence time
4. **Monitor convergence** - Log iteration progress for debugging
5. **Handle errors gracefully** - Provide meaningful feedback on failure

## Python Bindings

```python
import entropyk

# Note: Python bindings use placeholder adapters
# The solver won't converge because there are no real physics equations

system = entropyk.System()
# ... build system ...

solver = entropyk.NewtonSolver(
    max_iterations=100,
    tolerance=1e-6
)

result = solver.solve(system)
print(f"Status: {result.status}")
print(f"Iterations: {result.iterations}")
```

## Next Steps

- [Components Reference](./03-components.md) - Detailed component documentation
- [Building Systems](./04-building-systems.md) - Create system topology