Skip to content

Enhancement/high fidelity mode 3 - #339

Draft
WPringle wants to merge 15 commits into
Projectionfrom
enhancement/high-fidelity_mode-3
Draft

Enhancement/high fidelity mode 3#339
WPringle wants to merge 15 commits into
Projectionfrom
enhancement/high-fidelity_mode-3

Conversation

@WPringle

@WPringle WPringle commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Purpose of High-fidelity mode 3

There are now three modes of High fidelity, the newest one (3) is added in this PR:

  1. Hf = 1 resamples the vector features in a high fidelity domain geodata object with high fidelity flag 1.
  2. Hf = 2 uses the vectors point spacing as it is in the file.
  3. Hf = 3 resamples the vector features (same as Hf = 1) and then prunes typical degenerate cases.

Summary of changes

(A) Thin-/small-angle triangle collapse becomes a first-class cleanup stage. The old inline collapse_thin_triangles(p,t,db) call inside msh.clean is retired in favor of an explicit, iterative, quality-driven collapse pipeline gated by a new 'collapse' option. This introduces three new files — minAngleMeshQuality.m, collapseMinAngleTriangles.m — plus fixed-node-aware relocation logic and Laplacian re-smoothing between iterations. Collapse is now measured against a minimum-angle quality metric (angle/60°), not a raw distance/aspect heuristic.

(B) Open (unclosed) vector features — "linestrings"/thalwegs — are now a first-class geometry type. Read_shapefile was rewritten to classify each shape as a closed polygon or an open linestring by endpoint-closure tolerance. geodata gains a linestrings property that is populated, plotted, and carried through the pipeline. This is the data-model groundwork for river/thalweg-constrained meshing and pairs with the new @CleanPSLG class.

(C) New @CleanPSLG class for planar straight-line graph conditioning. A self-contained class (mergeVertices, dropIntersectingEdges, pruneEncroachingEdges, dropNearbyEdges, preserveLongChains, deleteShortChains, cleanUnusedVertices) that conditions fixed-point/edge constraint sets before meshing. meshgen now routes pfix/egfix through a CleanPSLG filtering pipeline that preserves long contiguous chains while pruning nearby/parallel edges.

(C′) New high-fidelity meshing mode high_fidelity == 3. Base meshgen supported two modes — 1 (resample shoreline/island vectors to local resolution via mesh1d) and 2 (constrain the vector verbatim). new/ adds mode 3, which performs mode-1 resampling and then prunes the resulting breakline PSLG via the @CleanPSLG pipeline. This is the integration point that binds threads (B) linestrings and (C) CleanPSLG together, and the option exercised by Example_13_New_Zealand_High_Fidelity. Constraint assembly is refactored out of the inline meshing loop into a new private method generateBreaklineConstraints(), which additionally consumes the new geodata.linestrings (thalwegs) as breakline constraints.

(D) Weir / internal-barrier boundary handling extended. msh.make_bc gains weir-specific controls (weir_height, weirs_set_elev, barinht, barincfsb, barincfsp, plus silent/noninteractive/tolerance UX flags). edgefx weir-spacing enforcement is bounds-checked and its spacing amplitude changed (weir_spacing → weir_spacing*8). meshgen weir enforcement is exposed via a new 'enforceWeirs' constructor option.

(E) ADCIRC→WW3 export hardened; NWS13 (OWI NetCDF) forcing support added. writeww3 no longer assumes dense, fully-populated open-boundary arrays — a normalize_open_boundary_nodes helper tolerates sparse/empty/mis-sized opedat. A regression test (TestWW3Export) is added and wired into RunTests. Separately, Make_f15/writefort15 add the owiWindNetcdf (NWS13) namelist path.

Additional smaller robustness fixes: NaN-aware shoelace area, non-closed-loop-aware Get_line_edges, polyshape/polybuffer-based filter_polygon_constraints, and a field-name correction in extract_boundary (ibtype → ibtypee).

WPringle and others added 15 commits July 31, 2026 16:01
…n-linestring-aware helpers

Isolated, additive changes with no cross-file dependencies beyond
what's already in the repo:

- New minAngleMeshQuality/collapseMinAngleTriangles utilities for
  quality-driven triangle collapse (used by msh.clean('collapse',N)
  in a later commit).
- New fill_single_element_holes utility (not yet wired into any
  caller).
- writeww3 gains normalize_open_boundary_nodes to tolerate sparse,
  empty, or mis-sized open-boundary arrays; writefort15 special-cases
  the OWI (NWS13) namelist. TestWW3Export regression test added and
  wired into RunTests.
