Skip to content

Merge the k-equation RANS branches (claude-RANS, claude-RANS-implicit) into ERF-Hazard - #405

Merged
hgopalan merged 21 commits into
ERF-Hazardfrom
merge-rans-into-hazard
Sep 11, 2026
Merged

Merge the k-equation RANS branches (claude-RANS, claude-RANS-implicit) into ERF-Hazard#405
hgopalan merged 21 commits into
ERF-Hazardfrom
merge-rans-into-hazard

Conversation

@hgopalan

Copy link
Copy Markdown
Owner

What

This brings the k-equation RANS work into ERF-Hazard now, ahead of its upstream review. It adds two merge commits, in this order:

  1. 23285ff2c merges claude-RANS, 12 commits, open upstream as One-equation k RANS: wall condition, closure fixes, terrain wall distance, Canonical_RANS regression cases erf-model/ERF#3971. It contains the one-equation k RANS closure fixes, the Exec/CanonicalTests/Canonical_RANS cases with their Python check scripts, seven gtests and the theory page Docs/sphinx_doc/theory/RANS.rst.
  2. 25bffd236 merges claude-RANS-implicit, 7 commits on top of claude-RANS. It contains:
    • the implicit vertical column solve of theta, KE, moisture and momentum under the anelastic integrator;
    • the TKE buoyancy-term fix;
    • NaN detection in check_for_negative_theta;
    • the Canonical_RANS/Timestep_Limits step sweep.

Both branches start from 489a2aa64, the development commit ERF-Hazard last synced, so nothing else from development comes in.

Conflicts and overlaps

  • CTestList.cmake: the only textual conflict was Tests/CTestList.cmake, where both sides appended a section at the same place. The fire and dust smoke tests and the RANS cases are both kept.
  • ERF_MakeNewLevel.cpp and ERF_MakeNewArrays.cpp: 14 files changed on both sides and merged automatically. I reviewed the ones that interact.
  • The other shared files: ERF_TurbStruct.H, ERF_SetupDiff.H, ERF_ComputeTurbulentViscosity.cpp, ERF_IndexDefines.H, ERF_PlotfileSelection.H, ERF_SlowRhsPost.cpp and Tests/Unit/CMakeLists.txt. ERF-Hazard's changes there are fire, dust and MRF additions, the PBL full-column check and the theta_ref FPE guard. They sit beside the RANS changes, with no shared lines.

Behaviour changes on ERF-Hazard

change who sees it
kEqn fixes: the wall value of k is held through every stage, Rt smoothing, the unstable length, and the Poisson wall distance half a cell off centre kEqn runs only
The TKE buoyancy term now uses the closure's cell-centred -K_h dtheta/dz. The explicit diffusion operator used to overwrite it with the face flux: with the implicit solve that zeroed it above the first cell, and in explicit runs it sat half a cell low. every Deardorff and kEqn run. That is 23 decks, none of them fire decks. The gold tests ABL_MOST and Deardorff_stationary still pass.
check_for_negative_theta aborts on a NaN rho or rho theta runs that used to carry on with NaN
erf.vert_implicit = true, or an explicit erf.vert_implicit_fac, now turns the column solve on under anelastic. The default stays explicit. opt-in. No existing anelastic deck sets it; only the new RANS convective deck does.
Level-0 grids are not split in z while the implicit solve is on (the default for compressible runs). decks with amr.max_grid_size_z below nz and x/y uncapped now run on whole columns instead of stopping at the #400 check

New options from erf-model#3971, all off by default: erf.tke_floor, erf.implicit_tke_dissipation, erf.rans_consistent_diffusivities, erf.rans_lscale_from_pblh with erf.rans_lscale_min, and erf.wall_dist_type = poisson | terrain_height. There are also new plot variables Rt, cmu and cmu_prime.

The largest stable step on the RANS-like Timestep_Limits column (dx = 800 m, dz = 5 m), with the first failing step in brackets:

