chore: sync project state and current artifacts
This commit is contained in:
306
docs/tutorial/05-solver-configuration.md
Normal file
306
docs/tutorial/05-solver-configuration.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user