Skip to content

docs: add an Interpolation gallery section (choosing an interpolator, regularisation, fold constraints) - #373

Draft
lachlangrose wants to merge 14 commits into
masterfrom
docs/golden-examples
Draft

lachlangrose wants to merge 14 commits into
masterfrom
docs/golden-examples

Conversation

@lachlangrose

@lachlangrose lachlangrose commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

Draft, stacked on #372 (and so #365). Its diff against master includes those commits until they merge; the change here is the last commit.

New examples (two batches) planned from the interpolation golden tests (packages/loop_interpolation/tests/golden). They use the same idea: synthetic geological models whose scalar field is known exactly, sampled for data, so every result is measured against the truth. They are self-contained (no shared helper, so the downloadable .py files work on their own) and use the interpolators directly. New gallery section examples/5_interpolation/, listed in examples/README.rst.

Examples

  • 5a Choosing an interpolator: FDI, PLI and P2 on a gaussian dome. No interpolator wins on every measure: P2 has the lowest interior error and gradient-angle error, FDI the lowest crest error, P2 passes through the data while FDI has the largest misfit, FDI is fastest.

  • 5b Regularisation: sweeps the regularisation weight on exact and noisy data (five noise draws averaged). With exact data the error rises with the weight; with noisy data a very small weight fits the noise (error 0.35 against 0.09), a very large one flattens the dome, and the best is near the default of 1.

  • 5c Fold constraints with sparse data: two contacts and one orientation on a cylindrical fold. The three plain interpolators give straight tilted horizons about 45 degrees out; FDFold, DFI and P2Fold bring the direction to within about 5 degrees. Only P2Fold is also close in the scalar field; FDFold and DFI recover the shape but exaggerate the amplitude.

  • 5d Refolded folds: a dome-and-basin interference pattern. With the axis rotation in the fold event the fold interpolators recover it (median direction error 2-10 degrees); with a constant axis the error is 18-19 degrees and the horizon collapses to straight stripes. The scalar-field error with a constant axis (0.065) is as small as with the rotating axis (0.062), so one error number hides the failure; the example says so. It also reports that FDFold ends 40 degrees from one of its orientation data, without explaining why.

  • 5e Kinds of orientation data (InterpolatorBuilder): with six contacts the error falls from contacts only (0.172) through one tangent (0.158), two tangents (0.127) or a gradient direction (0.129), to a full normal (0.110).

  • 5f The native solver: about 55 000 unknowns. The native field agrees with the Python one to 9e-9 and is faster (2.2 s against 3.9 s); Python-side peak memory is about 1 MB against 102 MB when the solver is chosen before setup (28 MB when chosen after); warm_start cuts a re-solve from 5062 to 3354 iterations. It also shows the trap: the native solver stops at its default maxiter=5000 without converging, logs a warning, leaves up_to_date False and returns a field that is 0.9 off, even though the residual in the warning (3e-8) looks close to the tolerance (1e-8). Raising maxiter fixes it. The user guide gets a short "checking convergence" section.

How I checked

  • Each example was run before its text was written, and the conclusions were rewritten from the printed tables and the rendered figures. Several first drafts were wrong and were corrected this way: I had assumed FDI smooths the crest (it has the lowest crest error), that a plane would show differences (every interpolator reproduces it exactly, so the examples use curved models), and that the weakest weight stops the solver converging (it does not with the default solve).
  • Results are stable run to run (fold example run twice; the fold interpolators shuffle rows with a shared generator).
  • Found while writing the first batch (5a): the 3D supports take nsteps as a cell count, so my first grid was 17 cells (18 nodes) and extended past the unit cube. The examples pass nsteps_cells explicitly.
  • Gallery build: succeeds with the same 5 warnings as docs: rewrite the visualisation guide, fix the API reference, add a docs check #372, none from the new section. The six examples add about 55 s (23 s the regularisation sweep, 21 s the native solver). ruff clean.

CI / requirements

The native example imports the compiled loop_interpolation_core. It has wheels on PyPI for Linux, macOS and Windows, so docs/requirements.txt, docs-check.yml and documentation.yml now install it. I could not run GitHub Actions to confirm the workflows.

Deliberately not included (findings, not examples)

  • Saving and loading (recipes): (pinned by xfail tests in the PR below) to_recipe_json / from_recipe_json do not store the build parameters. A feature entry has only name, kind, faults, regions, so a model built with nelements=2000, interpolatortype="PLI" or regularisation=10 rebuilds with the defaults and differs (max difference 27, 12 and 62 on a field spanning about 560). With the defaults the round trip is exact (difference 0.0). An example would mislead until this is fixed or documented.
  • Inequality constraints: with the default cg solver they are dropped with no warning, and with admm they are enforced only for some argument choices (default arguments leave the bounds violated; the arguments used in the existing builder test enforce them). Cause not diagnosed, so no example. (An earlier version of this note said they never changed the solution with admm; that was wrong, and is corrected in the xfail-test PR.)
  • Intrusions: INTRUSIONS.md lists known regressions and unimplemented parts (intrusion_steps broken against the current stratigraphic column, geometric_scaling_parameters raising, no gradients). I would not put it in the gallery as it stands.