closure explicit anelastic implicit anelastic implicit compressible
kEqn 2 s (4) 256 s (512) 64 s (128)
Deardorff 0.25 s (0.5) 512 s (1024) 64 s (128)
MRF 0.5 s (1) 256 s (512) 8 s (16)

MRF under implicit compressible stopping at 8 s is not investigated.

Tests

The build is Release with MPI, fire, dust and FFT, all warnings on, and has no warnings outside Submodules.

  • Unit tests: ctest -L unit -LE parallel (536) and ctest -L parallel (16) pass.
  • Regression: ctest -L regression passes 122 of 122. That covers the 10 RANS cases, fire smoke and abort tests, the tiling, box and fine z-split parity tests, and the gold tests including ABL_MOST*, ABL_MOST_IMP_DIFF* and Deardorff_stationary.
    • The first pass failed 80 tests because only erf_exec had been built: amrex_fcompare and the checker executables were missing. After building every target, those 80 pass.
  • Step sweep: ctest -L dt_sweep passes 3 of 3. It is kept out of regression because the CI runs that label in Debug.
  • Lint: codespell is clean on the changed files, and so is git diff --check. The exceptions are blank lines at the end of PLAN.md and erf_plotfile.py, which the style job does not check.

Later, upstream

erf-model/ERF squash-merges pull requests, so erf-model#3971 will land in development as one new commit, not these SHAs.

  • Syncing: when ERF-Hazard next syncs development, identical hunks merge cleanly; only lines changed during review would conflict.
  • The implicit branch: its seven commits then go upstream as their own PR:
    git rebase --onto upstream/development claude-RANS claude-RANS-implicit.

🤖 Generated with Claude Code

hgopalan and others added 21 commits September 9, 2026 15:41
…CTest hook

Adds Exec/CanonicalTests/Canonical_RANS with the eight-phase plan for a
minimal working one-equation k RANS (Axell & Liungman 2001), a neutral
Ekman-layer deck on flat ground under MOST, and a check script that reads
the plotfile with a standard-library-only AMReX reader and compares
planar-averaged numbers against stated targets with tolerances.

add_test_rans in Tests/CTestList.cmake runs a 40-step smoke deck and then
the script; the script's exit code is the verdict. RANS_Neutral_ABL_Flat
passes on 1 and 2 ranks, which agree to 2e-15.

The 12 h run records the "before" state in RESULTS.md: the log law holds
within 7 %, but the wall cell keeps only 80 % of its Dirichlet k through
a step and KE(0)/u*^2 is 2.60 against the AL01 value 3.23. Phase 2 fixes
the wall condition.

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

With erf.dirichlet_k the surface layer writes the AL01 Eq. 16 value of k
into the first cell of S_old once per step. The logical BC for RhoKE was
then switched to ext_dir, so the diffusion applied a one-sided stencil
against a zero ghost cell and drained a fifth of the wall k before the
step ended; the converged wall k sat 20 % below the AL01 value and the
implicit KE solve was silently switched off by the BC sanity check.

Keep foextrap for RhoKE at a surface-layer wall (ghost cell equals the
first cell, zero flux through the wall face), restore the first-cell
value from S_old after every RK stage in erf_slow_rhs_post, and pin the
bottom row of ImplicitDiffForStateLU_{N,S,T} for RhoKE so the implicit
solve treats it as a Dirichlet neighbour.

Neutral_ABL_Flat, 12 h: KE(0)/u*^2 = 3.232 against 3.2325 (was 2.60),
k_start/k_end = 1.000 (was 1.244), interior profile unchanged, restart
bit-exact, and the wall-cell check now runs in the CTest smoke mode. The
implicit path was exercised with a compressible variant since anelastic
disables it. erf.dirichlet_k keeps its default of false.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Without the Dirichlet wall value the first cell cannot resolve the
near-wall shear production and k there settles at about half the
Axell & Liungman equilibrium u*^2/Cmu0^2 (12 h neutral run: 1.59 against
3.23), while the mean wind still follows the log law because MOST
supplies the stress. Warn at startup in that configuration; the default
stays false. Every deck that enables kEqn now sets the flag (Askervein
added; the canonical deck already had it).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…table length, unit tests

