Skip to content

Regular update 2026-07: remeshing robustness, restart metadata, GPU and build fixes - #76

Open
chaseshyu wants to merge 21 commits into
masterfrom
update/regular-update-2026-07
Open

Regular update 2026-07: remeshing robustness, restart metadata, GPU and build fixes#76
chaseshyu wants to merge 21 commits into
masterfrom
update/regular-update-2026-07

Conversation

@chaseshyu

@chaseshyu chaseshyu commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

Regular maintenance update (2026-07): 21 commits of bug fixes and robustness work accumulated in five groups — remeshing robustness, boundary conditions, restart/output metadata, GPU fixes, and build/tooling.

Remeshing robustness

  • Boundary point deletion merged segments incorrectlydelete_points_on_boundary() (renamed delete_points_and_merge_boundary()) already fully removes the points and merges the adjacent boundary segments; the caller ran delete_points() again on the same list, a second deletion pass over an already-compacted array with stale indices. That rewrote the boundary segments of the highest-index nodes, detaching the top surface from the side wall and collapsing the corner into a spurious cliff.
  • mesh.max_steiner_factor (new parameter, default 30, 2D only) — caps Triangle's Steiner-point insertion at factor × npoints during remeshing. Bounds the non-terminating encroached-subsegment splitting Triangle enters when two boundary segments meet at a tiny angle (highly distorted mesh at extreme strain), which otherwise spins forever and exhausts memory. Applied only when no area constraint is active, so initial meshing is unaffected; <= 0 disables.
  • True BFS in barycentric interpolation — the enclosing-element search after remeshing only expanded one neighbor level and hard-exited at a fixed 256-element cap. Now a level-by-level BFS (capped at 3 layers, unordered_set dedup) with a best-candidate tolerance for nodes numerically on a face, and a nearest-node fallback for boundary nodes that legitimately fall just outside the old mesh. Interior nodes that still fail print a diagnostic instead of dying.
  • dt refreshed unconditionally after remeshing — dt is otherwise recomputed only every slow_updates_interval steps, so a remesh that refines the mesh could run up to 10 steps on a stale, too-large dt — enough for an explicit CFL runaway to invert elements. compute_mass consumes var.dt, so the refresh comes first.
  • Sediment deposition guarded against degenerate surface facets — a near-zero-area facet (collapsed "cliff" at a convergent trench) made dv_apply/base blow up, placing the marker ~10¹³ m below the domain and aborting the run (exit 168). Deposition on such a facet is skipped for the step (volume accumulator kept, so it deposits after the next remesh regularizes the surface).

Boundary conditions

  • Open-sidewall lithostatic traction floored at zero — for wall facets above the datum, ref_pressure options 0/4 extrapolate linearly negative, flipping the support traction into an outward suction that pulls the topographic strip out of the domain. The true exterior there is air/vacuum. Below the datum the clamp is inert, so normal runs are bit-identical.

Restart & output metadata

  • Frame files now embed the full .info row (steps, time, dt, walltime, nnode, nelem, nseg) — as FieldData in HDF5 frames and named header scalars in plain binary frames. restart() falls back to the embedded metadata when the .info file is missing, and the new utils/recreate_info.py rebuilds a deleted .info from the frame files alone. Dynearthsol.py gains scan_frames() to read the per-frame metadata directly.
  • plastic strain-rate read back at restartdelta_plstrain carries per-element state across steps but was never restored, so restarted runs diverged from continuous ones.
  • delta_plstrain reset each step — only the plastic branches write it, so an element that stopped yielding kept its last increment forever; the NN remap then smeared that fossil across the mesh at each remesh into a growing spurious halo. Clearing it at the top of update_stress makes "plastic strain-rate" a true per-step rate. Diagnostic-only: no feedback into the physics.
  • Final averaged-fields frame reached past max_time — with is_outputting_averaged_fields the run now continues to the next averaging-aligned step so the last frame is written instead of silently dropped.
  • info_display_next_step made consistent at restart, and the non-HDF5 checkpoint scalars converted from one packed 7-double array to named scalars via new BinaryOutput::write_scalar / BinaryInput::read_scaler (matching the existing HDF5 reader API).

GPU (OpenACC)

  • Fixed device data management for zmin in apply_vbcs (2D): the reduction result was never made present on the device for the subsequent kernel.
  • Fixed 2D regular-mesh generation under GPU builds: create_elem_from_cell / create_rect_node write host-allocated arrays and must not be ACC kernels.

