Skip to content

Split StokesDrift: function-mode StokesDrift + Field-mode FieldStokesDrift#5624

Open
glwagner wants to merge 1 commit into
mainfrom
field-stokes-drift
Open

Split StokesDrift: function-mode StokesDrift + Field-mode FieldStokesDrift#5624
glwagner wants to merge 1 commit into
mainfrom
field-stokes-drift

Conversation

@glwagner
Copy link
Copy Markdown
Member

@glwagner glwagner commented May 22, 2026

Summary

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 — no curl approximation drops them.
  • ∂t_uˢ, ∂t_vˢ, ∂t_wˢ are read directly via getindex at the velocity location.
  • compute_stokes_drift!(::FieldStokesDrift, grid) fills 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 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.

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·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.

Test plan

`test/test_stokes_drift.jl` gains a `FieldStokesDrift` testset:

  • All six Field slots allocated by `FieldStokesDrift(grid)` at the expected staggered locations.
  • Per-slot kwarg override (e.g., `FieldStokesDrift(grid; uˢ=my_uˢ)`).
  • 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.

🤖 Generated with Claude Code

@glwagner glwagner force-pushed the field-stokes-drift branch from 7501845 to 8690a1b Compare May 22, 2026 13:04
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>
@glwagner glwagner force-pushed the field-stokes-drift branch from 8690a1b to 66f8b8e Compare May 22, 2026 14:00
@glwagner glwagner changed the title StokesDrift: Field-mode constructor with wˢ + ∂t_wˢ from continuity Split StokesDrift: function-mode StokesDrift + Field-mode FieldStokesDrift May 22, 2026
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>
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