Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Tutorial

This tutorial walks through the core workflow of pastax on realistic, submesoscale-permitting, surface current fields read lazily from the IGE-MEOM OpenDAP:

  1. Load a realistic surface ocean current (a Gulf-Stream subset of the eNATL60-BLBT02 simulation) and an overlying wind forcing, each kept on its own grid as a Dataset.

  2. Deterministic trajectory simulation

    1. Run a deterministic trajectory through the field with the solve ODE integrator, including a windage term.

    2. Learn the windage coefficient thanks to JAX automatic differentiation, using optimistix to solve a least-square problem, and the separation distance as residual.

  3. Stochastic trajectory ensemble simulation

    1. Run a stochastic ensemble using a Smagorinsky diffusion on a smoothed copy of the current — exercising the SDE mode of solve and the Dataset.neighborhood API.

    2. Jointly learn the windage and the Smagorinsky coefficient of a stochastic simulator, using a time-aggregated energy score with separation distance as kernel.

  4. More “advanced” modelisation

    1. Run a perturbed-ODE ensemble with a non-linear tanh-squashed noise residual — illustrating the ODE+controls pattern for implementing generative neural networks as simulators.

    2. Run a second-order SDE with the full surface-ocean Maxey–Riley equation (a second-order PyTree (position, velocity) state, Coriolis, fluid acceleration and a wind/water-weighted carrier), perturbed by anisotropic eddy-diffusivity turbulence built as a lineax operator.

Boilerplate cells (imports, plot setup, animation rendering) are folded by default — click Show code to expand. The cells most relevant to the pastax API are always expanded.

Source

1. Loading the forcing fields

We load two forcing fields, each on its own grid:

  • the hourly ocean surface current uo\mathbf{u}_o from a Gulf-Stream subset of the eNATL60-BLBT02 simulation (≈ 1/60°, 306 × 240 points, spanning 2010-02-11 → 2010-02-15), streamed from OpenDAP;

  • the 3-hourly 10 m wind uw\mathbf{u}_w from the matching DFS5.2 forcing — a coarse 6 × 6 box covering the same region, also streamed from OpenDAP.

Because the two fields do not share a grid, they are kept as two Datasets and interpolated independently.

Source
currents: {'time': 120, 'lat': 224, 'lon': 233}  wind: {'time': 41, 'lat0': 6, 'lon0': 6}
peak current speed: 2.86 m/s
peak wind speed:    27.23 m/s

Animation of the joint forcing — the ocean current speed ∥uo∥\| \mathbf{u}_o \| is shown in colour; the wind uw\mathbf{u}_w is overlaid as white arrows on its own coarse grid.

Source
Loading...

Wrap each field into its own Dataset so the integrator can query them — forcing_ocean on the fine current grid and forcing_wind on the coarse wind grid. Dataset.from_xarray accepts forcings opened with xarray - note that those while be fully loaded in memory if opened lazily; for plain numpy or JAX arrays, use Dataset.from_arrays instead.

2. Deterministic trajectory simulation

1. A deterministic trajectory with windage

A surface object is advected by the ocean current and partially dragged by the wind. The direct windage model parameterises this as

dX(t)=[uo(t,X)+βw uw(t,X)] dt,\mathrm{d}\mathbf{X}(t) = \bigl[\mathbf{u}_o(t, \mathbf{X}) + \beta_w \, \mathbf{u}_w(t, \mathbf{X})\bigr] \, \mathrm{d}t,

with βw\beta_w a dimensionless coefficient — typically 0.1 to 10%10\% depending on the object. Here we take βw=3%\beta_w = 3\% as ground truth.

In pastax the dynamics are expressed as a Python callable term(t, y, args) that returns the velocity in degrees per second. solve defaults to ODE mode and uses the Tsit5 integrator with a fixed step size.

trajectory shape: (118, 2)  sub-steps/hour: 15

Animation — the time-evolving ocean speed and wind velocity underneath, the trajectory growing one step at a time.

Source
Loading...

2. Learning the windage coefficient

pastax’s solve is fully differentiable. We exploit that to fit the parameters of a term function: given a reference trajectory Y∗\mathbf{Y}^* produced by the true dynamics, recover the parameters of a model that matches it.

We start with the deterministic case: recover the windage coefficient βw\beta_w from a single trajectory, using the per-time separation distance between simulated and reference paths as residuals. The model uses the (perfectly observed) true ocean and wind fields but a tunable βw\beta_w; the reference trajectory is generated with βw=3%\beta_w = 3\%.

ref shape: (118, 2)   init shape: (118, 2)
stopped after 43 steps  ->  beta_w = 3.0%
truth                   ->  beta_w = 3.0%