Build & tooling

  • macOS/clang: OPENMP_RPATH uses an absolute path instead of @executable_path, so the binary runs from any working directory (e.g. benchmarks-cores subdirectory runs).
  • benchmarks-cores/Makefile: new INDIR=1 option runs each case in its own model-<CASE>-<MODELNAME> subdirectory to avoid clobbering output from other runs.
  • Unused pseudo-transient (PT) solver remnants (compute_dt_PT, update_velocity_PT, var.dt_PT) commented out.
  • CI workflows now clean the configuration they rebuildclean removes only $(OBJS)/$(EXE), whose names depend on ndims and the OpenACC suffix, so the previous bare make clean only removed default-config (3D, non-GPU) objects. A make ndims=2 step after an earlier 2D build with different flags (e.g. opt=0 openmp=0) silently reused the stale objects, since flags are not encoded in object names. All build steps in basic-build, macos-build, mmg-build, and nvc-build now pass the matching ndims=/openacc=1 to their clean invocation.

Compatibility notes

  • Non-HDF5 checkpoint format changed: .chkpt scalars are now individual named entries instead of one packed array, so plain-binary checkpoints written before this branch cannot be restarted by the new binary (HDF5 checkpoints are unaffected). Restart also now expects the plastic strain-rate array in the save frame (present in all existing output).
  • New parameter mesh.max_steiner_factor defaults to 30 (documented in examples/defaults.cfg); set <= 0 for the old unlimited behavior.

Testing

  • 2D build verified on macOS arm64 (clang++, Homebrew boost/HDF5/libomp).
  • The remeshing and restart fixes were each validated on long-running 2D convergent-margin simulations (multi-Myr, thousands of remeshes) where the original failures reproduced; restart determinism was checked bit-exact against continuous runs.

🤖 Generated with Claude Code

chaseshyu added 4 commits July 6, 2026 14:11
compute_dt_PT, update_velocity_PT, and Variables::dt_PT had no live call
sites; comment them out so the dead code no longer suggests an active
code path.
With a relative OPENMP_ROOT_DIR the rpath was @executable_path-relative,
which breaks when the executable is copied or launched outside the build
directory (e.g. benchmarks-cores INDIR runs). Use the absolute
build-directory path instead.
INDIR=1 runs the case in its own subdirectory (model-<CASE>-<MODELNAME>),
copying the executable and cfg there, so concurrent runs of different
cases do not overwrite each other's output.
…ic interpolation

The old fallback expanded the nearest node's element support by one level
only. After aggressive remeshing (especially in 3D) a query point can sit
in a large old element whose nearest node is not one of its vertices, so
the one-level expansion missed the enclosing element and the point fell
through to the nearest-node fallback, degrading the remap. Replace it with
a level-by-level BFS that visits every reachable element, and track the
least-outside element to distinguish boundary-face points from genuinely
outside ones.
Copilot AI review requested due to automatic review settings July 24, 2026 15:48

This comment was marked as off-topic.

chaseshyu and others added 16 commits July 24, 2026 10:58
An unbounded BFS can crawl a large part of the old mesh for points that
are genuinely outside the domain. Three layers cover the practical
enclosing-element cases; beyond that the least-outside fallback applies.
dt is otherwise only recomputed every slow_updates_interval steps in the
main loop, so a remesh that refines the mesh (smaller minl -> smaller
stable dt) could run up to 10 steps on the stale, too-large dt -- enough
for an explicit CFL runaway to invert elements, after which the next
compute_dt hits negative volumes and aborts. compute_mass consumes
var.dt, so the refresh must precede it.
The sidewall branch of apply_stress_bcs sets the support pressure to
ref_pressure(zcenter). For a wall facet above the datum (zcenter > 0,
wall-edge topography) ref_pressure_option 0 extrapolates linearly
negative, flipping the traction into an unphysical outward suction that
pulls the topographic strip out of the domain. Clamp p at zero: the true
exterior above the datum is air/vacuum (zero traction). Options 1/2
(PREM) already return 0 above the datum, and below the datum p > 0
everywhere, so the clamp changes nothing for normal runs.

This is a consumer-side fix, not a change to ref_pressure(): the SPR
pressure-centering sites rely on the smooth linear extension of the
reference and must not be clamped.

Reachable only in non-default cfgs (default vbc_x* = 1 fixes the normal
velocity and skips this branch) with open sidewalls and topography taller
than about one element at the wall.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surface-process deposition (set_surface_marker) places each new sediment marker
edh = dv_apply / base below the surface, where base is the surface facet's area
(edge length in 2D). At a convergent trench the free surface can leave a
degenerate, near-zero-area facet, so edh blows up and the marker lands absurdly
far below the domain (observed z ~ -2.5e13 m in a 1.5e5 m-deep box). It then
cannot be located in any element and set_surface_marker aborts the run
(exit 168) -- seen on convergent-2d (restart) after step ~3370000.

