Source code for fridom.shallowwater.modules.sadourny_advection

import fridom.framework as fr
import fridom.shallowwater as sw


[docs] @fr.utils.jaxify class SadournyAdvection(fr.modules.advection.AdvectionBase): r""" Advection scheme based on Sadourny [1975] that conserves the total energy. The nonlinear advection terms for the shallow water equations are given by: .. math:: \partial_t \boldsymbol{u} = - (\boldsymbol{u} + \boldsymbol{u}_b) \cdot \nabla \boldsymbol{u} = - \underset{\neg}{\boldsymbol{u}} \zeta - \frac{1}{2} \nabla \boldsymbol{u}^2 - \nabla \left( \boldsymbol{u_b} \cdot \boldsymbol{u} \right) \partial_t p = - \nabla \left\[ (\boldsymbol{u} + \boldsymbol{u}_b) p \right\] \partial_t C = - (\boldsymbol{u} + \boldsymbol{u}_b) \cdot \nabla C where :math:`\boldsymbol{u_b}` is a divergence free background flow that can be set with the `background` attribute of this module, :math:`\zeta` is the relative vorticity, and :math:`C` is a passive tracer. We express the rotational part of the momentum advection with the potential vorticity :math:`q`: .. math:: \underset{\neg}{\boldsymbol{u}} \zeta = \underset{\neg}{\boldsymbol{f_u}} q with .. math:: \boldsymbol{f_u} = (c^2 + Ro~ p) \boldsymbol{u} ~, \quad q = \frac{\zeta}{c^2 + Ro~ p} """ name = "Sadourny Advection"
[docs] @fr.modules.module_method def setup(self, mset: sw.ModelSettings) -> None: super().setup(mset) self.csqr = mset.csqr_field self._required_halo = 2 return
[docs] @fr.utils.jaxjit def advect_state(self, z: sw.State, dz: sw.State) -> sw.State: if self.background is None and self.disable_nonlinear: return dz if self.disable_nonlinear: zf = self.background else: # Compute the full state vector (including the background) if self.background is not None: zf = z + self.background else: zf = z scale = self.scaling interp = self.interp_module.interpolate diff_mod = self.diff_module # ================================================================ # Define some grid positions # ================================================================ CENTER = z.p.position EAST = z.p.position.shift(0) NORTH = z.p.position.shift(1) NORTHEAST = EAST.shift(1) # ---------------------------------------------------------------- # Compute the nonlinear term of the pressure tendency - ∇(vp) # ---------------------------------------------------------------- fx = zf.u * interp(z.p, EAST) fy = zf.v * interp(z.p, NORTH) dz.p -= scale * diff_mod.div((fx, fy)) # ---------------------------------------------------------------- # Advection of passive tracer - v∇C = - ∇(vC) + C∇v # ---------------------------------------------------------------- div = None for name, quantity in z.fields.items(): if quantity.flags["NO_ADV"]: continue if name in ["u", "v", "p"]: continue # compute the divergence of the velocity field (only once) if div is None: div = diff_mod.div((zf.u, zf.v)) fx = zf.u * interp(quantity, EAST) fy = zf.v * interp(quantity, NORTH) df = - diff_mod.div((fx, fy)) + quantity * div dz.fields[name] -= scale * df # ---------------------------------------------------------------- # Advection of momentum # ---------------------------------------------------------------- # start with momentum advection by the background flow if self.background is not None: u_b = self.background.u; v_b = self.background.v # u-component fx = interp(u_b, CENTER) * interp(z.u, CENTER) fy = interp(v_b, NORTHEAST) * interp(z.u, NORTHEAST) dz.u -= scale * diff_mod.div((fx, fy)) # v-component fx = interp(u_b, NORTHEAST) * interp(z.v, NORTHEAST) fy = interp(v_b, CENTER) * interp(z.v, CENTER) dz.v -= scale * diff_mod.div((fx, fy)) # now do the nonlinear advection if self.disable_nonlinear: return dz # compute the potential vorticity zeta = z.rel_vort h_full = self.csqr + scale * z.p # check if we should use scale or Ro here q = zeta / interp(h_full, NORTHEAST) # interp set h_full to zero on boundaries, as a result values of q on # boundaries are nan. We set them to zero here. q.arr = fr.config.ncp.nan_to_num(q.arr, 0.0) # compute the fluxes fu and fv at the northeast position (to match the vorticity) fu = interp(z.u * interp(h_full, EAST), NORTHEAST) fv = interp(z.v * interp(h_full, NORTH), NORTHEAST) # compute the kinetic energy ekin = 0.5 * (interp(z.u**2, CENTER) + interp(z.v**2, CENTER)) # compute the advection terms dz.u += scale * ( interp(fv * q, EAST ) - diff_mod.diff(ekin, axis=0)) dz.v += scale * (-interp(fu * q, NORTH) - diff_mod.diff(ekin, axis=1)) return dz