Skip to content

Sync main with anuga-community/anuga_core (3.3.10 → 4.1.0) - #301

Merged
stoiver merged 1523 commits into
mainfrom
sync/main-4.1.0
Oct 23, 2026
Merged

stoiver merged 1523 commits into
mainfrom
sync/main-4.1.0

Conversation

@stoiver

@stoiver stoiver commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

Brings this repository's main up to anuga-community/anuga_core@main, the 4.1.0 release point (dc8098a). GA main was last synced at 3.3.10 (#300).

The release tags 4.0.0, 4.0.1 and 4.1.0 have already been pushed to this repository, identical to those in anuga-community. As with 3.3.x, no GitHub Releases are created here. A bare tag doesn't trigger the PyPI publish workflow, which is gated on a published release. Publishing to PyPI, Docker and Zenodo is done from anuga-community.

What changes

1203 non-merge commits: the whole of 4.0.0, 4.0.1 and 4.1.0. The release notes in RELEASE_NOTES.md, and UPGRADING_TO_4.0.md, describe them:

  • 4.0.0: the 4.x restructuring.
  • 4.1.0: passive tracers, sediment transport, vegetation drag, the mesh size field, and validation reports with each release.

87 files present on GA main were removed upstream in that work, and they go with it.

Note on history

This is a merge rather than a fast-forward. GA main carries two merge commits that anuga-community/main does not: 9a7ef66 (#296) and c15aab5 (#300). Neither has content of its own. The diff from the merge base (98c3663) to GA main is empty, so nothing on GA main is lost by merging. This repository's ruleset blocks non-fast-forward pushes, hence the merge commit.

🤖 Generated with Claude Code

stoiver and others added 30 commits September 17, 2026 19:37
…own (#247)

meson.build computed the version at build time by running _git_version.py,
which runs `git describe`; an unpacked sdist has no .git, so a wheel built
from it reported 0.0.0+unknown while the sdist's own metadata was right.

A dist script (meson.add_dist_script, which meson-python's sdist build
runs through `meson dist`) now writes the version into
_version_static.txt at the root of the staged tree, and _git_version.py
reads that file, next to itself, before trying git. Precedence is the
ANUGA_VERSION override, then the static file, then git, then the fallback.
The file is gitignored, so a checkout never has one and git stays the
source there. git is also now run in the script's own directory rather
than the current one, since dist scripts run from the build directory.

Closes #247

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 9647606)
…d anuga

The actionable ImportError for a missing anuga/_version.py landed in
3257773 (#237); this adds the install-guide note the issue also asked for.

Closes #237

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit e45d296)
Release 4.0.1: install fixes on the 4.0 line
test_import_anuga_needs_no_triangulator compared the subprocess's whole
stdout to "None". On two Linux CI runners `import anuga` printed a UCX
transport warning from mpi4py's initialisation first, so the comparison
failed although TRILIB was None as expected. Compare the last line only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
test_import_anuga_needs_no_triangulator compared the subprocess's whole
stdout to "None". On two Linux CI runners `import anuga` printed a UCX
transport warning from mpi4py's initialisation first, so the comparison
failed although TRILIB was None as expected. Compare the last line only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 8fabdba)
Test: tolerate stdout noise from import anuga in the lazy-backend test
Test: tolerate stdout noise from import anuga in the lazy-backend test (main)
The analytical, experimental and behaviour cases under validation_tests/
only ran by hand. They now run every night in validation.yml, on Linux
with one Python, against main and develop, including the long HEC-RAS
cases, using the same environment recipe as the nightly build. The job
summary shows the VALIDATION SUMMARY table; the log and every plot the
cases write are kept as an artifact for two weeks; a failing scheduled run
opens or comments on a "Validation tests failing" issue. workflow_dispatch
runs any ref with a chosen -alg, with or without the long cases, which is
the way to validate a feature branch before merging.

The suite's default algorithm is DE1 (validation_utilities/parameters.py),
so that is the dispatch default too. Measured locally: 120 cases pass in
712 s at four threads without the long cases; the job timeout is three
hours.