Animation — the truth, the initial guess, and the fitted trajectory drawn together over the time-evolving ocean speed and wind velocity.

Source
Loading...

3. Stochastic trajectory ensemble simulation

1. A stochastic ensemble with Smagorinsky diffusion

In a real setting the gridded ocean current we feed the simulator is smoothed relative to the actual flow a drifter feels -- think of altimetry-derived surface currents, which only resolve the mesoscale. From here on the model therefore runs on u~o\widetilde{\mathbf{u}}_o, a smoothed copy of uo\mathbf{u}_o, and the role of a stochastic term is to compensate for the small-scale features the gridded field misses.

smoothing reduced peak ocean speed from 2.86 to 2.28 m/s (grid 224x233 -> 45x47)

Sub-grid turbulence the smoothed uo\mathbf{u}_o does not resolve is conventionally reintroduced as a stochastic diffusion with a Smagorinsky-style local turbulent viscosity:

K(x,t)=CS Δx22(∂xu)2+2(∂yv)2+(∂yu+∂xv)2,K(\mathbf{x}, t) = C_S \, \Delta x^2 \sqrt{2(\partial_x u)^2 + 2(\partial_y v)^2 + (\partial_y u + \partial_x v)^2},

estimated from a 3×33 \times 3 patch of the smoothed current via Dataset.neighborhood. With the solver’s Wiener-increment convention dW=Δt z\mathrm{d}\mathbf{W} = \sqrt{\Delta t}\,\mathbf{z} and z∼N(0,I2)\mathbf{z} \sim \mathcal{N}(0, I_2), the diffusion coefficient that produces a per-step displacement variance of 2K Δt2K\,\Delta t is σ=2K\sigma = \sqrt{2K} — the Δt\Delta t factor lives in the increment, not in the coefficient.

We switch to SDE mode by passing key to solve. In SDE mode the term has the signature term(t, y, args) -> (drift, g): it returns the deterministic velocity and the diffusion coefficient g(t,y)g(t, y) separately. The solver draws z∼N(0,I2)\mathbf{z} \sim \mathcal{N}(0, I_2) and applies g dW\mathbf{g}\,\mathrm{d}\mathbf{W} internally — the term never receives z\mathbf{z}. An ensemble of 100 independent realisations is obtained by passing n_samples=100; solve splits the key internally. We use the Stratonovich Heun solver — its predictor–corrector reuses the same Wiener increment across both stages.

ensemble shape: (100, 118, 2)

Animation — each thin red line is one stochastic realisation; the dashed red line is the deterministic, diffusion-free windage trajectory on the same smoothed currents. Both are drawn over the time-evolving smoothed ocean speed and wind velocity.

Source
Loading...

2. Jointly learning windage and diffusion

In the previous section we set the Smagorinsky constant CSC_S to an arbitrary value of 0.1. Here we will recover (βw,CS)(\beta_w, C_S) jointly.

We set this up by:

  • using the smoothed currents of §3.1 (forcing_smooth) as the observed forcing;

  • generating 10 deterministic reference trajectories with leeway βw∗=3%\beta_w^* = 3\% on the unsmoothed currents, seeded in the left side of the domain. Each one is treated as a single sample from the true distribution (the distribution induced by the unresolved scales we are about to parameterise away);

  • fitting the model -- which uses the smoothed currents plus a Smagorinsky SDE -- by minimising the time-aggregated energy score–see Pic et al., 2025–with separation distance as kernel, summed over all 10 reference trajectories:

ESt(F,yt∗)=1M∑i=1Md(Xt(i),yt∗)  −  12M(M−1)∑i≠jd(Xt(i),Xt(j)),L=∑k=1K=10∑tESt(Fn,yt∗,k).\mathrm{ES}_t(F, y^*_t) = \frac{1}{M} \sum_{i=1}^M d(X^{(i)}_t, y^*_t) \;-\; \frac{1}{2M(M-1)} \sum_{i \neq j} d(X^{(i)}_t, X^{(j)}_t), \qquad \mathcal{L} = \sum_{k=1}^{K=10} \sum_t \mathrm{ES}_t(F_n, y^{*,k}_t).

We do not have a ground-truth CSC_S -- CSC_S is a parameterisation knob, not a physical constant of the unsmoothed flow. We start from a non-zero value and ask the optimiser to move it without collapsing it to zero.

The energy score, alongside the squared error, Dawid-Sebastiani score, and variogram score, ships in pastax.score as a proper scoring rule for ensemble forecasts. squared_error, energy_score, and dawid_sebastiani support reduce=None, "last", or "sum", with optional per-time weights. squared_error, energy_score, and variogram_score also accept a broadcasting distance kernel; here we use separation_distance.

