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

7.8 KiB

storyId title epic status created priority
6-6 Python Solver Configuration Parity 6 done 2026-02-22 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:

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:

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:

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

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

# 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

  • Add initial_state: Option<Vec<f64>> field
  • Add use_numerical_jacobian: bool field
  • Add jacobian_freezing: Option<PyJacobianFreezingConfig> field
  • Add convergence_criteria: Option<PyConvergenceCriteria> field
  • Add timeout_config: PyTimeoutConfig field
  • Add previous_state: Option<Vec<f64>> field
  • Add line_search_armijo_c: f64 field
  • Add line_search_max_backtracks: usize field
  • Add divergence_threshold: f64 field
  • Update solve() to pass all fields to Rust NewtonConfig

Task 2: Extend PyPicardConfig

  • Add initial_state: Option<Vec<f64>> field
  • Add timeout_ms: Option<u64> field
  • Add convergence_criteria: Option<PyConvergenceCriteria> field
  • Update solve() to pass all fields to Rust PicardConfig

Task 3: Create PySolverStrategy

  • Create PySolverStrategy struct wrapping SolverStrategy
  • Implement #[staticmethod] newton() constructor
  • Implement #[staticmethod] picard() constructor
  • Implement #[staticmethod] default() constructor
  • Implement solve(&mut self, system: &mut PySystem) method
  • Register in lib.rs

Task 4: Create Supporting Types

  • Create PyJacobianFreezingConfig struct
  • Create PyTimeoutConfig struct
  • Create PyConvergenceCriteria struct
  • Register all in lib.rs

Task 5: Update Documentation

  • Update bindings/python/README.md with new API
  • Add examples for warm-start solving
  • Add examples for Jacobian freezing
  • Update solver_control_examples.ipynb

Task 6: Add Tests

  • Test initial_state warm-start
  • Test jacobian_freezing configuration
  • Test convergence_criteria configuration
  • Test SolverStrategy usage
  • 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

  • All acceptance criteria met
  • All tests pass (pytest tests/ -v)
  • Documentation updated
  • Backward compatibility verified
  • Code reviewed
  • Merged to main