Momentum Equations

Cocoa solves the non-conservative form of the 2D depth-integrated momentum equations in spherical coordinates.

Governing Equations

The momentum equations in spherical coordinates on an equidistant cylindrical projection (\(\lambda, \phi\) for longitude and latitude):

\[\begin{split}\begin{split} \frac{\partial U}{\partial t} &+ \frac{U}{R\cos\phi}\frac{\partial U}{\partial \lambda} + \frac{V}{R}\frac{\partial U}{\partial \phi} - f_{\text{eff}} V \\ &= -\frac{g}{R\cos\phi}\frac{\partial \zeta}{\partial \lambda} + \frac{\tau_{sx} - \tau_{bx}}{\rho_0 H} + M_x \end{split}\end{split}\]
\[\begin{split}\begin{split} \frac{\partial V}{\partial t} &+ \frac{U}{R\cos\phi}\frac{\partial V}{\partial \lambda} + \frac{V}{R}\frac{\partial V}{\partial \phi} + f_{\text{eff}} U \\ &= -\frac{g}{R}\frac{\partial \zeta}{\partial \phi} + \frac{\tau_{sy} - \tau_{by}}{\rho_0 H} + M_y \end{split}\end{split}\]

where \(M_x\) and \(M_y\) represent lateral stress terms, and the effective Coriolis parameter includes a spherical metric correction:

\[f_{\text{eff}} = 2\Omega\sin\phi + \frac{U \tan\phi}{R}\]

The second term accounts for the convergence of meridians and is evaluated using the velocity from the previous timestep.

Coordinate Systems

Cocoa supports multiple cylindrical map projections for spherical coordinates. The default is the equidistant cylindrical (CPP) projection, corresponding to ADCIRC’s ICS=21 setting. See Coordinate Systems for complete details on available projections and their scale factors.

Key points for momentum assembly:

  • The mesh precomputes shape function derivatives in projected coordinates

  • During assembly, x-derivatives are multiplied by the momentum scale factor \(S_x^{mom}\) (stored in elem_scale_momentum_x)

  • The y-derivatives require no correction for the equidistant cylindrical projection since latitude spacing is uniform

  • The effective Coriolis parameter includes a spherical metric correction using \(\tan\phi/R\) (see Equidistant Cylindrical (Default))

Time Discretization

Bottom friction and Coriolis use Crank-Nicolson weighting (\(\theta = 0.5\)). Writing the x-momentum equation:

\[\begin{split}\begin{split} \frac{U^{n+1} - U^n}{\Delta t} - f_{\text{eff}}\left(\frac{V^{n+1} + V^n}{2}\right) &= -\tau_{xx}\left(\frac{U^{n+1} + U^n}{2}\right) \\ &\quad - \tau_{xy}\left(\frac{V^{n+1} + V^n}{2}\right) + F_x \end{split}\end{split}\]

Rearranging:

\[\begin{split}\begin{split} \left(1 + \frac{\Delta t}{2}\tau_{xx}\right) U^{n+1} &+ \frac{\Delta t}{2}\left(\tau_{xy} - f_{\text{eff}}\right) V^{n+1} \\ &= \left(1 - \frac{\Delta t}{2}\tau_{xx}\right) U^n - \frac{\Delta t}{2}\left(\tau_{xy} - f_{\text{eff}}\right) V^n + \Delta t \cdot F_x \end{split}\end{split}\]

Per-Node 2x2 System

