Skip to content

Add GP-node-discrete contour path (Phase 3.0e) - #96

Merged
nmorabowen merged 1 commit into
mainfrom
guppi/viewer-contour-discrete
May 13, 2026
Merged

Add GP-node-discrete contour path (Phase 3.0e)#96
nmorabowen merged 1 commit into
mainfrom
guppi/viewer-contour-discrete

Conversation

@nmorabowen

Copy link
Copy Markdown
Owner

Summary

Closes the directive's five-path dispatch (per docs/viewer/02-porting-from-apegmsh.md §4). Each element keeps its own corner-value copies — the cross-element averaging step is skipped, so visualisations preserve field discontinuities at element boundaries (material interfaces, plastic-hinge regions, local-stress jumps).

What lands

  • make_gp_discrete_scalars in viewer/math/gauss_extrapolation.py: parallel to make_gp_nodal_scalars but skips extrapolate_to_nodes_averaged. Returns Callable[[step], dict[int, dict[int, float]]]{element_id: {node_id: value}}. Same multi-step batch + 0-D defensive contracts as the smooth helper.
  • ContourLayer.topology="nodal_discrete": routes through the same backend.add_polygons(point_values=...) call as "nodal" — the difference lives entirely in the per-vertex lookup. Duplicated vertex copies of a shared node hold the element-local value instead of the averaged value.
  • Missing-entry error is topology-aware: "missing element id N" (outer key) vs "missing entry for (element id E, node id N)" (inner key).
  • Topology validation message extended to list all three modes.

The five paths, all done

Path Topology Helper
nodal "nodal" (caller-supplied)
cell "cell" (caller-supplied)
GP-cell-averaged "cell" (caller pre-aggregates)
GP-node-extrap-smooth "nodal" make_gp_nodal_scalars
GP-node-discrete "nodal_discrete" make_gp_discrete_scalars

Test plan

  • 7 new gauss unit tests: constant-field nested dict, two-quad per-element preserved (not averaged), step-varying callable, empty lookup, 0-D input rejected, multi-step batch indexing, out-of-range raises.
  • 6 new PyVista-gated integration tests: discrete-topology acceptance, mpl capability error, two-quad value preservation at shared edges, in-place update_to_step on the SAME actor, missing-element and missing-(element,node) error messages, end-to-end via make_gp_discrete_scalars.
  • 2 new mpl-only tests: topology validation message lists all modes; discrete mode on mpl raises BackendCapabilityError with the "per-vertex" hint.
  • system Python (no pyvista): full suite 1629 passed, 54 skipped (+10 over the 1619 baseline; 6 new pyvista-gated tests skip correctly).
  • opensees_venv (pyvista 0.47.1 + VTK 9.6.1): combined contour + gauss suite 81 passed, including all 6 new PyVista integration tests.

What's next

Phase 3 contour story is complete. Followup: bump pyproject.toml + tag a release on main per CLAUDE.md's versioning policy.

🤖 Generated with Claude Code

Closes the directive's five-path dispatch (per
docs/viewer/02-porting-from-apegmsh.md §4). Each element keeps its
own corner-value copies — the cross-element averaging step is
SKIPPED, so visualisations preserve field discontinuities at
element boundaries (material interfaces, plastic-hinge regions,
local-stress jumps).

Helper API (viewer/math/gauss_extrapolation.py):
- make_gp_discrete_scalars(*, gp_values_fn, element_index,
  natural_coords, element_lookup) returns
  Callable[[step], dict[int, dict[int, float]]] —
  {element_id: {node_id: value}}. Parallel to
  make_gp_nodal_scalars but skips
  extrapolate_to_nodes_averaged; pinv projection from GPs to
  corners is the same.
- Same multi-step batch + 0-D scalar defensive contracts as the
  smooth helper. Out-of-range step raises IndexError, never wraps.

ContourLayer (viewer/layers/contour.py):
- Adds topology="nodal_discrete". Scalars contract:
  dict[element_id, dict[node_id, float]] (or callable returning
  one).
- Routes through the same backend.add_polygons(point_values=...)
  call as topology="nodal" — the difference lives entirely in the
  per-vertex lookup. Duplicated copies of a shared node hold the
  element-local value instead of the averaged value.
- Topology validation extended; the unknown-mode error message
  now lists all three accepted modes.
- Missing-entry error is topology-aware: "missing element id N"
  when the outer key is absent, "missing entry for (element id E,
  node id N)" when the inner key is absent.

Same PyVista renderer; MplBackend still raises
BackendCapabilityError on per-vertex coloring — covers both nodal
modes identically.

9 new tests:
- 7 unit tests in tests/viewer/math/test_gauss_extrapolation.py:
  constant-field nested dict, two-quad per-element preserved (not
  averaged), step-varying callable, empty lookup, 0-D input
  rejected, multi-step batch indexing, out-of-range raises.
- 6 PyVista-gated integration tests in
  tests/viewer/layers/test_contour_layer.py: discrete-topology
  acceptance, mpl capability error, two-quad value preservation
  at shared edges, in-place update_to_step on the same actor,
  missing-element and missing-(element,node) error messages,
  end-to-end via make_gp_discrete_scalars.
- 2 mpl-only tests: topology validation message lists all modes;
  discrete mode on mpl raises capability error with the
  "per-vertex" hint.

Local suites:
- system Python (no pyvista): tests/viewer/ overall 32 contour
  tests pass + 35 gauss tests pass. Full project suite 1629 passed,
  54 skipped. (+10 over the 1619 baseline; 6 new pyvista-gated
  tests skip correctly.)
- opensees_venv (pyvista 0.47.1 + VTK 9.6.1): combined
  contour + gauss suite 81 passed, including all 6 new PyVista
  integration tests.

Phase 3 contour story is now complete. The five directive paths
all flow through ContourLayer + add_polygons + the gauss
extrapolation kernel:

  | Path                       | Topology         | Helper                       |
  |----------------------------|------------------|------------------------------|
  | nodal                      | nodal            | (caller-supplied)            |
  | cell                       | cell             | (caller-supplied)            |
  | GP-cell-averaged           | cell             | (caller pre-aggregates)      |
  | GP-node-extrap-smooth      | nodal            | make_gp_nodal_scalars        |
  | GP-node-discrete           | nodal_discrete   | make_gp_discrete_scalars     |

Next: bump pyproject.toml + tag v1.11.0 / v1.12.0 release on main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@nmorabowen
nmorabowen merged commit 373e1ed into main May 13, 2026
4 of 6 checks passed
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