---
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