- extract_boundary: fix field name ibtype -> ibtypee to match the
  opendat/op convention already used elsewhere (e.g. readfort14).
- edgefx: bounds-check weir-spacing indices and increase weir spacing
  amplitude (weir_spacing -> weir_spacing*8).
- Get_line_edges: only add a closing edge for genuinely closed loops,
  so open polylines (thalwegs) don't get a spurious wrap-around edge.
- filter_polygon_constraints: rewritten on polyshape/polybuffer;
  primary-box retention flips AND->OR (keep edge if either endpoint
  is inside) to avoid dropping constraints near multiscale seams.
- shoelace: NaN-aware, now sums absolute sub-polygon areas instead of
  a single signed loop.
- drawedge2: trailing-whitespace cleanup only.
Read_shapefile is rewritten to classify each shapefile feature as a
closed polygon or an open linestring by endpoint-closure tolerance,
returning the open features in a new linestrings field. geodata gains
a matching linestrings property (populated from the constructor) and
renders them as dashed lines with start/end markers in plot().

This is the data-model groundwork for river/thalweg-constrained
meshing consumed by the meshgen high-fidelity mode 3 pipeline added
next.
…h collapse/weir extensions

@CleanPSLG (new): conditions a planar straight-line graph of
fixed points/edges via mergeVertices, dropIntersectingEdges,
pruneEncroachingEdges, dropNearbyEdges, preserveLongChains, and
deleteShortChains, preserving long contiguous chains while pruning
nearby/parallel edges.

@meshgen: constraint assembly is extracted from the inline meshing
loop into a new generateBreaklineConstraints() method, which now also
consumes geodata.linestrings (thalwegs) as breakline constraints.
Adds high_fidelity mode 3: resample shoreline/island vectors like
mode 1, then prune the resulting breakline PSLG through the CleanPSLG
pipeline (preserveLongChains -> dropNearbyEdges -> deleteShortChains
-> cleanUnusedVertices), gated by any(high_fidelity == 3). Also adds
an enforceWeirs constructor option. Fixed the collapse_thin_triangles
method, which referenced an undefined `mesh` variable instead of
`obj`.

@MSH: clean() gains a 'collapse' option (max iterations) that
iteratively collapses low-min-angle triangles via
collapseMinAngleTriangles, re-smoothing and relocating fixed points
between iterations, replacing the old inline
collapse_thin_triangles(p,t,db) call. make_bc() gains extended
weir/internal-barrier controls (weir_height, weirs_set_elev, barinht,
barincfsb, barincfsp) plus silent/noninteractive/tolerance UX flags.
Adds the owiWindNetcdf name-value option, which populates a new
owiWindNetCDF control namelist (NWS13File, NWS13ColdStartString) and
writes it via writefort15's no-'Control'-suffix OWI special case.
Also updates the wetDry namelist defaults (slim 1e9 -> 4e-4, windLim
false -> true) to match ADCIRC's documented wetDryControl defaults.
When the first cleaning pass leaves a poor-quality mesh, clean()
recurses to clean it again but only captured the recursive call's
mesh (obj = clean(obj,varargino(:));), not its quality metrics. The
outer call then returned its own pre-recursion qual, computed on the
mesh that was just discarded for being too poor -- e.g. a min quality
of ~0 from a degenerate element that the recursive pass had already
fixed.

This bug already existed identically in the Projection base; it just
wasn't often exposed because Example_1_NZ rarely needed a second
cleaning pass. It surfaces more now that mode-1 clean() no longer
collapses thin triangles unconditionally (collapse is opt-in), which
makes meshes more likely to need re-cleaning.
Read_shapefile's rewrite dropped a my_interpm(...,h0/2) densification
of the outer bounding-box polygon that the prior version always
performed. Per Keith, this was intentional: meshing works fine
without it and it's faster on large bounding boxes, so it stays off
by default. This adds it back as an explicit opt-in
('densify_outer',1) for cases that want the denser outer boundary.
Keeps TestSanity's existing baseline mesh statistics valid without
changing the library-wide default (densify_outer stays off elsewhere).
Three issues in meshgen.build's iterative refinement loop, found by
bisecting a TestEleSizes regression (91.30% -> 99.06% of vertices in
resolution bounds at grade=0.15, matching the old Projection
baseline):

1. `imp` (how often the periodic delete/split mesh-improvement pass
   runs) was hardcoded to 9999. Since itmax defaults to 100, that
   pass effectively never ran. Restored the old default of 10.

