chore: sync project state and current artifacts
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user