🤖 Generated with Claude Code

lachlangrose and others added 3 commits October 29, 2026 09:11
- fix homepage and user-guide examples that used bounding_box.structured_grid().properties
  (that returns the interpolation-support grid, which has no properties dict)
- fix dtm_creator importing Union from ctypes, which made the module unimportable
- make docs/requirements.txt install the docs extra; add trame packages to it
- start gallery examples from an empty stratigraphic column so the unused Basement
  group no longer logs 'Model does not contain Group_N' errors
- fix reST errors (toctree, code-block directives, literal blocks, title underline)
- add preparing_data and structural_frames to the user guide toctree

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

- visualisation.rst and debugging.rst described the removed LavaVuModelViewer; rewrite them
  around Loop3DView. Every snippet was run. Lambda features need an explicit bounding_box
- map2loop.rst documented GeologicalModel.from_map2loop_directory, which does not exist;
  describe Map2LoopProcessor + GeologicalModel.from_processor (checked against the code) and
  add the page to the User Guide. Fix the stale docstring that still named the old method
- API reference: index each class once, where it is defined. Re-exports are shown on each page
  but marked :no-index:; document loop_common and loop_interpolation, where most of the
  re-exported classes are defined; leave third-party members (pydantic, sklearn, pyvista,
  abc) out of the class and module pages
- fix the remaining reST errors in docs, examples and two loop_interpolation docstrings;
  exclude the internal to-do page; drop the LavaVu notes from the contributors guide
- add .github/workflows/docs-check.yml: build the docs on pull requests that touch them; a
  failing gallery example fails the build

Sphinx warnings: 98 -> 5.

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

Three examples that measure interpolator behaviour against synthetic models with a known
scalar field, the idea behind packages/loop_interpolation/tests/golden:

- 5a choosing an interpolator: FDI, PLI and P2 on a gaussian dome (error, crest error, data
  misfit, gradient angle, time)
- 5b regularisation: sweep the weight on exact and noisy data; the best weight moves from the
  smallest to about the default as noise increases
- 5c fold constraints: three plain interpolators against FDFold, DFI and P2Fold with two
  contacts and one orientation on a cylindrical fold

The prose in each was written from the printed results and the rendered figures.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Comment on lines +24 to +69
name: Build documentation
runs-on: ubuntu-latest
timeout-minutes: 45
env:
PYVISTA_OFF_SCREEN: "True"
MPLBACKEND: Agg
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"

- name: Install system libraries for off-screen rendering
run: |
sudo apt-get update
sudo apt-get install -y libosmesa6-dev libgl1-mesa-dev python3-tk pandoc

- name: Install the packages and the docs extra
run: |
pip install -e packages/loop_common -e packages/loop_interpolation \
-e "packages/loopstructural_visualisation[all]"
pip install -e ".[docs]"

- name: Use the OSMesa build of VTK
run: |
pip uninstall vtk -y
pip install vtk-osmesa==9.3.0 --index-url https://gitlab.kitware.com/api/v4/projects/13/packages/pypi/simple

# pyvista's viewer directive writes _static as a file on a fresh output directory
- name: Build
run: |
mkdir -p docs/build/html/_static
python -m sphinx -b html docs/source docs/build/html -w sphinx_warnings.txt --keep-going

- name: Show the warnings
if: always()
run: if [ -e sphinx_warnings.txt ]; then cat sphinx_warnings.txt; fi

- name: Upload HTML
if: always()
uses: actions/upload-artifact@v4
with:
name: docs-html
path: docs/build/html/
…ation section

- 5d refolded folds: a dome-and-basin interference pattern. A fold event with a rotating axis
  recovers it; a constant axis leaves the median direction error at 18-19 degrees and the
  horizon collapses to stripes, even though the scalar-field error looks as good
- 5e kinds of orientation data with InterpolatorBuilder: with six contacts, error falls from
  contacts only through one tangent, two tangents or a gradient direction, to a full normal

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
lachlangrose and others added 10 commits October 29, 2026 17:21
5f solves the dome with about 55 000 unknowns with the Python CG solver and the native solver:
the fields agree to the solver tolerance (9e-9) and the native solve is faster (2.2 s against
3.9 s), the Python-side peak memory is about 1 MB against 102 MB when the solver is chosen
before setup (28 MB when chosen after), and warm_start cuts the iterations of a re-solve by a
third.

It also shows the thing that catches people out: the native solver stops at its default
maxiter=5000 without converging, logs a warning, leaves up_to_date False, and returns a field
that is 0.9 off, although the residual in the warning is close to the tolerance. The user guide
gets a short section on checking convergence.

The example imports the compiled core, which has wheels for Linux, macOS and Windows on PyPI, so
the docs requirements and both docs workflows install it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…not resolve LoopStructural from PyPI

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The root pyproject pins loop-common and loop-interpolation below the versions in packages/,
so installing everything in one resolve fails.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…eger value) and remove stale LavaVu mentions

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…number the interpolation gallery title

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The root pyproject pins loop-common and loop-interpolation below the versions in packages/, and
pip also checks the pin when only the packages are installed afterwards. Install the third-party
requirements first and the workspace packages without dependencies.

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.

2 participants