squared_error and energy_score also support reduce="joint". This mode flattens each trajectory and uses Euclidean distance, ignoring kernel and weights. Project geographic coordinates with chordal_projection first. variogram_score instead compares temporal increments over lags.

ref_trajs shape: (10, 118, 2)
stopped after 92 steps  -> beta_w = 2.9%, C_S = 0.047
truth                   -> beta_w = 3.0% (no true C_S)

Animation -- the 10 deterministic reference trajectories drawn against the smoothed ocean current the model actually sees, each surrounded by a 30-member SDE ensemble drawn from the fitted parameters.

Source
Loading...

4. More “advanced” modelisation

1. A stochastic ensemble via perturbed ODE

The SDE approach above hard-wires a linear noise model: the per-step displacement is g(t,X) dW\mathbf{g}(t, \mathbf{X})\,\mathrm{d}\mathbf{W}, a linear function of the Wiener increment. More expressive stochastic samplers — such as generative neural networks — learn a non-linear mapping z↦ε\mathbf{z} \mapsto \boldsymbol{\varepsilon} from a standard-normal seed to a richer, possibly multi-modal displacement distribution. The SDE solver cannot represent this: it always computes g⋅dW\mathbf{g} \cdot \mathrm{d}\mathbf{W}.

The ODE+controls pattern handles it directly. The user pre-samples a batch of noise trajectories (shape (S, n_fine, 2)) from any distribution and vmaps solve over them; each integration step receives one ctrl slice and the term applies whatever non-linear transform it likes. The solver never sees the noise — it simply multiplies the returned velocity by Δt\Delta t.

Here we illustrate with a tanh-squashed Gaussian: ctrl ~ N(0, I_2) is mapped through tanh⁡\tanh before being scaled by the local Smagorinsky amplitude gg. Compared to the linear SDE this produces a bounded residual velocity (no arbitrarily large steps), while retaining the correct local scale. Replacing tanh with a generative neural network requires no changes to the solver or the integration loop.

perturbed ODE ensemble shape: (100, 118, 2)

Animation — same as §3.1.

Source
Loading...

2. Surface-ocean Maxey–Riley: a full second-order inertial model

The windage term of §2 is an ad-hoc leeway model. The principled description of a finite-size buoyant object at the air–sea interface is the Maxey–Riley equation, adapted to the rotating ocean surface by Beron-Vera et al. 2019. The particle carries its own velocity vp\mathbf v_p which evolves as (their Eq. 29, on a β\beta-plane):

dvpdt=R DvDt⏟fluid accel. + added mass+τ−1 (u−vp)⏟drag to weighted forcing+R ⁣(f+ω3) ⁣Jv−(f+Rω3) ⁣Jvp⏟Coriolis + lift,dxdt=vp.\frac{\mathrm d\mathbf v_p}{\mathrm dt} = \underbrace{R\,\frac{\mathrm D\mathbf v}{\mathrm Dt}}_{\text{fluid accel. + added mass}} + \underbrace{\tau^{-1}\,(\mathbf u - \mathbf v_p)}_{\text{drag to weighted forcing}} + \underbrace{R\!\left(f+\tfrac{\omega}{3}\right)\!\mathbf J\mathbf v - \left(f+\tfrac{R\omega}{3}\right)\!\mathbf J\mathbf v_p}_{\text{Coriolis + lift}}, \qquad \frac{\mathrm d\mathbf x}{\mathrm dt}=\mathbf v_p .
symbolmeaning
v\mathbf vseawater velocity (the field u_o, v_o); DvDt=∂tv+(v ⁣⋅ ⁣∇)v\tfrac{\mathrm D\mathbf v}{\mathrm Dt}=\partial_t\mathbf v+(\mathbf v\!\cdot\!\nabla)\mathbf v
u=(1−α) v+α va\mathbf u=(1-\alpha)\,\mathbf v+\alpha\,\mathbf v_adensity-weighted forcing — seawater and air (u_w, v_w) velocities
R=1−Φ/21−Φ/6R=\frac{1-\Phi/2}{1-\Phi/6}buoyancy parameter (Φ\Phi from the submerged fraction)
τ\tauinertial response (Stokes) time
α\alphaair-drag weight, ∝\propto viscosity ratio and emerged fraction (small)
f=2Ωsin⁡φf=2\Omega\sin\varphiCoriolis parameter at latitude φ\varphi; ω=∂xv2−∂yv1\omega=\partial_x v^2-\partial_y v^1 vorticity
J\mathbf Jrotation by 90∘90^\circ, J(a,b)=(−b,a)\mathbf J(a,b)=(-b,a), i.e. k× ⋅\mathbf k\times\,\cdot