Factor the Axell & Liungman closure relations into ERF_RANSClosure.H
(namespace AL01) so ComputeTurbulentViscosityRANS and the unit tests
evaluate the same expressions; the refactor is bit-identical to phase 2.

Validate Cmu0, Cb, max_geom_lscale, Rt_crit <= 0, Rt_min < Rt_crit and
Rt_min > -3.6 (the poles of Eqs. 31-32) at input time. Add
erf.tke_floor (runtime floor on k; distinct from erf.tke_min, the initial
value) and the opt-in erf.implicit_tke_dissipation, which drops the
explicit sink from the source and divides the update by 1 + dt c with
c = diss_old / (rho k)_old, half-weighted on the anelastic stage 1.

Two robustness changes came out of the new gtests and the review against
the paper and the Kynema KLAxell implementation: the Burchard & Petersen
smoothing is rewritten as Rt_crit + a x / (x + a), the same function
without the cancellation that returned -2 for Rt = -1e16 and +1.4e14 for
Rt = -1e30; and the unstable length evaluates Eq. 28 once with the
smoothed Rt from the geometric length, bounded by about 1.31 l_g, instead
of the two-pass corrector that iterated the fixed-point map of Eq. 26,
which has no fixed point in strong convection (AL01 p. 78).

Also: fill the derived diffusivity components on the tilebox only (the
wrapper fills ghost cells), drop unused parameters, correct the surface
heat-flux index in the TKE-source comment, and use the level's own
periodicity in the wall-distance Poisson solve.

Neutral_ABL_Flat: all checks unchanged to the digits shown; the 4x-dt
run showed the anelastic integrator's explicit vertical diffusion is the
real time-step limit (dz^2/2K about 19 s), not the dissipation; a
compressible dt = 60 s pair with implicit vertical diffusion passes with
the dissipation explicit and implicit.

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

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ap, stable and convective decks

