Skip to content

Miura Weno scheme - #1396

Draft
jcanton wants to merge 22 commits into
mainfrom
miura_weno
Draft

Miura Weno scheme#1396
jcanton wants to merge 22 commits into
mainfrom
miura_weno

Conversation

@jcanton

@jcanton jcanton commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

(Claude) port of Andreas' Weno advection scheme

jcanton and others added 22 commits July 14, 2026 14:15
Fix from Andreas Jocksch: the distance-vector allocation and the
dimension passed on to compute_z_lsq_mat_c/compute_lsq_pseudoinv must be
the actual stencil size, not the allocation size. Latent on main (both
are 3 for the linear reconstruction) but required for 9-point stencils.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Row 8 listed cell 3 as an outer neighbor, but the outer neighbors of
direct neighbor 9 are {6, 12} per c2e2c_table (cell 3 is not adjacent to
any neighbor of cell 8): the entry must be 6.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port the torus branch of the candidate least-squares setup from ICON
(mo_intp_coeffs_lsq_bln.f90, icon-exclaim branch transport_ajocksch):
9-point stencil construction with orientation swap, torus moments and
translated (hat) moments, the 27 quadratic and 3 linear candidate
pseudoinverses with the live zero patterns and l_weights_s correction,
plus scattering of the Fortran-ordered coefficients onto the C2E2C and
C2E2C2E2C connectivity slots for the runtime stencils.

Pure init-time numpy; unit tests run on the SimpleGrid tables and a
synthetic periodic equilateral-triangle torus patch, including exact
quadratic/linear reconstruction and an SVD conditioning gate (worst
singular value ratio 2.3e-2 across all cells and candidates).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two runtime gt4py stencils consuming the Task 1 WENO coefficients, one
candidate slice at a time (a later task loops them 27x):
- reconstruct_quadratic_coefficients_weno_candidate: per-candidate
  quadratic reconstruction on cells, reassembling the 9-point pseudoinverse
  dot products from the direct (C2E2C) and butterfly (C2E2C2E2C) row sets.
- accumulate_weno_candidate_flux_weights: per-candidate WENO smoothness
  weighting on edges, gathering the upwind cell's coefficients via E2C and
  accumulating the weighted sums and weight sum.

Faithful to mo_advection_hflux.f90 2447-2511. StencilTests with numpy
references pass on embedded and gtfn_cpu on SimpleGrid.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cell-domain gt4py stencil for the ihadv_tracer=102 reconstruction
(mo_advection_hflux.f90 upwind_hflux_miura_weno, 983-1019): blends the 3
linear least-squares candidates -- candidate i drops direct neighbour i --
by the inverse-square smoothness weight of each candidate gradient. The
zonal/meridional candidate pseudoinverses are slices of the Task-1
(n_cells, 3, 2, 3) array over the C2E2C rows. The conservative branch
(llsq_lin_consv) is not ported (Fortran default off), so the constant
coefficient stays p_cc.

StencilTest with a literal-Fortran numpy reference passes on embedded and
gtfn_cpu. Adds a numpy consistency unit test: on a linear field the 3
candidates coincide, so the WENO blend reduces to the plain LSQ gradient.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add the AdvectionWenoLinearState (6 zonal/meridional candidate
pseudoinverses over C2E2C) and the SecondOrderMiuraWeno tracer flux, which
mirrors SecondOrderMiura -- same cell/edge domains and reused flux stencil
and limiter hook -- but swaps the SVD linear reconstruction for the WENO
blend. Adds the HorizontalAdvectionType enum values LINEAR_2ND_ORDER_WENO
(102) and QUADRATIC_3RD_ORDER_WENO (103); the factory builds
SecondOrderMiuraWeno for 102 (raising a ValueError when the state is
missing) and raises NotImplementedError for the not-yet-wired 103.

Unit tests cover the from_fortran_dict int->enum mapping and the factory
error paths. The existing LINEAR_2ND_ORDER path is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When the configured horizontal advection type is WENO (102/103), compute
the init-time linear WENO pseudoinverses from the grid C2E2C table, cell
centers and torus domain size, wrap them into an AdvectionWenoLinearState
and pass it to the advection factory. Guarded to a torus grid on a single
rank (the sphere coefficients are not ported and the coefficient halo
exchange is not implemented); the non-WENO path is byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New analytical initial condition for a tracer-advection-only torus
experiment: constant zonal wind u0 in a hydrostatically balanced base
state (gauss3d-style) plus a constant circular qv disc. Since the
dycore is off, the IC also prescribes the advection driving fields the
dycore would normally provide: vn_traj = vn, mass_flx_me from
edge-interpolated rho and ddqz_z_full_e, mass_flx_ic = 0 and
airmass_now/new = rho * ddqz_z_full.

