[AZ-382] C5 GtsamIsam2StateEstimator skeleton + real iSAM2 handle bodies

- Add GtsamIsam2StateEstimator owning the GTSAM substrate:
  gtsam.ISAM2(ISAM2Params()) + gtsam_unstable.IncrementalFixedLagSmoother
  (K * 1/3 s window per D-C5-3) + NonlinearFactorGraph + Values.
- Module-level create(...) factory + register() helper for
  register_state_estimator("gtsam_isam2", create). Opt-in registration
  per ADR-002 — no auto-import.
- Key-management policy: key_for_frame(UUID) -> int via
  gtsam.symbol('x', counter); idempotent re-lookup.
- Replace all four NotImplementedError bodies in _isam2_handle.py with
  real GTSAM calls:
  * add_factor → estimator._graph.add(factor); R05 defensive logging
    on success/failure; EstimatorDegradedError on failure.
  * update → _isam2.update + _smoother.update; empty
    FixedLagSmootherKeyTimestampMap substituted for timestamps=None;
    EstimatorFatalError on either failure.
  * compute_marginals → gtsam.Marginals(getFactorsUnsafe(),
    calculateEstimate()).
  * last_anchor_age_ms → (monotonic_ns - _last_anchor_ns) // 1e6.
- StateEstimator Protocol methods on the estimator still raise
  NotImplementedError naming AZ-383 (factor adds) / AZ-384
  (marginals + outputs).
- AZ-382 AC tests: 27 cases covering 10/10 ACs + factory integration.
- AZ-381 test_ac8_handle_methods_raise_named_task removed (obsolete:
  bodies are real now); test_ac8_handle_is_isam2_graph_handle retained.
- Full suite: 547 passed (+26 vs B12), 2 skipped.
- Impl report: _docs/03_implementation/batch_13_cycle1_report.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 05:51:23 +03:00
parent beed43724f
commit 8b394a98c6
7 changed files with 899 additions and 38 deletions
@@ -293,17 +293,12 @@ def test_ac8_handle_is_isam2_graph_handle() -> None:
assert isinstance(handle, ISam2GraphHandle)
def test_ac8_handle_methods_raise_named_task() -> None:
handle = ISam2GraphHandleImpl(estimator=mock.MagicMock())
with pytest.raises(NotImplementedError, match="AZ-382"):
handle.add_factor(mock.MagicMock())
with pytest.raises(NotImplementedError, match="AZ-382"):
handle.update(mock.MagicMock(), mock.MagicMock())
with pytest.raises(NotImplementedError, match="AZ-382"):
handle.compute_marginals()
with pytest.raises(NotImplementedError, match="AZ-382"):
handle.last_anchor_age_ms()
# Note: the AZ-381 skeleton's ``NotImplementedError`` bodies were
# replaced with real GTSAM calls by AZ-382. The "methods raise" test
# that lived here has moved to
# ``tests/unit/c5_state/test_az382_isam2_smoother_wiring.py``, which
# now asserts the real behaviour (``add_factor`` grows the graph,
# ``update`` advances iSAM2 + the smoother, etc.).
# ----------------------------------------------------------------------