Documented on the validation reference page.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The analytical, experimental and behaviour cases under validation_tests/
only ran by hand. They now run every night in validation.yml, on Linux
with one Python, against main and develop, including the long HEC-RAS
cases, using the same environment recipe as the nightly build. The job
summary shows the VALIDATION SUMMARY table; the log and every plot the
cases write are kept as an artifact for two weeks; a failing scheduled run
opens or comments on a "Validation tests failing" issue. workflow_dispatch
runs any ref with a chosen -alg, with or without the long cases, which is
the way to validate a feature branch before merging.

The suite's default algorithm is DE1 (validation_utilities/parameters.py),
so that is the dispatch default too. Measured locally: 120 cases pass in
712 s at four threads without the long cases; the job timeout is three
hours.

Documented on the validation reference page.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 4f19ee6)
…flow

The fprintf rewrite in #349 also rewrote the stderr echo inside
gpu_set_error, the very function it was routing the other sites to, into a
call to itself. Any error recorded through it, an out-of-range inlet index
for one, recursed until the stack ran out. The CI compiler warned
(-Winfinite-recursion) but nothing read the warning. Restore the fprintf.

A regression test drives a real error through the path: an inlet operator
with a triangle index beyond the mesh is rejected by the C init and the
wrapper raises with the recorded message. The static-analysis script now
passes -Winfinite-recursion as well, so this class fails the CI job.