Two opt-in closure options: erf.rans_consistent_diffusivities makes the
horizontal heat and every scalar and moisture diffusivity follow the AL01
scalar stability function (rho cmu' sqrt(k) L) like the vertical heat
diffusivity, instead of Pr_t and Sc_t times the eddy viscosity; and
erf.rans_lscale_from_pblh caps the geometric length at kappa 0.1 zi from
the surface layer's MYNN25 PBL-height diagnostic, clamped to
[erf.rans_lscale_min, erf.max_geom_lscale]. The neutral deck is
bit-identical with both off.

Two new Canonical_RANS cases with numeric check scripts and CTest smoke
entries: Stable_ABL_Flat (GABLS1, 9 h) passes every check first time,
u* 0.244, jet 1.23 Ug at 154 m, depth 134 m; Convective_ABL_Flat
(MS94-B sounding, 0.24 K m/s, 4 h) closes the column heat budget to
0.02 %, keeps the inversion at 1020 m and carries the AL01 buoyancy term
at the wall. It runs at dt = 2 s: at 5 s the eddy viscosity reaches the
explicit vertical-diffusion limit of the anelastic integrator (40 m2/s)
and the run aborts, the same limit phases 9 and 10 address. The
mixed-layer spread tolerance (2 K) reflects the superadiabatic lapse a
local-K closure keeps without countergradient transport; Kynema's
KLAxell has none either and its Prandtl function matches cmu'/cmu.

Shared check code moves to rans_checks.py; the dissipation-lag check
allows 10 % in smoke mode (early transient) and 5 % in physics mode.

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

The Poisson wall distance (Tucker 2003) took the cell's low-face fluxes
as its gradient, half a cell off centre in every direction, which
overstates |grad phi| by dz/2 and shortens every distance by z dz/(2H) on
a flat mesh, 0.78 % for 64 cells. Form a cell-centred gradient instead
from centred differences with the cell-centre terrain metrics (chain rule
for a mesh deformed in z); it needs one ghost cell of phi and the cell's
own nodes. On a flat fitted mesh the distance is now exact to 1e-6 m
above the first cell (1.5 cm there, the odd-reflection Dirichlet ghost);
on the ridge the mean error drops from 1.5 % to 1.0 %.

New Canonical_RANS case Neutral_Hill_2D: periodic Witch of Agnesi ridge
(h 100 m, L 500 m) on a terrain-fitted mesh under MOST, one cell wide in
y, anelastic with the MLMG projection. Its check script compares the wall
distance with the exact distance to the curve, and after 6 h measures the
crest speed-up (0.55, 0.45, 0.39 in the lowest cells against the
Jackson-Hunt estimate 0.4) and the upstream log law (within 4 %). A
flat-fitted variant of the same deck (prob.hmax = 1e-6) is the second
CTest entry; both pass. erf_plotfile.py gains a full-field reader for the
terrain checks.

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

Add erf.wall_dist_type = terrain_height: the height above the local
surface projected on its normal, no linear solve (after the amr-wind
immersed terrain and Kynema). It is exact to 1e-10 on a flat fitted mesh
and closer to the true distance than the Poisson solve on both hills
(mean 0.02 % and 0.01 % against 1.0 % and 0.3 %); the hill decks use it
and three _Poisson CTest variants keep the Poisson path covered. The
Poisson solve is now posed in the positive-definite form MLABecLaplacian
documents (same iterates as before).

New case Neutral_Hill_3D: a periodic radial Witch of Agnesi hill at
dx = dy = dz = 40 m; after 4 h the crest speed-up is 0.41, 0.28, 0.22 in
the lowest cells against the axisymmetric estimate 1.6 h/L = 0.32, and
the upstream log law holds within 10 %. Restart is bit-exact on the 2D
and 3D terrain decks; the Askervein deck runs clean.

Recorded in the plan, outside its scope: on a 3D fitted mesh with dz
different from dx the initial field's divergence is a deterministic
1.788e139 before the first projection and the run aborts, independent of
the closure, the boundaries, the terrain source, stretching, the box
layout and the projection solver, while Askervein runs at the same aspect
ratios; a separate uninitialised read in the w boundary fill trips the
invalid-operation trap in the first advance. Reproducer in PLAN.md.

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

Add Docs/sphinx_doc/theory/RANS.rst, describing the Axell & Liungman
one-equation closure as implemented: the eddy viscosity and diffusivity,
the k equation and its dissipation, the geometric and stratified length
scales with the bounded unstable form, the stability functions with the
cancellation-free smoothing, the wall condition on k, the two wall
distances on a fitted mesh, the limitations and an input table. Link it
in the THEORY toctree, document the Canonical RANS regression cases in
RegressionTests.rst, and add a top-level README for the case directory.

Three new EddyDiff components, written by the closure, expose the
smoothed turbulent Richardson number and the two stability functions as
the plot variables Rt, cmu and cmu_prime; the neutral smoke check
verifies them against AL01 Eqs. 31-32 to round-off and Kmv against
rho cmu sqrt(k) L.

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

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

Opt-in: erf.vert_implicit = true (or an explicit erf.vert_implicit_fac) in the
inputs turns the column solve on for the scalars under anelastic; without it
the run stays fully explicit as before, and a banner says how to enable it.
Momentum stays explicit (its solve lives in the substepping path).

The anelastic second stage is trapezoidal and rebuilds the first-stage
tendency from the state difference, which already carries the first
stage's implicit increment, so the pre-stage theta solve and the
post-stage KE/moisture solves now act with half the step on that stage.
With the full step the convective column gained 1.45 times the surface
heat flux per step (stage decomposition with vert_implicit_fac 1 0 0 /
0 1 0 / 0 0.5 0); it is 0.975 for every combination now.

The column solves take each box's z extent as the column, so level 0 is
no longer decomposed in z when the solve is on and init aborts if any
box is cut in z (this also closes a silent error in the compressible
path). check_for_negative_theta now trips on NaN, which it let through.

The convective RANS deck runs at dt = 5 s with the solve on (it aborted
at 5 s explicit); a CTest entry runs the neutral deck at dt = 10 s
implicit.

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

The k-equation closures (Deardorff, Axell-Liungman) write -K_h dtheta/dz
from the cell-centred gradient into Hfx3 at the start of the step and the
TKE source reads it there (AL01 Eq. 15; Kynema forms it the same way).
Since "Optimize Post" (erf-model#2310) the explicit diffusion operator overwrote
that value above the first cell with the face flux times the explicit
fraction, so with vert_implicit_fac = 1 the buoyancy production and
destruction of k vanished everywhere but the first cell (the convective
RANS case carried a third of its KE and the mean theta was 1.3 K off),
and even fully explicit the term sat half a cell low. The overwrite is
removed; the surface layer still writes Hfx3(i,j,0).

Verified by restarting the neutral RANS deck at 6 h and taking one step
implicit and explicit: KE differed by 1e-6, growing linearly in time and
independent of dt, only when the buoyancy term was present; after the
fix the two agree to 8e-9 in one step and to 5e-7 after 10 s, and over
the full runs to 3e-4 m/s (neutral 12 h), 4e-5 m/s (stable 9 h) and
3e-3 m/s (convective 4 h, dt 5 s implicit against dt 2 s explicit). The
Deardorff and implicit-diffusion gold tests pass. Theory page, plan and
results updated for phase 9.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Phase 9 left momentum explicit under anelastic on the grounds that its
solve lives in the substepping path. That was wrong: with
implicit_before_substep, which is forced true when there is no
substepping, ERF_ImplicitPre.H folds the momentum solve into the slow
tendency before the stage update, so under anelastic the momenta are
diffused and then projected, which is the order the divergence
constraint needs. Removing the flag is the whole change; the half-step
on the trapezoidal second stage from phase 9 applies to the momentum
solve unchanged.

erf.vert_implicit = true under anelastic now covers u, v, theta, k and
moisture. w stays explicit unless built with ERF_IMPLICIT_W, as in the
compressible path, and the solve remains opt-in under anelastic.

Neutral deck, 12 h: dt = 5, 10, 20, 30 and 60 s all pass every physics
check, and at 60 s (6 times the explicit limit) the profiles are within
2.9e-3 m/s of the explicit dt = 5 s run. Compressible at dt = 5, 20 and
60 s lands within 1.8e-3 m/s of the same reference. The convective deck
reaches dt = 20 s, ten times its original explicit step, and the stable
deck dt = 8 s. On the 2D hill the advective Courant number binds first
(it fails at dt = 3 s identically with the solve on and off), and there
the terrain-fitted momentum solve reproduces the explicit answer to
1.2e-4 m/s. Restart at dt = 20 s is bit-exact.

Recorded in PLAN.md and RESULTS.md, with one caveat found while
verifying: the implicit theta solve makes the answer sensitive to the box
decomposition at the 1e-5 level in wind, because the column tridiagonal
spreads a one-ulp difference over the whole column in a single step and
the near-neutral buoyancy term amplifies it. The k and momentum solves
are decomposition invariant to round-off on their own, and with
Smagorinsky instead of the k equation the theta solve stays at 1e-12.
No physics check moves. plot_dt_overlay.py draws the comparison.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…y and Deardorff too

The column solve is in the dycore, not the closure. On the neutral
geometry under anelastic, explicit Smagorinsky fails at dt = 20 s and
explicit Deardorff already at dt = 5 s, while both run at 5, 20, 60 and
120 s with erf.vert_implicit = true.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ressible

Neutral deck, 12 h: anelastic passes every check up to dt = 240 s and
fails at 480 s; compressible passes to dt = 120 s and loses seven checks
at 240 s. At their best passing steps the anelastic run costs 1 s of wall
time against 433 s, since it pays one FFT solve per step rather than
thousands of acoustic substeps. Before phases 9 and 10 the ordering was
inverted, the compressible path having had the column solve all along.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The phase 10 note blamed the k equation's buoyancy term. That is wrong.
Chasing it properly: it reproduces on a single rank with four boxes, so
MPI reduction order is not involved; it survives local MOST averaging,
a nulled buoyancy term, dirichlet_k off, a TKE floor, and Deardorff in
place of the k equation; two identical runs agree bitwise; and
fab.init_snan with the invalid trap armed finds no uninitialised read.

What holds up: the deck stays exactly uniform horizontally, so the two
decompositions differ only through the round-off of the projection,
which leaves a w of order 1e-19 where w should be zero. The state first
differs at step 3 by one ulp of rho theta and grows before saturating.
The growth needs both the implicit solve and the anelastic projection
(the compressible path with the same solve stays at 5e-12) and scales
with the implicit increment: 9e-7 in wind at dt 5 s, 1.3e-5 at 20 s and
3.6e-4 at 60 s, while theta alone at vert_implicit_fac = 1 0 0 is clean
at one ulp. A scalar amplification analysis of the fold-in gives a
factor in (0,1] for every mode, so it is not the theta update alone.

Not fixed. The mechanisms ruled out are recorded so the search does not
have to be repeated. It moves no physics check; at dt 60 s the spread is
3e-5 relative against an implicit-explicit difference of 2.5e-4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
New case Timestep_Limits: a neutral column on a RANS-like grid (4 x 4 x 200,
dx = 800 m, dz = 5 m) that measures the largest stable step of kEqn,
Deardorff and MRF under explicit anelastic, implicit anelastic and implicit
compressible integration. sweep_dt.py spins each closure up for 1 h at
dt = 5 s with the implicit solve (anelastic and compressible checkpoints),
restarts over a ladder from 0.125 s to 1024 s for 200 steps per rung, and
reports the largest rung below the first failure.

Largest passing step (first failure):

  closure    explicit anel.  implicit anel.  implicit comp.  dz^2/(2K)
  kEqn       2 (4)           256 (512)       64 (128)        2.13
  Deardorff  0.25 (0.5)      512 (1024)      64 (128)        0.34
  MRF        0.5 (1)         256 (512)       8 (16)          0.58

The checks require the explicit step within a factor 2 of dz^2/(2K) (0.94,
0.74, 0.86) and both implicit integrators at 8 times it or more. With the
solve switched off in the implicit anelastic mode the MRF sweep fails that
check. MRF under implicit compressible stops at 8 s; the 16 s run keeps
going with |u| in the thousands of m/s, with ERF's own substep count as
with pinned substeps. Not investigated.

Two things the driver has to do: lift erf.change_max, which caps step growth
at 10 % per step even with erf.fixed_dt, so a restart would otherwise ramp
up from 5 s; and pin erf.fixed_mri_dt_ratio, because the substep count sized
from a state that is going unstable reaches billions and the run hangs.
Restarts from the spin-up checkpoints are bit-exact for all six.

CTest entries RANS_Timestep_Limits_{kEqn,Deardorff,MRF}, 60-90 s each on one
rank in Release, labelled rans and dt_sweep but not regression, which the CI
runs in Debug.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…and Canonical_RANS cases

Brings in the twelve commits of claude-RANS (open upstream as erf-model#3971) ahead of its upstream merge; both branches share the development base 489a2aa. Tests/CTestList.cmake conflicted only because both sides appended a section at the same place; the fire and dust smoke tests and the Canonical RANS cases are both kept.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…on under anelastic and the time-step sweep

Brings in the seven commits of claude-RANS-implicit on top of claude-RANS: the implicit column solve of theta, KE, moisture and momentum under the anelastic integrator (opt-in with erf.vert_implicit = true), the TKE buoyancy term using the closure's cell-centred heat flux, NaN detection in check_for_negative_theta, and the Timestep_Limits dt sweep. It merged without conflicts. Its level-0 full-column abort in update_diffusive_arrays duplicates ERF-Hazard's check_stacked_boxes_in_z, which runs first, and it does not touch fine levels, so the whole-column joining of fine boxes from #404 is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hgopalan
hgopalan merged commit ee0b35b into ERF-Hazard Sep 11, 2026
14 of 48 checks passed
@hgopalan
hgopalan deleted the merge-rans-into-hazard branch September 11, 2026 21:43
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