The driver now allocates the tracer advection states before the IC and
threads the target fields through initial_condition.create as a plain
dataclass of fields (keeping icon4py.model.common free of advection
imports); the other ICs ignore it. While at it, allocate mass_flx_ic
with the KDim + 1 extension: the advection granule reads it at
KDim + 1, and the previous full-level allocation made gtfn read out of
bounds (garbage in the bottom tracer level).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add the active tracer fields (qv, ...) to the driver's output pipeline:
a tracer_state_to_dataarrays assembler using the common tracer CF
metadata, an output_variables helper that extends the default variable
list with the active tracer names, and the corresponding store call in
the driver. Runs without tracers are unchanged (empty tracer state adds
nothing and the variable list stays the default).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Integration test for the tracer-advection-only experiment on the
torus_50000mx5000m_res500m grid (no serialized data): builds the
ExperimentConfig programmatically with dycore and diffusion off,
verifies the grid is fully periodic, runs the driver for 24 steps at
CFL ~ 0.3 and checks that the initial output frame equals the sampled
disc, that total tracer mass is conserved to 1e-12 and that the final
qv matches the analytically translated disc. Observed relative L2
errors: 0.328 for linear WENO (ihadv=102) and 0.481 for plain miura
(ihadv=2); the tolerances are frozen at ~1.5x these values, while a
non-moving tracer produces sqrt(2) ~ 1.41. xfails on the embedded
backend like the neighboring driver tests (concat_where in the metrics
factory).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reads the driver's icon4py_output_*.nc and the *_ugrid.nc companion and
writes two figures into the output directory: tripcolor panels of the
initial, final, analytically translated and difference qv fields
(periodic seam triangles masked), and time series of the relative L2
error versus the translated disc and of the total tracer mass drift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port of prep_gauss_quadrature_q_miura3 (mo_advection_quadrature.f90
561-703): 4-point Gauss-Legendre quadrature of the monomials
{1, x, y, x^2, y^2, xy} over the parallelogram departure region. The
constant Jacobian (a quarter of the parallelogram area) is folded into
the weights z_wgt = 0.25 * wgt, so the sums are the area AVERAGES of
the monomials and, unlike the plain/cubic variants, there is no
Jacobian determinant and no dreg_area output. Argument conventions
mirror prepare_numerical_quadrature_for_cubic_reconstruction; the test
uses the live gaussq_2d_o2 shape functions and weights against a
literal-port numpy reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port of the final flux write of upwind_hflux_miura3_weno
(mo_advection_hflux.f90 2514-2521): the accumulated candidate-weighted
coefficients are divided by the accumulated smoothness weights (before
the dot product, as in the Fortran), dotted with the miura3 quadrature
vector and multiplied by the mass flux. No division by the
departure-region area: the miura3 quadrature already averages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port of calculate_planar_distance_at_edge (mo_intp_coeffs.f90
2278-2406): torus-periodic separation vectors from the edge midpoint to
the E2C cell circumcenters and E2V vertices, rotated into the
edge-local (primal normal, dual normal) frame - ICON's pos_on_tplane_e
components 1:4, including the edge-vertex components 3:4 that
compute_ffsl_backtrajectory needs and that no icon4py code produced so
far. Unlike interpolation_fields.compute_pos_on_tplane_e_x_y_torus this
is the full projection without the equilateral shortcut. The remaining
backtrajectory inputs need no new code: primal/dual_normal_cell equal
the per-edge normals on both E2C slots on the plane torus (cvec2gvec
identity) and lvn_sys_pos is computed at runtime by the existing
counterclockwise-indicator stencil.