On develop only; 4.0.1 predates #349.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`python -c "import anuga"` at the repository root imports the unbuilt
source tree instead of the installed package (#237), which is what failed
the first dispatch of the nightly validation run. Check from
validation_tests/ instead, as the test steps in conda-setup.yml do from
sandpit/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`python -c "import anuga"` at the repository root imports the unbuilt
source tree instead of the installed package (#237), which is what failed
the first dispatch of the nightly validation run. Check from
validation_tests/ instead, as the test steps in conda-setup.yml do from
sandpit/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 149022c)
Fix gpu_set_error recursing into itself; validation workflow import check
CI: run the validation workflow's import check from a subdirectory (main)
…idation page

examples/notebooks/notebook_sediment_transport.ipynb, executed with outputs
stored like the other notebooks, in two parts: scour of a sand bed in a
sloping channel (the setup page's shortest program, with the concentration
and bed change plotted on the mesh), then sediment settling out of still
water compared against the exponential reference, the same case as the new
sediment_settling validation test. Linked from the docs examples index and
the notebooks README, and the sediment setup page points at both.

The validation reference page gains a benchmark description for the case.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tion

A new analytical case under validation_tests/analytical_exact. A tank of
still water carries a uniform concentration of 100 micron sediment; with no
flow nothing is entrained, and the only exchange is deposition [D-1] with a
constant profile factor, so the concentration decays and the bed rises. The
depth falls as the bed rises, which feeds back on the depth-averaged
concentration; the reference integrates that coupled system with scipy to
machine precision (and reduces to the exponential h0/(d* v_s) decay when the
feedback is dropped). The case checks the deposition term, the sediment mass
balance [G-3] and the bed update [G-4].

Checked at every output time: cell-mean concentration and bed rise against
the reference (relative L1 < 1%; measured 1.6e-4 and 9.8e-5 with DE1), every
cell on the same curve (spread 0), a motionless free surface (0), and
sediment mass conserved between water column and bed (4e-8). Runs in a few
seconds, so the nightly validation job picks it up at no real cost. Listed
in the master validation report.

The scripts read the tracer and the time-varying bed straight from the sww,
since plot_utils.get_centroids collapses the bed to one slice.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sediment transport: a validation case and a notebook example
Wind_stress, Wind_stress_fast, Rainfall, Inflow, Barometric_pressure,
Barometric_pressure_fast and General_forcing are gone from
anuga.shallow_water.forcing, as announced in 4.0.0; Cross_section stays.
The anuga.Inflow/Rainfall/Wind_stress exports and the pyproject
filterwarnings that silenced their DeprecationWarnings go with them, along
with the stale Culvert_flow filters.

The only capability the operators lacked was a wind or pressure field read
from an sww/tms file (use_coordinates=False, the _fast classes).
Wind_stress_operator and Barometric_pressure_operator now take
use_coordinates=False with a file_function, evaluated for every point in
one vectorised step by the new evaluate_file_function_all_points helper.

Tests: test_forcing.py rewritten around the operators (24 tests, same
cases: constant/variable/file-driven wind, rainfall with polygons,
starttime, georef and default rates, wind/pressure fields from file
compared against the functions they were written from over an evolve),
the inflow/rain tests in test_shallow_water_domain.py ported to
Rate_operator.inflow/rainfall and their legacy-mode pins dropped.
All pass under both compute modes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Remove the deprecated forcing-function classes (4.1)
…rm solution

A tank of still water over a fixed bed that is flat on one half and slopes
at 1e-3 on the other, 0.5 mm sand, no initial load. With the depth-slope
closure the bed shear stress of a cell is a function of its depth and bed
slope alone, so the flat half sits below the Shields threshold (the
control) while the sloped half entrains via Smith-McLean [E-1]/[E-2] and
relaxes to c_eq = E/(d* v_s) along the closed-form exponential with time
constant h/(d* v_s). Checked per cell at every output time: relative L1
error 2.4e-4 in the interior, nothing entrained on the flat half, the end
state at equilibrium to 1e-5, and no motion of the surface, bed or momenta.

The bed is fixed on purpose: with it evolving, the far-wall cells (whose
reconstructed slope the limiter zeroes) do not erode while their neighbours
do, and the steps that open feed back into the depth-slope closure through
the reconstructed bed edge values, so no cell keeps a known slope and there
is no closed form. The eight kink and wall cells whose slope the limiter
reduces are compared against the slope they were actually given, which the
run records from the elevation edge values after the first step.

Also adds the Ferguson & Church (2004) entry the sediment reports cite but
the suite bibliography lacked, registers the case in the master report, and
documents it on the validation and sediment pages.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… own pass

The slope for [T-7]/[T-7e] was read from the bed/stage EDGE values by the
divergence theorem. The DE extrapolation rebuilds those every step as
limited stage minus limited height, so the erosion stress was whatever the
hydrodynamic limiter left: zero in the cells along a reflective wall (the
ghost mirrors the interior, so the reconstruction sees no gradient toward
it) and reduced wherever the limiter engages. On a uniformly sloping bed
the wall cells never eroded while their neighbours did.

S is now the least-squares gradient of the centroid values over the cell
and its neighbours, one-sided at boundaries, so a plane gives its slope in
every cell. Because that reads the neighbours and the source loop writes
the bed, the slope is computed in a pass of its own into a new per-cell
scratch array (sediment_slope_work), wired through the header, the Python
allocation, both Cython bindings and the device mapping. Taken inline it
was loop-order dependent and mode 1 and mode 2 disagreed at 1e-3 from the
first step, which test_the_depth_slope_shear_closure_agrees caught. The
bedload kernel's first pass is read-only and takes it inline.

Tests: the edge-formula mirror is replaced by a centroid mirror, plus a
regression on still water over a uniform slope, parametrised over bed
evolution, that checks every cell entrains at the rate its own depth and
the one slope give (wall cells included). Documented: 'depth_slope' with
bed evolution on is self-amplifying (erosion -> rougher bed -> steeper
local slopes -> more erosion); that is the closure, anugaSed contains it
with a global clamp, and it is why the validation case holds the bed fixed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ntrol fraction

With the slope now exact in every cell, the case no longer needs the flat
half or the special treatment of the kink and wall cells: the bed is one
plane, every cell is compared against its own closed-form curve at the
constructed slope (worst cell 2.4e-4), and the threshold check moves to a
second fraction of the same grain with tau_c* = 10, which must stay at
zero. The bed stays fixed, for the closure's own feedback rather than for
the discretisation; the analytic module and report say why.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sediment: exact slope for the depth-slope closure, and an erosion validation case
An Acknowledgements section in the README (which is also the PyPI project
description), a matching section on the docs Background page with an
anchor, a sentence on the docs landing page, a shared note included on
the four GPU pages (offloading, GPU install, compute modes, profiling),
and a line in the 4.0.0 release notes: the GPU and multicore solver was
developed in collaboration with the Centre for Development of Advanced
Computing (C-DAC), India, and the National Computational Infrastructure
(NCI), Australia, whose Gadi supercomputer provided the GPU systems it
was developed, tested and benchmarked on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…kh (C-DAC)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…on contributors

Names Samir Shaikh, Rutvik Gulhane and Parikshit (C-DAC) and Jorge Luis
Gálvez Vallejo (NCI); places the C-DAC collaboration in its catchment flood
prediction project (two-day forecasts) and the GPU hackathons from 2023.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… Das and Rutvik Gulhane

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
stoiver and others added 28 commits October 12, 2026 18:53
interior_regions vary the resolution by adding closed polygons to the
mesh, and Triangle honours every one of their segments. When the outline
is not a physical feature (an elevation band, a flood extent, a buffer),
its geometry still forces small triangles, and the smallest triangle sets
the timestep of the whole run.

size_field gives the maximum triangle area as a function of position and
adds no segments. It is applied through Triangle's user test (-u), which
meshpy exposes and mesh_engine.generate_mesh previously always passed as
None: a triangle is refined if its area exceeds the field at its
centroid, as well as any region or global maximum area. It is either a
callable f(x, y) in absolute coordinates or an anuga.Size_field raster.
anuga.size_field_from_regions builds a graded raster from the same
(polygon, maximum area) list as interior_regions, with a scanline fill
and one distance transform per distinct resolution.

relax_passes, and Pmesh.relax_worst_cells, then move the free vertices
of the worst 0.5% of cells (Laplacian moves and steps in eight
directions) wherever that raises min(radii, inradius) of the worst cell
around the vertex. Vertices on segments stay put and the triangles are
not reconnected. The kernel is a small Cython extension.

Both arguments go through create_pmesh_from_regions,
create_basic_mesh_from_regions, create_domain_from_regions and
Pmesh.generate_mesh, and are remembered on the Pmesh so they apply when
it is triangulated later by pmesh_to_mesh. Defaults leave existing
meshes unchanged.

An exception in the user test used to abort the interpreter from inside
Triangle; it is now held and raised after the triangulation returns. The
triangle backend has no user test, so a size field there is an error
asking for meshpy.

On a 1,407 km2 catchment (780,706 triangles, 30 m fine zone) this gives
min radii 4.69 m against 0.65 m from tuned interior regions; the field
takes 2.3 s to build, the mesh 2.9 s and 30 relaxation passes 1.1 s.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Avoids M_PI, which MSVC's math.h does not define by default.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A full run with DE_ader2 (otherwise run F settings) matches the DE1 run:
flood-mark RMS 0.367 vs 0.368 m, no mark moving more than 0.05 m, Pioneer
Rd gauge peak 4.09 vs 4.10 m at the same time and gauge RMS 0.31 m in both.
Mass is conserved (9.8e-11 relative). The time step is the same, but each
step is cheaper, so the run takes 6760 s instead of 9023 s (25% faster).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Towradgi report: results.tex for run F, and a figure script that matches it
Towradgi TOML: use DE_ader2 as the flow algorithm
Mesh resolution from a size field, and worst-cell relaxation
A pytest process that dies without running atexit handlers (SIGKILL, a
timeout kill, a native runtime abort) left every <file>.sww.lock taken
during the session in the working directory. An autouse fixture in the
root conftest now releases this process's locks after each test, so a
crash leaves at most the lock of the test that was running.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tests: release SWW writer locks after each test
Animating a tracer over more than one frame failed in every worker with
KeyError: 'tracer_<name>'.  The workers number each frame by setting the
plotter's counter attribute named in _COUNTER_ATTR, which had no tracer
entries -- and could not, since SWW_plotter keeps tracer counters in the
_tracer_frame_count dict.  Set that dict's entry for tracer quantities.

Also add the missing max_stage counter.  It was not reachable yet, since
max plots are single frames and are not sent to the workers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SWW_plotter read stage, xmomentum and ymomentum for every timestep and
derived depth, velocity, speed and speed x depth for every timestep too:
about eight arrays the size of the whole run, held for the plotter's
life.  anuga_sww_gui builds one plotter in the GUI and one in each of up
to four animation workers, so a long run needed many GB.

The (time, triangle) quantities are now FrameArrays (_sww_frames.py),
which read from the file as they are used: one timestep for a[frame], a
few triangles' timeseries for a[:, ids], and a stream over the timesteps
for min, max and max(axis=0), whose results are cached.  Anything else
-- numpy functions, arithmetic, other ndarray methods -- gets the whole
array, so existing callers keep working at the old cost.  The file stays
open between reads, reopens in a forked worker, and close() closes it.

On a synthetic 64,800-triangle, 400-step file (312 MB), anuga_sww_gui
gives the same colour limits, frames, timeseries and exports as before,
with peak memory of 431 MB rather than 1,239 MB in the GUI and 308 MB
rather than 2,051 MB in each animation worker.  Opening the plotter takes
0.01 s rather than 1.1 s.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
anuga_sww_gui: animate tracers in the parallel frame workers
…mory

# Conflicts:
#	anuga/utilities/tests/meson.build
SWW_plotter: read quantities a timestep at a time, not all at once
Older SWW files carry no timezone, and SWW_plotter failed opening them
with "NetCDF: Attribute not found".  Default to UTC, which is what the
SWW writer uses when the domain has no timezone and what sww_merge
assumes for files without one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SWW_plotter: open SWW files that have no timezone attribute
anuga_sww_gui draws max_* frames, and static elevation, in its own process,
where pyplot figures have a TkAgg toolbar.  Closing a figure destroys that
toolbar, and clearing the figure afterwards updates it:

    TclError: invalid command name ".!navigationtoolbar2tk.!button2"

so generating any max_* frame failed, with the PNG written but never loaded.
The save_max_*_frame methods cleared their figure after closing it; they now
just close it.  The figure cache had the same problem: every save_* closes
its cached figure, and the next frame cleared and reused it, so a second
elevation or max tracer frame in the GUI failed too, and plot_*_frame after
a save returned a closed figure that a notebook would not show.  A cached
figure is now reused only while pyplot still has it open.  In the worker
processes (Agg) this costs nothing measurable: 100 ms a frame either way.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SWW_plotter: never draw on a figure pyplot has closed (fixes max frames in anuga_sww_gui)
The Visualisation section gains a table comparing the ways to look at
.sww files: ANUGA Viewer, anuga_sww_gui, SWW_plotter, ParaView, the web
viewer, QGIS and Domain_plotter. A new page covers the anuga-paraview
package: the ParaView reader and the browser viewer for results on
remote or headless machines.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs: add a "Choosing a viewer" table and a ParaView / web viewer page
Brings 4.0.1 and the fixes made on main into the line that becomes
4.1.0, so main can take 4.1.0 as a fast merge. Most of main's changes
were already on develop through their develop-side PRs; what is new is
the dependabot bump of the actions in docker-publish.yml and lint.yml.

Conflicts resolved toward develop:
- tools/install_anuga_nvc.sh: develop's GPU probe supersedes main's,
  and develop already sets CXX to nvc++ as main's ff74678 does.
- docs/source/reference/validation.rst: both sides added the "Nightly
  run in CI" section, which merged into two copies.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds the TOML runner building culverts, weirs and sediment (#431), the
anuga_sww_gui and SWW_plotter fixes (#441, #443, #444), lazy SWW_plotter
reads (#442), the viewer docs (#445), TOML starttime for a burn-in
(#433), the once-per-run enquiry point warning (#417), and the results
of the independent size-field tests on #436.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Prepare 4.1.0: merge main into develop, and complete the release notes
Drops "(unreleased)" from the 4.1.0 heading, fills in the Thanks
section, and sets CITATION.cff to version 4.1.0, released 2026-10-23.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Release 4.1.0: date, thanks, and CITATION.cff
@stoiver
stoiver merged commit a10ac78 into main Oct 23, 2026
71 of 72 checks passed
@stoiver
stoiver deleted the sync/main-4.1.0 branch October 23, 2026 08:33
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