104 lines
5.0 KiB
Markdown
104 lines
5.0 KiB
Markdown
# 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
|