Entropyk/_bmad-output/implementation-artifacts/6-6-python-solver-configuration-parity.md

263 lines
7.8 KiB
Markdown

---
storyId: 6-6
title: Python Solver Configuration Parity
epic: 6
status: done
created: 2026-02-22
priority: P0
---
# Story 6.6: Python Solver Configuration Parity
## Overview
Expose all Rust solver configuration options in Python bindings to enable full control over convergence optimization from Python scripts.
## Problem Statement
The current Python bindings expose only a subset of the Rust solver configuration options. This prevents Python users from:
1. **Setting initial states** for cold-start solving (critical for convergence)
2. **Configuring Jacobian freezing** for performance optimization
3. **Using advanced convergence criteria** for multi-circuit systems
4. **Accessing timeout behavior configuration** (ZOH fallback for HIL)
5. **Using SolverStrategy** for uniform solver abstraction
### Gap Analysis
| Rust Field | Python Exposed | Impact | Priority |
|------------|----------------|--------|----------|
| `initial_state` | ❌ | Cannot warm-start solver | P0 |
| `use_numerical_jacobian` | ❌ | Cannot debug Jacobian issues | P1 |
| `jacobian_freezing` | ❌ | Missing 80% performance optimization | P1 |
| `convergence_criteria` | ❌ | Cannot configure multi-circuit convergence | P1 |
| `timeout_config` | ❌ | Cannot configure ZOH fallback | P2 |
| `previous_state` | ❌ | Cannot use HIL zero-order hold | P2 |
| `line_search_armijo_c` | ❌ | Cannot tune line search | P2 |
| `divergence_threshold` | ❌ | Cannot adjust divergence detection | P2 |
| `SolverStrategy` enum | ❌ | No uniform solver abstraction | P0 |
## Acceptance Criteria
### AC1: NewtonConfig Full Exposure
**Given** Python script using entropyk
**When** creating `NewtonConfig`
**Then** all Rust configuration fields are accessible:
```python
config = entropyk.NewtonConfig(
max_iterations=200,
tolerance=1e-6,
line_search=True,
timeout_ms=5000,
# NEW FIELDS:
initial_state=[101325.0, 420000.0, ...], # Warm-start
use_numerical_jacobian=False,
jacobian_freezing=entropyk.JacobianFreezingConfig(
max_frozen_iters=3,
threshold=0.1
),
convergence_criteria=entropyk.ConvergenceCriteria(
pressure_tolerance_pa=1.0,
mass_balance_tolerance_kgs=1e-9
),
timeout_config=entropyk.TimeoutConfig(
return_best_state_on_timeout=True,
zoh_fallback=False
),
line_search_armijo_c=1e-4,
line_search_max_backtracks=20,
divergence_threshold=1e10
)
```
### AC2: PicardConfig Full Exposure
**Given** Python script using entropyk
**When** creating `PicardConfig`
**Then** all Rust configuration fields are accessible:
```python
config = entropyk.PicardConfig(
max_iterations=500,
tolerance=1e-4,
relaxation=0.5,
# NEW FIELDS:
initial_state=[...],
timeout_ms=10000,
convergence_criteria=entropyk.ConvergenceCriteria(...)
)
```
### AC3: SolverStrategy Exposure
**Given** Python script using entropyk
**When** needing uniform solver interface
**Then** `SolverStrategy` enum is available:
```python
# Create strategy directly
strategy = entropyk.SolverStrategy.newton(tolerance=1e-6)
strategy = entropyk.SolverStrategy.picard(relaxation=0.5)
# Use default
strategy = entropyk.SolverStrategy.default()
# Solve uniformly
result = strategy.solve(system)
```
### AC4: Supporting Types
**Given** Python script using entropyk
**When** configuring advanced options
**Then** supporting types are available:
```python
# JacobianFreezingConfig
jf_config = entropyk.JacobianFreezingConfig(
max_frozen_iters=3,
threshold=0.1
)
# TimeoutConfig
to_config = entropyk.TimeoutConfig(
return_best_state_on_timeout=True,
zoh_fallback=False
)
# ConvergenceCriteria
cc = entropyk.ConvergenceCriteria(
pressure_tolerance_pa=1.0,
mass_balance_tolerance_kgs=1e-9,
energy_balance_tolerance_w=1e-6
)
```
### AC5: Backward Compatibility
**Given** existing Python code
**When** upgrading to new version
**Then** all existing code continues to work:
```python
# OLD CODE - still works
config = entropyk.NewtonConfig(max_iterations=100, tolerance=1e-6)
result = config.solve(system)
# NEW CODE - with advanced options
config = entropyk.NewtonConfig(
max_iterations=100,
tolerance=1e-6,
initial_state=previous_result.state_vector
)
```
## Implementation Tasks
### Task 1: Extend PyNewtonConfig
- [x] Add `initial_state: Option<Vec<f64>>` field
- [x] Add `use_numerical_jacobian: bool` field
- [x] Add `jacobian_freezing: Option<PyJacobianFreezingConfig>` field
- [x] Add `convergence_criteria: Option<PyConvergenceCriteria>` field
- [x] Add `timeout_config: PyTimeoutConfig` field
- [x] Add `previous_state: Option<Vec<f64>>` field
- [x] Add `line_search_armijo_c: f64` field
- [x] Add `line_search_max_backtracks: usize` field
- [x] Add `divergence_threshold: f64` field
- [x] Update `solve()` to pass all fields to Rust `NewtonConfig`
### Task 2: Extend PyPicardConfig
- [x] Add `initial_state: Option<Vec<f64>>` field
- [x] Add `timeout_ms: Option<u64>` field
- [x] Add `convergence_criteria: Option<PyConvergenceCriteria>` field
- [x] Update `solve()` to pass all fields to Rust `PicardConfig`
### Task 3: Create PySolverStrategy
- [x] Create `PySolverStrategy` struct wrapping `SolverStrategy`
- [x] Implement `#[staticmethod] newton()` constructor
- [x] Implement `#[staticmethod] picard()` constructor
- [x] Implement `#[staticmethod] default()` constructor
- [x] Implement `solve(&mut self, system: &mut PySystem)` method
- [x] Register in `lib.rs`
### Task 4: Create Supporting Types
- [x] Create `PyJacobianFreezingConfig` struct
- [x] Create `PyTimeoutConfig` struct
- [x] Create `PyConvergenceCriteria` struct
- [x] Register all in `lib.rs`
### Task 5: Update Documentation
- [x] Update `bindings/python/README.md` with new API
- [x] Add examples for warm-start solving
- [x] Add examples for Jacobian freezing
- [x] Update `solver_control_examples.ipynb`
### Task 6: Add Tests
- [x] Test `initial_state` warm-start
- [x] Test `jacobian_freezing` configuration
- [x] Test `convergence_criteria` configuration
- [x] Test `SolverStrategy` usage
- [x] Test backward compatibility
## File List
| File | Action | Description |
|------|--------|-------------|
| `bindings/python/src/solver.rs` | Modify | Extend config structs |
| `bindings/python/src/lib.rs` | Modify | Register new classes |
| `bindings/python/README.md` | Modify | Document new API |
| `bindings/python/tests/test_solver.py` | Modify | Add tests |
| `bindings/python/solver_control_examples.ipynb` | Modify | Add examples |
## Technical Notes
### Priority Implementation Order
1. **P0: `initial_state`** - Most critical for convergence
2. **P0: `SolverStrategy`** - Architectural consistency
3. **P1: `jacobian_freezing`** - Performance optimization
4. **P1: `convergence_criteria`** - Multi-circuit support
5. **P2: Other fields** - Advanced tuning
### Memory Safety
- `initial_state` and `previous_state` are cloned when passed to Rust
- No lifetime issues as all data is owned
- `unsendable` on `PySystem` remains for now (future: thread safety)
### Performance Considerations
- Jacobian freezing can reduce per-iteration time by ~80%
- Warm-start can reduce iterations by 50-90% for similar conditions
## References
- **FR52**: Python Solver Configuration Parity [Source: prd.md]
- **Story 6.2**: Python Bindings (PyO3) - foundation [Source: epics.md]
- **Epic 4**: Intelligent Solver Engine - Rust solver implementation [Source: epics.md]
- **NewtonConfig**: `crates/solver/src/solver.rs:398-490`
- **PicardConfig**: `crates/solver/src/solver.rs` (Picard section)
- **SolverStrategy**: `crates/solver/src/solver.rs:2030-2073`
## Dependencies
- Story 6.2 (Python Bindings) - ✅ Complete
- Epic 4 (Solver Engine) - ✅ Complete
## Definition of Done
- [x] All acceptance criteria met
- [x] All tests pass (`pytest tests/ -v`)
- [x] Documentation updated
- [x] Backward compatibility verified
- [x] Code reviewed
- [ ] Merged to main