2. HIGH_FIDELITY_MODE detection was checking `nfix > 0` instead of
   `obj.high_fidelity{i}`, and the correct check was left commented
   out. Restored it, and -- since disabling periodic mesh improvement
   makes sense specifically when high-fidelity breakline constraints
   are in play -- made high-fidelity mode force imp back to 9999,
   rather than hardcoding it unconditionally.

3. delaunay_elim's egfix-empty path subtracts mean(pt1) before
   building the Delaunay triangulation (for numerical conditioning),
   but never added it back before converting TR.Points to lat/lon,
   silently displacing every point on every retriangulation call.
   This was the dominant cause of the regression: fixing only #1
   dropped the mesh to 4579 points (94.06% in bounds) because the
   improvement pass, once active, kept acting on displaced
   coordinates. Storing and re-adding the mean brings the result to
   13878 points / 98.85% in bounds, matching the old baseline.
imp is forced to 9999 in high-fidelity mode so the periodic
mesh-improvement pass doesn't disturb breakline constraints, but the
"quality is good enough, exit early" check below is gated by that
same imp cadence -- so in high-fidelity mode it never triggers and
the loop always runs the full itmax iterations. Documenting this
rather than decoupling it, since none of the current tests exercise
high_fidelity and the tradeoff hasn't been evaluated against one.
deltat defaults to 0.1 a few lines above; re-setting it to 0.10 when
nfix > 0 was a no-op (same value). Only Fscale actually changes in
that block. Pre-existing in the Projection base, not introduced by
this branch -- functionally identical, just dead code removed.
- Exposes 'imp' (iterations between periodic mesh-improvement passes)
  as a meshgen constructor option, resolved in build() once nfix is
  known: user-supplied value if given, else 9999 (effectively
  disabled) when fixed points are present, else 10.
- Changes the disabling condition from "high-fidelity mode is on" to
  "nfix > 0", matching how other convergence tuning (Fscale) is
  already conditioned in this codebase, and covering fixed points
  from weirs/user-supplied pfix as well as high-fidelity breaklines.
- HIGH_FIDELITY_MODE itself is unchanged and still gates
  heal_fixed_edges as before.

Verified the default (unspecified 'imp', nfix=0) path still produces
the same TestEleSizes grade=0.15 result as before this refactor
(13878 points, 98.85% in resolution bounds).
…e it for JBAY

meshgen now warns per box if geodata.weirs is non-empty but
enforceWeirs isn't set to 1, since weirPfix/weirEgfix are only pulled
into the mesh's fixed constraints when enforceWeirs is on -- otherwise
weirs are silently not enforced during meshing.

Enables enforceWeirs on Example_5b_JBAY_w_weirs.m, which specifies
weirs but wasn't turning this on.
mesh1d.m required boubox/box_num0 as positional args and assumed
fh0/h were always cell arrays/vectors, breaking the original
single-polygon calling style (no nesting) entirely. Moved boubox and
box_num0 into varargin, defaulting to [] and 1 respectively when
omitted, and wrap fh0 in a cell if it isn't one already. Existing
callers in meshgen.m are unaffected since the calling syntax doesn't
change.

mesh1d is private to @meshgen and can't be called from outside the
class (MATLAB blocks adding private folders to the path). Added a
static call_mesh1d passthrough so it can be exercised from tests
without duplicating the implementation.

test_1d_original.m now calls meshgen.call_mesh1d, has real pass/fail
assertions (point/edge count, convergence, edge-length bounds) instead
of just drawing a figure, and is wired into RunTests.m.
…mp gating and weir tolerance

Read_shapefile: features that are open by more than the closure
tolerance are still carried through mainland/inner classification as
pseudo-closed polygons (placeholder area, guaranteed to clear the
size thresholds), in addition to being recorded as linestrings. This
mirrors the previous Read_shapefile's behavior and stops such
features (e.g. a barrier island digitized as an open trace) from
being silently dropped from the mesh boundary when linestrings aren't
consumed (high_fidelity off).

meshgen.build: comment out the nfix>0 -> imp=9999 branch pending
further evaluation against high-fidelity meshes; imp now only forces
9999 when high-fidelity mode is on. Also restores the missing
trailing newline at EOF.

msh.make_bc: weir front/back coordinate matching tolerance is now
h0-relative (gdat.h0/111e3) instead of a fixed 0.001 degrees, since
crestline points should coincide almost exactly with fixed mesh nodes
when enforceWeirs is on.

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

Copy link
Copy Markdown
Collaborator Author

@krober10nd I have trouble with Example 5b with weirs. Will post issues soon

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