Skip to content

fix: saved interpolator frames, copies and stale-frame check, stage S3 (spec 005) - #397

Merged
lachlangrose merged 24 commits into
feat/005-s2-fold-framesfrom
feat/005-s3-frame-persistence
Nov 5, 2026
Merged

lachlangrose merged 24 commits into
feat/005-s2-fold-framesfrom
feat/005-s3-frame-persistence

Conversation

@lachlangrose

@lachlangrose lachlangrose commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Stacked pull request

This pull request is stacked on #396 (stage S2). Its base is feat/005-s2-fold-frames, so the diff shows only stage S3. After #396 merges, change the base to master. A rebase onto master may be needed, because #396 was rebased after the S1 squash merge.

Summary

This pull request implements stage S3 of spec 005 (coordinate and fault-frame correctness): saved interpolators, copies and transform changes keep the coordinate frame (FR-016 to FR-020). The spec, plan and tasks are in specs/005-coordinate-frame-correctness/. All 18 S3 tasks (T033 to T050) are done. S4 to S6 are not planned yet.

Changes

  • Saved frame (FR-016): GeologicalInterpolator.to_dict now writes format_version: 2, constraint_space, transform and bounding_box after the old keys. format_version is the first new key.
  • No second conversion (FR-017): a saved payload is loaded with _restore_local_constraints. It stores the local arrays as they are and does not call set_*_constraints.
  • Copies (FR-018): copy() of the FDI, P2 and P2 fold interpolators keeps the bounding_box of its source.
  • Legacy files (FR-019): a payload with no format_version should get local_origin and local_rotation, or declare_identity=True. With no choice, the loader on 1.x loads the identity frame (as older releases did) and emits one FutureWarning that points at the caller. A partial input raises InterpolatorFormatError and names the missing argument. Malformed, unsupported and conflicting input fail the same way. The loader returns nothing and changes nothing when it fails.
  • Transform change (FR-020): the policy is "reject at use". An interpolator that holds data in one frame raises StaleFrameError at the next set, evaluate or save in another frame. It changes no state. New public method: GeologicalInterpolator.frame_signature().
  • New module: loop_interpolation._frame_persistence. InterpolatorFormatError and StaleFrameError are exported from loop_interpolation.
  • Old pickles: an interpolator saved before S3 has no _frame_at_data. A class-level default keeps GeologicalModel.from_file working.
  • COMPAT.md has a row in the deprecation table (earliest removal: the next major release). ROADMAP.md records the decision for legacy interpolator payloads. The module constant _STRICT_LEGACY in _frame_persistence.py carries the note "REMOVE AT THE NEXT MAJOR RELEASE" (spec 005, C-018).

Behaviour change

None on the 1.x line. Loading a legacy interpolator file with no choice keeps the old result (identity frame) and now also emits a FutureWarning. At the next major release the loader raises InterpolatorFormatError there: set _STRICT_LEGACY to true, or delete the fallback (decision C-018).

Test plan

  • ruff check LoopStructural packages: pass
  • pytest (root): 1994 passed, 5 skipped. Before S3: 1760 passed, 5 skipped.
  • pytest packages/loop_common/tests packages/loop_interpolation/tests: 912 passed, 47 skipped, 2 xfailed
  • New tests: test_frame_persistence.py (184 cells) and test_frame_copy_and_lifecycle.py (50 cells). They take about 40 s.
  • Before S3 most of the new cells fail, as expected. The rest pass and are coverage, not defects. See verification.md.
  • Rollback check: legacy files saved by the old code load exactly (error 0.0) with a supplied transform. A reader from before S3 fails on a new file with a TypeError that names format_version.
  • Wave reviews and the end review (Opus) pass. The end review found two major defects, and both are fixed with regression tests (old pickles; partial legacy transform).
  • OP7 closed. The P2 value-only fit of R1 misses SC-002 in every frame (value error 7.8e-8 against 7.5e-8; gradient error 3.9e-7 against 1e-8). Decided in spec C-017: SC-002 for P2 uses the exact nodal field. The fit accuracy is issue P2 value-only fit of an affine field is about 100 times less accurate than FDI and PLI (misses 1e-8 in every frame) #399. The S3 rebuild test uses the exact nodal control for P2.

Notes

  • black --check reports 4 changed library files that are not Black-clean. They were not Black-clean before S3, and only the task lines were changed.
  • 22 minor review findings stay open. They are listed in specs/005-coordinate-frame-correctness/run-state.json. One is outside S3: model.evaluate_feature_gradient raised a shape mismatch in a small rotated model (_geological_feature.py:229).
  • PLI has no copy(). S3 does not add one.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1

lachlangrose and others added 24 commits November 5, 2026 08:20
Plan saved interpolator frames, copies that keep the frame, the legacy
load rule and the policy for a transform change (FR-016 to FR-020).
Close OP4 and OP5.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Add 18 tasks in waves 14 to 22 for saved interpolator frames, copies that
keep the frame, and the stale-frame check. Update the spec index.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Move the S2 record to history and record the S3 start.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Add the R1 interpolator helpers for five engines, a legacy payload helper,
and the stubs and exports of the frame persistence module.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add the payload, round-trip and two-cycle tests, and the copy, stale-frame
and rebuild tests. They fail now for the missing frame handling. Record the
P2 fit accuracy as open point OP7.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add the legacy, conflicting, malformed and unsupported payload tests, and
the fault restoration test after a transform change. Compare a loaded
interpolator with its source, not with the analytic field.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Check the message of the unsupported and malformed payload errors, the
unchanged source dict, and the conflict wording. Compare fault-frame
coordinates in the fault restoration test, because the R2 fixture moves
no probe.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Check declare_identity and saved for a declare_identity conflict with a
saved transform. Reword the conflict message rule in the plan.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Record the failing ids and first errors of the new S3 tests, and three
behavioural defects measured with the production path.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…40, wave 18)

Write the frame keys of a saved interpolator, classify a payload as legacy
or version 2, and build a bounding box from a support and a transform.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…, T042, wave 19)

Add resolve_frame for legacy, conflicting and malformed payloads. Add the
frame signature and the stale-frame check to GeologicalInterpolator, store
loaded arrays without conversion, and write the frame keys in to_dict.

Six existing JSON and YAML round-trip tests fail until the loader is
rewritten in the next wave.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…T043 to T046, wave 20)

Rewrite InterpolatorFactory.from_dict for saved payloads: check the payload
kind, resolve the frame, and restore the stored arrays without conversion.
Make FDI, P2 and P2 fold copy() keep the coordinate frame.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Remove a stray blank line from the factory module and record the review.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
…47 to T049, wave 21)

Add the COMPAT.md row, the ROADMAP.md decision for legacy interpolator
payloads, and the S3 rollback check in verification.md.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…wave 22)

Record the final gate results, the golden review, the backend matrix and the
cost of the frame check. Add the S3 status line to the ROADMAP.md log.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Add a class default for _frame_at_data so interpolators saved before the
frame check still evaluate. Refuse a legacy payload that has only one of
local_origin and local_rotation, and name the missing argument. Add
regression tests for both.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XADRvJ98bJ8Ey8Rw98F3j1
@lachlangrose
lachlangrose added this pull request to stack #398 November 5, 2026 02:19
@lachlangrose
lachlangrose merged commit e5ff480 into master Nov 5, 2026
47 checks passed
@lachlangrose
lachlangrose deleted the feat/005-s3-frame-persistence branch November 5, 2026 03:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant