Split StokesDrift: function-mode StokesDrift + Field-mode FieldStokesDrift#5624
Open
glwagner wants to merge 1 commit into
Open
Split StokesDrift: function-mode StokesDrift + Field-mode FieldStokesDrift#5624glwagner wants to merge 1 commit into
glwagner wants to merge 1 commit into
Conversation
7501845 to
8690a1b
Compare
glwagner
added a commit
to NumericalEarth/Ripple.jl
that referenced
this pull request
May 22, 2026
The upstream redesign (CliMA/Oceananigans.jl#5624) cleans the `StokesDrift` constructor into two non-mixed modes: function-only and all-Fields. The all-Fields path is now allocated by the single `StokesDrift(grid)` constructor, which sets up `uˢ, vˢ, wˢ, ∂t_uˢ, ∂t_vˢ, ∂t_wˢ` at the C-grid velocity locations. The model's `update_state!` calls `compute_stokes_drift!` to fill `wˢ` and `∂t_wˢ` from `(uˢ, vˢ)` and `(∂t_uˢ, ∂t_vˢ)` by vertical integration of continuity, so neither Ripple's example nor its tests need to manage the vertical-component Fields explicitly. This commit switches: - `examples/coupled_wind_drift_instability.jl`: replace the manual 4-field allocation with `stokes_drift = StokesDrift(grid)` and pull the Fields back out from the struct. - `test/integration/energy_conservation.jl`: same update in the `timestepped_coupled_drift` helper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Splits the Stokes-drift abstraction into two concrete subtypes of a new
`AbstractStokesDrift` supertype:
- **`StokesDrift`** (function mode, fully backward-compatible): all
derivative slots are callables `f(x, y, z, t[, params])`. Restored to
its original 10-parameter form; no `uˢ, vˢ, wˢ` slots, no Field
dispatch, no validation. Existing user code is unchanged.
- **`FieldStokesDrift`** (Field mode): six prognostic Field slots
(`uˢ, vˢ, wˢ, ∂t_uˢ, ∂t_vˢ, ∂t_wˢ`) at the C-grid velocity locations
for couplings where the Stokes drift comes from an external wave model.
```julia
sd = FieldStokesDrift(grid) # all six Fields default-allocated
sd = FieldStokesDrift(grid; uˢ=my_uˢ_field) # override individual slots
NonhydrostaticModel(grid; stokes_drift=sd, ...)
```
The 7-parameter `FieldStokesDrift` struct stores only the Field slots —
no dead `zerofunction` placeholders. The per-slot kwarg constructor
defaults each missing argument to a freshly-allocated `Field` at the
matching staggered location, so callers can share Fields with other
code by overriding individual kwargs.
## Behavior
- Spatial derivatives in `x_curl_Uˢ_cross_U, y_curl_Uˢ_cross_U,
z_curl_Uˢ_cross_U` are computed inline from the stored
`uˢ, vˢ, wˢ` Fields via the staggered FD operators
(`∂xᶠᶠᶜ, ∂yᶠᶠᶜ, ∂zᶠᶜᶠ, ∂zᶜᶠᶠ, ∂xᶠᶜᶠ, ∂yᶜᶠᶠ` + `ℑ` interpolators).
Both `∂z uˢ, ∂z vˢ` and the cross-derivative terms `∂y uˢ, ∂x vˢ`
participate.
- `∂t_uˢ, ∂t_vˢ, ∂t_wˢ` are read directly via `getindex` at the
velocity location.
- `compute_stokes_drift!(::FieldStokesDrift, grid)` fills `wˢ` and
`∂t_wˢ` by vertical integration of `∂_z wˢ = -∂_x uˢ - ∂_y vˢ`
(same continuity pattern as the hydrostatic model's
`compute_w_from_continuity!`), starting from zero at the bottom.
No-op on `StokesDrift` and `UniformStokesDrift`.
- `NonhydrostaticModel.update_state!` invokes `compute_stokes_drift!`
between the velocity halo fill and auxiliary-field computation, so
`wˢ` and `∂t_wˢ` are always in sync with `uˢ, vˢ, ∂t_uˢ, ∂t_vˢ`.
## Shared structure
The three `*_curl_Uˢ_cross_U` methods share a single body (the
`(∇×uˢ) × uᴱ` algebra) and dispatch on
`Union{StokesDrift, FieldStokesDrift}`; per-derivative `_∂{x,y,z}_{u,v,w}ˢ_*`
helpers branch on the concrete type. `UniformStokesDrift` retains its
own specialized curl-cross methods because it has a simpler structure.
## Tests
`test/test_stokes_drift.jl` gains a `FieldStokesDrift` testset that
covers:
- All six Field slots allocated by `FieldStokesDrift(grid)` at the
expected staggered locations.
- Per-slot kwarg override: pass one Field, the others default.
- Zero state gives zero contributions.
- Linear vertical shear `uˢ = α·z`, uniform `w = W` →
`x_curl_Uˢ_cross_U = W·α` at every interior point.
- Cross-derivative term `uˢ = β·y, v = V` → `V·β` (the term
`UniformStokesDrift` drops).
- `∂t_uˢ, ∂t_vˢ` read directly with the right sign.
- `compute_stokes_drift!` fills `wˢ` from continuity when
`uˢ, vˢ` have nonzero divergence.
- `compute_stokes_drift!` is a no-op on function-mode `StokesDrift`.
All 326 Stokes-drift tests pass.
Motivation: developed against the wind-drift instability example in
NumericalEarth/Ripple.jl, where the wave-model pseudomomentum
`p(z) = Q(z)·A·K` equals the deep-water Stokes drift when the wave
action is calibrated to the target steepness, and the analytic
pseudomomentum tendency `Q(z)·∂t(A·K)` equals the Stokes acceleration
the ocean must see for the wave-mean energy budget to close.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8690a1b to
66f8b8e
Compare
glwagner
added a commit
to NumericalEarth/Ripple.jl
that referenced
this pull request
May 22, 2026
The upstream redesign (CliMA/Oceananigans.jl#5624) splits `StokesDrift` back into a function-only struct (unchanged) and a new `FieldStokesDrift` for couplings where the Stokes-drift state is carried as Oceananigans `Field`s at the C-grid velocity locations. `FieldStokesDrift(grid)` allocates the six prognostic Field slots (`uˢ, vˢ, wˢ, ∂t_uˢ, ∂t_vˢ, ∂t_wˢ`); kwargs override individual slots. `update_state!` fills `wˢ, ∂t_wˢ` from continuity via `compute_stokes_drift!`, so Ripple only manages the four horizontal Fields. - `examples/coupled_wind_drift_instability.jl`: switch from `StokesDrift(grid)` to `FieldStokesDrift(grid)`; update docstring. - `test/integration/energy_conservation.jl`: same switch in the `timestepped_coupled_drift` helper. All 13 energy-conservation tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Splits the Stokes-drift abstraction into two concrete subtypes of a new
AbstractStokesDriftsupertype:StokesDrift(function mode, fully backward-compatible): all derivative slots are callablesf(x, y, z, t[, params]). Restored to its original 10-parameter form; nouˢ, vˢ, wˢslots, no Field dispatch, no validation. Existing user code is unchanged.FieldStokesDrift(Field mode): six prognosticFieldslots (uˢ, vˢ, wˢ, ∂t_uˢ, ∂t_vˢ, ∂t_wˢ) at the C-grid velocity locations for couplings where the Stokes drift comes from an external wave model.```julia
sd = FieldStokesDrift(grid) # all six Fields default-allocated
sd = FieldStokesDrift(grid; uˢ=my_uˢ_field) # override individual slots
NonhydrostaticModel(grid; stokes_drift=sd, ...)
```
The 7-parameter
FieldStokesDriftstruct stores only the Field slots — no deadzerofunctionplaceholders. The per-slot kwarg constructor defaults each missing argument to a freshly-allocatedFieldat the matching staggered location, so callers can share Fields with other code by overriding individual kwargs.Behavior
x_curl_Uˢ_cross_U, y_curl_Uˢ_cross_U, z_curl_Uˢ_cross_Uare computed inline from the storeduˢ, vˢ, wˢFields via the staggered FD operators (∂xᶠᶠᶜ, ∂yᶠᶠᶜ, ∂zᶠᶜᶠ, ∂zᶜᶠᶠ, ∂xᶠᶜᶠ, ∂yᶜᶠᶠ+ℑinterpolators). Both∂z uˢ, ∂z vˢand the cross-derivative terms∂y uˢ, ∂x vˢparticipate — no curl approximation drops them.∂t_uˢ, ∂t_vˢ, ∂t_wˢare read directly viagetindexat the velocity location.compute_stokes_drift!(::FieldStokesDrift, grid)fillswˢand∂t_wˢby vertical integration of `∂_z wˢ = -∂_x uˢ - ∂_y vˢ` (same continuity pattern as the hydrostatic model's `compute_w_from_continuity!`), starting from zero at the bottom. No-op onStokesDriftandUniformStokesDrift.NonhydrostaticModel.update_state!invokes `compute_stokes_drift!` between the velocity halo fill and auxiliary-field computation, sowˢand∂t_wˢare always in sync withuˢ, vˢ, ∂t_uˢ, ∂t_vˢ.Shared structure
The three
*_curl_Uˢ_cross_Umethods share a single body (the(∇×uˢ) × uᴱalgebra) and dispatch onUnion{StokesDrift, FieldStokesDrift}; per-derivative_∂{x,y,z}_{u,v,w}ˢ_*helpers branch on the concrete type.UniformStokesDriftretains its own specialized curl-cross methods because it has a simpler structure.Backward compatibility
Function-mode user code is completely unchanged.
StokesDrift(; ∂z_uˢ, ...)returns the same struct as before, with the same type-parameter layout.Motivation
Developed against the wind-drift instability example in NumericalEarth/Ripple.jl, where the wave-model pseudomomentum
p(z) = Q(z)·A·Kequals the deep-water Stokes drift when the wave action is calibrated to the target steepness, and the analytic pseudomomentum tendencyQ(z)·∂t(A·K)equals the Stokes acceleration the ocean must see for the wave-mean energy budget to close.Test plan
`test/test_stokes_drift.jl` gains a `FieldStokesDrift` testset:
All 326 Stokes-drift tests pass.
🤖 Generated with Claude Code