# Story 4.8: Jacobian-Freezing Optimization

Status: done

<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->

## Story

As a performance-critical user,
I want to freeze Jacobian updates when approaching the solution,
so that CPU time is reduced significantly without losing convergence stability.

## Acceptance Criteria

1. **Enable Jacobian Freezing in Configuration**
   - Given a `NewtonConfig`
   - When configured with `with_jacobian_freezing(max_frozen_iters)` (and optionally threshold)
   - Then the solver uses the frozen Jacobian optimization when conditions permit

2. **Reuse Jacobian Matrix**
   - Given a system solving with Newton-Raphson
   - When the freezing condition is met
   - Then the solver skips calling `traverse_for_jacobian` and reuses the previous `JacobianMatrix`
   - And speed per iteration improves significantly (up to ~80% target)

3. **Auto-disable on Residual Increase**
   - Given the solver is using a frozen Jacobian
   - When the current iteration's residual is GREATER than the previous iteration's residual
   - Then freezing is automatically disabled, and the Jacobian is recomputed for the next step

4. **Backward Compatibility**
   - Given an existing `NewtonConfig`
   - When no freezing configuration is provided
   - Then the solver behaves exactly as before (recomputing Jacobian every step)

## Tasks / Subtasks

- [x] Add `JacobianFreezingConfig` to `NewtonConfig`
  - [x] Add `jacobian_freezing: Option<JacobianFreezingConfig>` field
  - [x] Add builder method `with_jacobian_freezing()`
- [x] Implement Modified Newton logic in `crates/solver/src/solver.rs`
  - [x] Track frozen iterations counter
  - [x] Skip Jacobian update when conditions permit (residual decreasing, counter < max)
  - [x] Force Jacobian update if residual increases
- [x] Fix broken test struct literals
  - [x] Update `crates/solver/tests/*.rs` with `..Default::default()` for `NewtonConfig` where new fields are added (if not using builders)
- [x] Add unit/integration tests
  - [x] Test frozen Jacobian converges correctly
  - [x] Test auto-recompute on divergence trend

## Dev Notes

- Relevant architecture patterns and constraints:
  - **Zero Allocation**: Do not dynamically allocate new matrices in the hot path. The `JacobianMatrix` should be reused.
  - **Safety**: No panics allowed.
- Source tree components to touch:
  - `crates/solver/src/solver.rs` (update Newton solver loop)
  - `crates/solver/tests/newton_raphson.rs` (add integration tests for freezing)
- Testing standards summary:
  - Use `approx::assert_relative_eq!` for floating point assertions.
  - Run `cargo clippy -- -D warnings` to ensure style compliance.

### Project Structure Notes

- Alignment with unified project structure: all changes are safely confined to the `solver` crate. No bindings code required.

### References

- [Source: `epics.md` FR19] "Solver can freeze Jacobian calculation to accelerate"
- [Source: `epics.md` Story 4.8] "Jacobian-Freezing Optimization"
- [Source: `architecture.md` NFR4] "No dynamic allocation in solver loop (pre-calculated allocation only)"

## Dev Agent Record

### Agent Model Used

Antigravity

### Debug Log References

### Completion Notes List
- Ultimate context engine analysis completed - comprehensive developer guide created
- Implemented `JacobianFreezingConfig` struct with `max_frozen_iters` and `threshold` fields
- Added `jacobian_freezing: Option<JacobianFreezingConfig>` to `NewtonConfig` with `None` default for backward compatibility
- Added `with_jacobian_freezing()` builder method on `NewtonConfig`
- Modified Newton-Raphson solve loop: decision logic skips Jacobian assembly when frozen, stores and reuses `JacobianMatrix` via `.clone()`
- Implemented auto-disable: sets `force_recompute = true` when residual ratio exceeds `(1.0 - threshold)`, resets frozen counter
- Fixed inline struct literal in existing test (`test_with_timeout_preserves_other_fields`)
- Exported `JacobianFreezingConfig` from `lib.rs`
- Created 12 new integration tests in `jacobian_freezing.rs` covering all ACs
- All 151 tests pass (130 unit + 21 integration) + 17 doc-tests; zero regressions
- **[AI Code Review Fixes]**: Fixed critical Zero-Allocation architecture violation by pre-allocating `JacobianMatrix` outside the loop and adding in-place `update_from_builder` logic to avoid `clone()`.
- **[AI Code Review Fixes]**: Added missing Rustdoc documentation to `JacobianFreezingConfig` public fields.
- **[AI Code Review Fixes]**: Fixed integration test file `jacobian_freezing.rs` not being tracked in git natively.

### File List
- `crates/solver/src/solver.rs` (modified — added struct, field, builder, loop logic)
- `crates/solver/src/lib.rs` (modified — exported `JacobianFreezingConfig`)
- `crates/solver/tests/jacobian_freezing.rs` (new — 12 integration tests)
- `_bmad-output/implementation-artifacts/4-8-jacobian-freezing-optimization.md` (modified — status + checkboxes + dev notes)

## Change Log
- 2026-02-19: Implemented Jacobian-Freezing Optimization (Story 4.8) — all ACs satisfied, 12 tests added, zero regressions
