--- 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>` field - [x] Add `use_numerical_jacobian: bool` field - [x] Add `jacobian_freezing: Option` field - [x] Add `convergence_criteria: Option` field - [x] Add `timeout_config: PyTimeoutConfig` field - [x] Add `previous_state: Option>` 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>` field - [x] Add `timeout_ms: Option` field - [x] Add `convergence_criteria: Option` 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