Entropyk/docs/tutorial/05-solver-configuration.md

306 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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