We integrate the model in SI units (velocity in m/s) and convert only the kinematic coupling x˙=vp\dot{\mathbf x}=\mathbf v_p to deg/s. The material derivative and vorticity use grid-scale central differences of the (differentiable) bilinear interpolation. Following §3.1, we run this model — deterministic and stochastic alike — on the smoothed currents forcing_smooth.

Full Maxey-Riley state: (118, 2) + velocity (118, 2)
Slow Manifold:          (118, 2)
Source
<Figure size 1100x460 with 2 Axes>

The §3.1 ensemble injected the unresolved scales as a Smagorinsky diffusion on position, with a diffusivity KK modelled from the local strain and amplitude σ=2K\sigma=\sqrt{2K}. The Maxey–Riley model lets us be more physical. A real float is dragged through unresolved turbulence, so the seawater velocity felt in the drag is v+v′\mathbf v + \mathbf v', with v′\mathbf v' a sub-grid fluctuation; modelling v′\mathbf v' as white-in-time turns the drag into a stochastic forcing on the velocity. Here KK is the ocean eddy diffusivity with K=σv2 τK=\sigma_v^2\,\tau, and the drag makes the slip an Ornstein–Uhlenbeck process whose stationary variance is σv2=σ2τ/2\sigma_v^2=\sigma^2\tau/2; hence

σ=2Kτ\sigma=\frac{\sqrt{2K}}{\tau}

-- the same 2K\sqrt{2K} as in §3.1, now divided by the response time τ\tau because the noise drives the velocity rather than the position directly.

Lateral stirring is anisotropic — stronger along the flow than across it (shear dispersion), so K∥>K⊥K_\parallel>K_\perp. With e^∥\hat{\mathbf e}_\parallel along the local seawater velocity, the diffusion maps a 2-D Wiener increment into the velocity leaf,

Σ dW=2K∥τ dW∥ e^∥+2K⊥τ dW⊥ e^⊥,\Sigma\,\mathrm d\mathbf W = \frac{\sqrt{2K_\parallel}}{\tau}\,\mathrm dW_\parallel\,\hat{\mathbf e}_\parallel + \frac{\sqrt{2K_\perp}}{\tau}\,\mathrm dW_\perp\,\hat{\mathbf e}_\perp ,

a state-dependent, non-diagonal 2×22\times2 operator. We express it as a lineax.FunctionLinearOperator returning a State tangent (noise on vp\mathbf v_p only) and declare the 2-D Brownian space with brownian_structure.

Maxey-Riley ensemble: (100, 118, 2)

Animation — the stochastic Maxey–Riley ensemble (thin red) spreads anisotropically along the flow under the eddy-diffusivity noise, around the “true” inertial path (gold), over the evolving ocean speed and wind.

Source
Loading...

This capstone packs the whole framework into one physically-grounded model: a PyTree state (x,vp)(\mathbf x,\mathbf v_p) for the second-order Maxey–Riley dynamics (fluid acceleration, Coriolis, lift and drag to a wind/water-weighted carrier); an operator-valued diffusion (lineax.FunctionLinearOperator) for anisotropic, flow-aligned turbulence that a diagonal g cannot represent; and a browian_structure decoupling the 2-D Wiener space from the 4-D state. Because the term is built from differentiable interp calls, the whole simulator is end-to-end differentiable — the parameters (R,α,τ,C∥,C⊥)(R,\alpha,\tau,C_\parallel,C_\perp) could be learned with the scoring rules of §3.2.

Where to next

  • See the API reference for the full surface of solve, Dataset, and the interpolation, metric, and score helpers.

  • The solve integrator supports both reverse-mode (jax.grad, used by BFGS above) and forward-mode (jax.jvp, used by Levenberg-Marquardt above) auto-differentiation. Reverse mode is the low-memory default (adjoint="checkpointed"); forward mode requires solve(..., adjoint="forward"), as in the §2.2 fit.

References
  1. Aurélie, A. (2026). Champs de surface eNATL60. IFREMER. 10.12770/DD5D496A-8B55-4EAC-B989-F50B44BFC4AB
  2. Pic, R., Dombry, C., Naveau, P., & Taillardat, M. (2025). Proper scoring rules for multivariate probabilistic forecasts based on aggregation and transformation. Advances in Statistical Climatology, Meteorology and Oceanography, 11(1), 23–58. 10.5194/ascmo-11-23-2025
  3. Beron-Vera, F. J., Olascoaga, M. J., & Miron, P. (2019). Building a Maxey–Riley framework for surface ocean inertial particle dynamics. Physics of Fluids, 31(9). 10.1063/1.5110731