digraph momentum { rankdir=LR; node [shape=box, style="filled,rounded", fontname="Helvetica", fontsize=10]; edge [color="#555555"]; bgcolor="transparent"; // RHS contributions (inputs) subgraph cluster_rhs { label="RHS Contributions (element loop)"; style="filled,rounded"; fillcolor="#F5F5F5"; color="#BDBDBD"; fontname="Helvetica"; fontsize=10; pres [label="Pressure\nGradient", fillcolor="#E3F2FD", color="#1E88E5"]; adv [label="Advection", fillcolor="#E3F2FD", color="#1E88E5"]; lat [label="Lateral\nStress", fillcolor="#E3F2FD", color="#1E88E5"]; tipg [label="Tide Potential\nGradient", fillcolor="#E3F2FD", color="#1E88E5"]; wind [label="Wind Stress\n(nodal)", fillcolor="#E3F2FD", color="#1E88E5"]; } // LHS contributions subgraph cluster_lhs { label="LHS Matrix (per node)"; style="filled,rounded"; fillcolor="#F5F5F5"; color="#BDBDBD"; fontname="Helvetica"; fontsize=10; fric [label="Bottom Friction\n(TKM diagonal)", fillcolor="#FFF3E0", color="#FB8C00"]; cor [label="Coriolis\n(off-diagonal)", fillcolor="#FFF3E0", color="#FB8C00"]; } // Assembly assemble [label="Assemble 2x2\nSystem per Node", fillcolor="#E8F5E9", color="#43A047"]; // Solve solve [label="Cramer's Rule\nDirect Solve", fillcolor="#FFEBEE", color="#E53935"]; // Output vel [label="U, V\n(velocity)", fillcolor="#F3E5F5", color="#8E24AA"]; flux [label="Qx, Qy\n(flux = H * U)", fillcolor="#F3E5F5", color="#8E24AA"]; pres -> assemble; adv -> assemble; lat -> assemble; tipg -> assemble; wind -> assemble; fric -> assemble; cor -> assemble; assemble -> solve -> vel; solve -> flux; }

Fig. 21 Momentum solver: per-node 2x2 system assembly and solve

The momentum solve at each node:

(9)\[\begin{split}\begin{bmatrix} 1 + \frac{\Delta t}{2}\tau_{xx} & \frac{\Delta t}{2}(\tau_{xy} - f_{\text{eff}}) \\[6pt] \frac{\Delta t}{2}(\tau_{xy} + f_{\text{eff}}) & 1 + \frac{\Delta t}{2}\tau_{yy} \end{bmatrix} \begin{bmatrix} U^{n+1} \\ V^{n+1} \end{bmatrix} = \begin{bmatrix} R_x \\ R_y \end{bmatrix}\end{split}\]

The right-hand side:

\[\begin{split}\begin{split} R_x = \left(1 - \frac{\Delta t}{2}\tau_{xx}\right) U^n &- \frac{\Delta t}{2}(\tau_{xy} - f_{\text{eff}}) V^n \\ &+ \Delta t \cdot F_x \end{split}\end{split}\]
\[\begin{split}\begin{split} R_y = \left(1 - \frac{\Delta t}{2}\tau_{yy}\right) V^n &- \frac{\Delta t}{2}(\tau_{xy} + f_{\text{eff}}) U^n \\ &+ \Delta t \cdot F_y \end{split}\end{split}\]

Coriolis appears only in the off-diagonal entries of Equation (9). The system is solved via Cramer’s rule, keeping all four matrix entries in GPU registers.

Bottom Friction (TKM Tensor)

The TKM tensor stores the linearized friction coefficient. For Manning’s n (see (4)):

(10)\[C_d = \frac{g n^2}{H^{1/3}}\]
(11)\[\tau = \min\left(\frac{C_d |\mathbf{U}|}{H}, \frac{2}{\Delta t}\right)\]

The upper bound \(2/\Delta t\) prevents the implicit friction term from overwhelming the solution in very shallow water.

For isotropic friction, \(\tau_{xx} = \tau_{yy} = \tau\) and \(\tau_{xy} = 0\). When internal tide friction is enabled, its contribution is added to the TKM tensor after Manning’s friction is computed (see Internal Tide Friction in the governing equations). With tensor internal tide friction, \(\tau_{xy} \neq 0\) and the full \(2 \times 2\) system in Equation (9) is required.

These values are stored directly in PhysicsData and used as shown in the matrix above.

RHS Forcing Terms (Continuous)

The forcing terms \(F_x\) and \(F_y\) include:

Pressure Gradient (time-centered):

\[\begin{split}\begin{aligned} F_x^{pres} &= -\frac{g}{2} \alpha_L \left(\frac{\partial \zeta^{n-1}}{\partial x} + \frac{\partial \zeta^{n+1}}{\partial x}\right) \\[4pt] F_y^{pres} &= -\frac{g}{2} \alpha_L \left(\frac{\partial \zeta^{n-1}}{\partial y} + \frac{\partial \zeta^{n+1}}{\partial y}\right) \end{aligned}\end{split}\]

where \(\alpha_L\) is the slope limiter from wet/dry processing (Equation (24)) that reduces pressure gradients in shallow water.

Tide Potential Gradient:

\[\begin{split}\begin{aligned} F_x^{tip} &= \frac{g}{2} \alpha_L \left(\frac{\partial \eta_{tip}^{n-1}}{\partial x} + \frac{\partial \eta_{tip}^{n+1}}{\partial x}\right) \\[4pt] F_y^{tip} &= \frac{g}{2} \alpha_L \left(\frac{\partial \eta_{tip}^{n-1}}{\partial y} + \frac{\partial \eta_{tip}^{n+1}}{\partial y}\right) \end{aligned}\end{split}\]

The tide potential acts as a body force opposing the free surface gradient.

Advection (non-conservative form, time-weighted):

\[F_x^{adv} = -\left(\bar{U} \frac{\partial U}{\partial x} + \bar{V} \frac{\partial U}{\partial y}\right), \quad F_y^{adv} = -\left(\bar{U} \frac{\partial V}{\partial x} + \bar{V} \frac{\partial V}{\partial y}\right)\]

Cocoa uses 2-level time centering with weights between \(n-1\) and \(n\).

Wind Stress (nodal application):

\[F_x^{wind} = \frac{f_w \, \tau_{sx}}{\rho_0 H}, \quad F_y^{wind} = \frac{f_w \, \tau_{sy}}{\rho_0 H}\]

where \(f_w\) is the depth-dependent wind stress limiter that smoothly ramps wind forcing to zero near the drying threshold. Wind stress is computed from 10-meter wind velocity using the Garratt drag law and applied per node (not per element). See Meteorological Forcing for the drag law formulation, wind limiter definition, and discretized form.

Lateral Stress Divergence:

\[F_x^{ls} = \frac{1}{H}\left(\frac{\partial \sigma_{xx}}{\partial x} + \frac{\partial \sigma_{xy}}{\partial y}\right), \quad F_y^{ls} = \frac{1}{H}\left(\frac{\partial \sigma_{yx}}{\partial x} + \frac{\partial \sigma_{yy}}{\partial y}\right)\]

The stress tensor uses the Smagorinsky eddy viscosity model.

Discretized RHS Assembly

The forcing terms are discretized using linear triangular finite elements. For an element with nodes \((1, 2, 3)\), area \(A_e\), and shape function derivatives stored as \(FDX_i = 2 A_e \frac{\partial N_i}{\partial x}\).

Pressure Gradient (Discretized):

\[\begin{split}\begin{aligned} F_x^{pres,e} &= -\frac{g \Delta t}{4} \alpha_L \sum_{i=1}^{3} (\zeta_i^{n-1} + \zeta_i^{n+1}) \cdot FDX_i \\[4pt] F_y^{pres,e} &= -\frac{g \Delta t}{4} \alpha_L \sum_{i=1}^{3} (\zeta_i^{n-1} + \zeta_i^{n+1}) \cdot FDY_i \end{aligned}\end{split}\]

Note: The \(\Delta t\) factor is included because the RHS represents \(\Delta t \cdot F\), not \(F\) alone. The factor of \(1/4\) combined with \(FDX = 2A_e \partial N/\partial x\) (and similarly \(FDY = 2A_e \partial N/\partial y\)) yields the continuous \(g/2\) coefficient for the time-centered average.

Advection (Discretized):

The velocity gradients are computed as:

\[\frac{\partial U}{\partial x} = \frac{1}{2A_e} \sum_{i=1}^{3} U_i \cdot FDX_i, \quad \frac{\partial V}{\partial x} = \frac{1}{2A_e} \sum_{i=1}^{3} V_i \cdot FDX_i\]

and similarly for the y-derivatives using \(FDY_i\).

The advection contribution uses element-averaged velocities:

\[\begin{split}\begin{aligned} F_x^{adv,e} &= -\Delta t \left(\bar{U} \frac{\partial U}{\partial x} + \bar{V} \frac{\partial U}{\partial y}\right) \\[4pt] F_y^{adv,e} &= -\Delta t \left(\bar{U} \frac{\partial V}{\partial x} + \bar{V} \frac{\partial V}{\partial y}\right) \end{aligned}\end{split}\]

where \(\bar{U} = \frac{1}{3} \, \sum_{i=1}^{3} U_i\). Cocoa applies time-weighting between levels \(n-1\) and \(n\) with coefficients matching ADCIRC’s 2-level centered scheme.

Lateral Stress (Discretized):

The Smagorinsky eddy viscosity is computed per-element using the deviatoric strain rate (matching ADCIRC’s formulation):

\[\nu_e = C_s \cdot A_e \cdot |S|, \quad |S| = \sqrt{ \left(\frac{\partial U}{\partial x} - \frac{\partial V}{\partial y}\right)^{\!2} + \left(\frac{\partial U}{\partial y} + \frac{\partial V}{\partial x}\right)^{\!2} }\]

The stress tensor uses per-node depth \(H_i\). Cocoa uses ADCIRC’s symmetric IBPSV formulation (CME_LS_IBPSV), so the off-diagonal shear components are equal:

\[\sigma_{xx,i} = \nu_e H_i \frac{\partial U}{\partial x}, \quad \sigma_{yy,i} = \nu_e H_i \frac{\partial V}{\partial y}\]
\[\sigma_{xy,i} = \sigma_{yx,i} = \tfrac{1}{2} \nu_e H_i \left(\frac{\partial U}{\partial y} + \frac{\partial V}{\partial x}\right)\]

The lateral stress divergence contribution (symmetric IBPSV formulation):

\[F_x^{ls,i} = -\frac{\Delta t}{H_i} \cdot 1.5 \cdot (\sigma_{xx,i} \cdot FDX_i + \sigma_{xy,i} \cdot FDY_i)\]
\[F_y^{ls,i} = -\frac{\Delta t}{H_i} \cdot 1.5 \cdot (\sigma_{yx,i} \cdot FDX_i + \sigma_{yy,i} \cdot FDY_i)\]

Element-to-Node Assembly:

Element contributions are accumulated to nodes using atomic operations (shown for x-momentum; y-momentum follows the same pattern with \(F_y\) terms):

\[\begin{split}\begin{aligned} RHS_{x,i} &\mathrel{+}= F_x^{pres,e} + F_x^{adv,e} + F_x^{ls,i} \\ RHS_{y,i} &\mathrel{+}= F_y^{pres,e} + F_y^{adv,e} + F_y^{ls,i} \end{aligned}\end{split}\]

After assembly, the accumulated RHS is normalized by the node’s control volume:

\[RHS_{x,i} \leftarrow \frac{RHS_{x,i}}{A_{total,i}}, \quad RHS_{y,i} \leftarrow \frac{RHS_{y,i}}{A_{total,i}}\]

where \(A_{total,i} = \sum_{e \ni i} A_e/3\) is the sum of one-third of each element area surrounding node \(i\)

Land Boundary Conditions

At land boundaries, Cocoa enforces a no-normal-flow condition:

\[n_x U + n_y V = 0\]

This is implemented by transforming the \(2 \times 2\) system to tangential-normal coordinates:

  1. Transform RHS: \((RHS_t, RHS_n) = R \cdot (RHS_x, RHS_y)\)

  2. Transform matrix to rotated coordinates

  3. Set \(RHS_n = 0\) (no normal flow)

  4. Solve transformed system

  5. Transform back to \((U, V)\)

The transformation uses:

  • \(\cos\theta = n_x\) (CSII in ADCIRC notation)

  • \(\sin\theta = n_y\) (SIII in ADCIRC notation)

Flux Computation

After solving for velocity, the flux is computed:

\[Q_x = H \cdot U, \quad Q_y = H \cdot V\]

where \(H = h + \zeta\) is the total water depth.

A minimum depth threshold is applied to avoid division issues in very shallow water.

Implementation Files

  • MomentumSolver.hpp: High-level solver class

  • MomentumSolveKernels.hpp: Per-node \(2 \times 2\) solve kernel

  • MomentumRhsKernels.hpp: RHS assembly kernels

  • BottomFriction.hpp: TKM tensor computation

  • LateralStress.hpp: Smagorinsky turbulence model