The synthetic torus patch moves from the weno unit tests to the shared
tests utils and grows edge tables (E2V, E2C, edge centers, primal/dual
normals, tangent orientation) with ICON's conventions: the primal
normal points from cell 1 to cell 2, the stored tangent is
tangent_orientation * normalize(v2 - v1). Unit tests check the derived
closed forms on the equilateral patch: cell offsets at -/+ the apothem
a/(2*sqrt(3)) with zero tangential part (matching the existing
equilateral shortcut), vertex offsets purely tangential at +-a/2 and
ordered by the tangent orientation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closed-form test of the never-wired backtrajectory stencil on the
synthetic equilateral torus patch with constant vn = c > 0, vt = 0 and
the new init-time torus geometry: the departure-region vertices must
equal the edge vertices and their images translated upstream by c*dt
along the edge normal, expressed relative to the upwind (cell 1)
circumcenter in the global frame, ordered counterclockwise per
lvn_sys_pos (btraj_dreg, mo_advection_traj.f90 363-773). The expected
values are derived from the unwrapped patch triangles only -
independent of the edge-local-frame arrays under test - and the output
loop is additionally checked to be counterclockwise with shoelace area
edge_length * c * dt. Sign mutations (flipped primal normal, inverted
lvn_sys_pos) make the test fail, so it pins down the frame conventions
of both the geometry and the stencil. Runs on embedded and gtfn_cpu.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Literal numpy port of the upwind_hflux_miura3_weno cell loop (f90
2443-2546) compared against the composed gt4py stencils (reconstruct x27,
accumulate x27, flux) on the torus patch with real candidate
pseudoinverses. Gated three ways: hand-derived constant-reconstruction
closed form on the reference, 1e-12 reference-vs-pipeline agreement with
live and synthetic candidate weights, and a polynomial-only two-group
closed form on an exactly quadratic field.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Runtime assembly of upwind_hflux_miura3_weno (mo_advection_hflux.f90
2033-2620): per-step lvn_sys_pos, ffsl backtrajectory over the full time
step, quadratic Gauss quadrature, a Python loop over the 27 candidate
reconstruction/accumulate launches with L_WEIGHTS_S, and the WENO flux.
AdvectionWenoQuadraticState carries the scattered candidate pseudoinverses,
moments, cell area and the torus ffsl geometry. p_vt is threaded from
SemiLagrangian through the SemiLagrangianTracerFlux interface; the second
order schemes ignore it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
_construct_weno_quadratic_state assembles the 9-point stencil, torus
moments, 27 candidate pseudoinverses and the ffsl backtrajectory geometry
from the grid geometry sources; the linear state is now built only for 102
and the quadratic one only for 103 (previously 103 built the linear state
before the factory raised).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Observed relative L2 error 0.297 (vs 0.328 for 102 and 0.481 for 2);
tolerance frozen at ~1.5x observed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- config: honor run_nml["ldynamics"]; with dynamics off, leave
  nonhydrostatic/diffusion None so tracer advection runs from the
  IC-prescribed mass fluxes instead of silently running the dycore over
  the frozen prescription.
- tracer blob test: use the squared disc formula (dx**2+dy**2 <= r**2)
  to match the tracer_blob IC bit-for-bit, so the frame-0 array-equal
  check can't flake on a boundary cell.
- interpolation_factory: TODO guarding the lsq_high_ord -> lsq_dim_stencil
  mapping (WENO namelists set lsq_high_ord=2 while this assumes 3).
- advection tests: add level markers (integration on the miura3 pipeline,
  unit on the ffsl uniform-flow stencil test).
- driver_io: rename create_io_monitor's local variable so it no longer
  shadows the module-level output_variables function.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

When developing, you can test your changes on CSCS CI before merge with the default pipeline: cscs-ci run default. This will run a default subset of tests.

You can pass options to override pipeline variables, for example:

  • cscs-ci run default;BACKENDS=gtfn_cpu;LEVELS=unit
  • cscs-ci run default;MODEL_SUBPACKAGES=common:standalone_driver;SESSIONS=model
    Avoid running the pipeline for all tests when you are developing.

Available options are:

  • SESSIONS: model, model_mpi, or tools (correspond to nox sessions)
  • MODEL_SUBSETS: datatest, basic, or stencils (correspond to nox session selections)
  • MODEL_SUBPACKAGES: subpackages for non-MPI tests (last component, e.g. diffusion, standalone_driver)
  • MODEL_MPI_SUBPACKAGES: subpackages for MPI tests (as above)
  • BACKENDS: backends
  • GRIDS: grids for stencil tests (simple, icon_regional, or icon_global)
  • LEVELS: testing level for non-stencil tests (unit or integration)

For each option, all can be used as a shorthand for all possible values of that variable, e.g. LEVELS=all.

See scripts/python/generate_ci_pipeline.py and noxfile.py for available values for each option.

The all pipeline can be run with cscs-ci run all. This will run all icon4py tests in CSCS CI which can be expensive. This pipeline runs on a schedule on main, and can be run when extensive validation is needed (e.g. before releases).

Merging

Once your PR is approved and ready for merging, add it to the merge queue. The merge CSCS CI pipeline will run automatically on the merge-queue branch and must pass before the PR is merged. A dummy merge check will be triggered on the PR itself since it's required to add a PR to the merge queue.

Optional Tests

To run benchmarks you can use:

  • cscs-ci run benchmark-bencher

For more detailed information please look at CI in the EXCLAIM universe.

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