Skip to content

feat: recipes rebuild faults and unconformities - #376

Draft
lachlangrose wants to merge 6 commits into
masterfrom
fix/recipe-faults-unconformities
Draft

lachlangrose wants to merge 6 commits into
masterfrom
fix/recipe-faults-unconformities

Conversation

@lachlangrose

@lachlangrose lachlangrose commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

Draft, stacked on #375 (stage 1), which is stacked on #374. Its diff against master includes those commits until they merge; the change here is the last commit. Stage 2 of the plan in #374's description.

Recipes now rebuild faults and unconformities as well as foliations.

Change

  • Faults. A fault is stored with its creation parameters (displacement, axes, buffer, fault_center, ...) and its explicit faults list by name. faults=[] (no faults, what create_and_add_fault passes by default) is kept distinct from faults=None (every fault in the model), because they build different models. The reader resolves the names against earlier entries.
  • Unconformities. add_unconformity / add_onlap_unconformity record the feature, the value and whether it is onlap, on the object that actually lands in model.features (for onlap that is the inverse, named __<feature>_unconformity_inverse). The reader replays add_unconformity / add_onlap_unconformity in recipe order, which reproduces the regions they add to earlier features.
  • Faults assigned to a foliation after it was built (the old post-hoc restore) are applied only to foliations; faults and unconformities are built with theirs.
  • An entry that refers to a feature that is not an earlier entry is refused with a clear error.
  • Still refused when the recipe is written: fold frames, folded foliations, domain faults, intrusions, custom data=, parameters JSON cannot hold.

Evidence

  • Fault model (the gallery's fault example): rebuilt strata and fault displacement are identical (difference 0.0; before this PR 0.21 on a field of range 10, and 0.49 for the displacement). The rebuilt fault is a FaultSegment.
  • Two-group model with an erode unconformity, and the same with an onlap unconformity: identical fields for both groups, identical NaN patterns, identical evaluate_model unit ids, identical feature list and feature types.
  • faults=[] vs the default: the two models differ, and each rebuilds to itself (the test checks they differ, so it is not vacuous).
  • New test_recipe_faults_unconformities.py (11 tests, including a fault + unconformity model). Against the stage 1 code the 10 tests that existed when I checked all failed; the combined-model test was added afterwards and I did not run it against stage 1. test_recipe_limits.py updated: the stage 1 "fault/unconformity is refused" tests are replaced by a domain-fault refusal.
  • tests: 757 passed, 6 skipped before the combined-model test was added (it passes on its own, together with the rest of its file). ruff: no new findings.

Things to know

  • A domain fault is refused, but under "no record of how it was created" rather than "created as domain_fault": the object in model.features is not the one create_and_add_feature returns, so it carries no record. The behaviour (refused, naming the feature) is what matters; I did not chase the cause.
  • Unconformities are replayed by add_unconformity on the rebuilt base feature, so they depend on feature order. The recipe keeps model.features order, which I verified for the cases above. A model with a fault, two groups and an unconformity also rebuilds exactly (tested). I did not test several unconformities in one model.
  • Not done (stage 3): folds. Their fold functions are callables, which JSON cannot hold.
  • COMPAT.md row updated.

🤖 Generated with Claude Code

lachlangrose and others added 4 commits October 29, 2026 14:50
Recipe: to_recipe_json / from_recipe_json do not store a feature's build parameters
(nelements, interpolatortype, regularisation), so a model built with anything but the defaults
rebuilds with the defaults and gives a different field (differences of 12 to 60 on a field of
range about 560). The default-parameter case passes; the three others are strict xfails.

Inequality: ADMM enforces the bounds with the arguments the existing builder test uses, but
with its default arguments returns essentially the unconstrained solution (4 of 24 bounds
violated, worst 0.056), and with solver cg the inequality data are dropped without a warning.
A control (the unconstrained solution does violate bounds) and the working ADMM configuration
pass; the default-ADMM case and the cg-warning expectation are strict xfails.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nnot rebuild

A recipe rebuilt every feature with the default build parameters and as a foliation, so a model
built with non-default nelements, interpolatortype or regularisation gave a different field (12
to 60 on a field of range ~560), a fault came back as a foliation, and a model with an
unconformity could not be rebuilt at all.

- create_and_add_feature records the parameters it was *requested* with on the feature (the
  element count realised by the interpolator would not reproduce the grid)
- the recipe stores them per foliation (build.feature_type, build.parameters) and the reader
  replays them through create_and_add_feature
- the stratigraphic column's group_mapping is stored
- feature_recipe_kind tests subclasses first, so faults and unconformities are no longer
  labelled structural_frame / foliation
- to_recipe_* raise LoopValueError for a feature it cannot rebuild yet (non-foliation, custom
  data=, a parameter JSON cannot hold, no creation record) instead of writing a wrong recipe
- recipes written before this (no build entry) still load, with a warning; an older non-foliation
  entry is refused

The three strict xfails from the recipe test now pass and their markers are removed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Stage 2 of the recipe fix. Foliations were already rebuilt from their stored parameters; now
faults and unconformities are too.

- a fault is stored with its creation parameters (displacement, axes, buffer, ...) and an
  explicit faults list by name: [] (no faults) is kept distinct from the default (all faults)
- add_unconformity / add_onlap_unconformity record the feature, value and whether it is onlap on
  the object that lands in model.features (the inverse, for onlap); the reader replays them in
  order, which reproduces the regions they add to earlier features
- foliation faults restored after the fact are only applied to foliations; faults and
  unconformities are built with theirs
- an entry that refers to a feature that is not an earlier entry is refused
- anything else (fold frames, folded foliations, domain faults, intrusions) is still refused when
  the recipe is written

A fault model rebuilds to the identical strata and fault displacement (previously 0.21 and 0.49
different); erode and onlap unconformity models rebuild to identical fields and unit ids.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
lachlangrose and others added 2 commits October 29, 2026 18:02
… parameters

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…model's feature, raise LoopValueError for missing unconformity value

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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