Guard it at the source: skip deposition on a facet whose base is non-positive or
whose edh exceeds the element's own scale (sqrt in 2D / cbrt in 3D of its
volume), KEEPING edvacc so the sediment deposits once the next remesh regularises
the surface. A well-formed deposit has edh << element size, so normal deposition
is unchanged.

The exit(168) fail-fast is RETAINED (with a clearer message): a marker that still
cannot be remapped after the guard is genuinely unexpected and must stop the run
rather than silently drop sediment.

Verified: convergent-2d restart from frame 36 now runs past step 3370000 (to
3470000+) with no deposition failure, where it previously aborted at 3370000.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With is_outputting_averaged_fields, a frame is only written on a
quality_check_step_interval boundary (when an averaging window closes), but the
main loop exited as soon as var.time passed max_time -- a step that is almost
never on that boundary -- so the last time-defined frame was never written.

Keep stepping past max_time until the first quality_check_step_interval boundary
when averaged output is on; normal mode and the max_steps hard cap are unchanged.

Verified: an averaged-fields run (max_time 50 kyr, quality_check_step_interval
100) ends at step 23400 (t = 50.1 kyr) and writes all frames including the
50 kyr one; before, it stopped mid-window and dropped the final frame.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e rate

The "plastic strain-rate" output (var.delta_plstrain, the per-step plastic-strain
increment, also read by the earthquake-state check as max|delta_plstrain|/dt) was
nonzero far from any yielding and grew a diffuse halo out of the free surface /
inflow corner over time.

delta_plstrain is written only on the PLASTIC branch of rh_evp / rh_evp_rsf; when
an element subsequently takes the viscous (Maxwell) branch -- or is elastic /
viscous / maxwell -- it is never touched, so it keeps the value from the last time
it yielded forever. That fossil is then ratio-blended across the mesh by the
nearest-neighbour remap at each remesh and accumulated by the time-averaged output,
producing a growing, spatially diffusing nonzero region (measured nonzero fraction
36% -> 66%, interior halo 315 -> 926 elements, values smeared down to 1e-66).

Clear delta_plstrain[e] at the top of the per-element loop in update_stress so
every non-plastic path leaves 0 and only the plastic branches write depls -- a true
per-step increment. delta_plstrain feeds only diagnostics (the output field and the
earthquake-state stats), so this does not change the simulation dynamics; verified
by an identical NaN-crash state between baseline and fixed runs. The nonzero
fraction is now bounded (flat ~18-20% vs baseline 19% -> 43%) and nonzero only in
the actively-yielding zones.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Frame files now carry the full .info row (steps, time, dt, walltime,
nnode, nelem, nseg) -- as FieldData in vtkhdf output and as header
scalars in plain binary output. A deleted .info previously made all
frames unreadable by Dynearthsol.py; utils/recreate_info.py rebuilds
it byte-identically from the frames alone (old frames lacking the
new fields fall back to 0 in the unused columns).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chaseshyu
chaseshyu force-pushed the update/regular-update-2026-07 branch from f3d1fab to b998978 Compare July 24, 2026 16:10
Copilot AI review requested due to automatic review settings July 24, 2026 16:10

This comment was marked as off-topic.

@chaseshyu chaseshyu changed the title Regular update 2026-07: remeshing robustness, topography-aware stress remap, restart metadata Regular update 2026-07: remeshing robustness, restart metadata, GPU and build fixes Jul 24, 2026
Copilot AI review requested due to automatic review settings July 24, 2026 16:25

This comment was marked as off-topic.

Copilot AI review requested due to automatic review settings July 24, 2026 16:42
@chaseshyu
chaseshyu force-pushed the update/regular-update-2026-07 branch from cc7f164 to 87259d5 Compare July 24, 2026 16:42

This comment was marked as off-topic.

@chaseshyu chaseshyu added enhancement New feature or request bugfixes Fix bugs labels Jul 24, 2026
@chaseshyu
chaseshyu requested review from echoi, sungho91 and tan2 July 24, 2026 17:03
@chaseshyu
chaseshyu marked this pull request as ready for review July 24, 2026 17:03
@chatgpt-codex-connector

This comment was marked as off-topic.

@chaseshyu

Copy link
Copy Markdown
Member Author

Hi @echoi , this PR includes the bugfixes and minor updates splitted from #74. Once this PR has been aggreed and merged, I will reopen #74 of remeshing stress remap feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfixes Fix bugs enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants