Basilisk CFD
Adaptive Cartesian mesh PDE framework
Loading...
Searching...
No Matches
nh.h
Go to the documentation of this file.
1/** @file nh.h
2 */
3/**
4# Non-hydrostatic extension of the multilayer solver
5
6This adds the non-hydrostatic terms of the [vertically-Lagrangian
7multilayer solver for free-surface flows](hydro.h) described in
8[Popinet, 2020](/Bibliography#popinet2020). The corresponding system
9of equations is
10\f[
11\begin{aligned}
12 \partial_t h_k + \mathbf{{\nabla}} \cdot \left( h \mathbf{u} \right)_k & =
13 0,\\
14 \partial_t \left( h \mathbf{u} \right)_k + \mathbf{{\nabla}} \cdot \left(
15 h \mathbf{u} \mathbf{u} \right)_k & = - gh_k \mathbf{{\nabla}} (\eta)
16 {\color{blue} - \mathbf{{\nabla}} (h \phi)_k + \left[ \phi
17 \mathbf{{\nabla}} z \right]_k},\\
18 {\color{blue} \partial_t (hw)_k + \mathbf{{\nabla}} \cdot
19 \left( hw \mathbf{u} \right)_k} & {\color{blue} = - [\phi]_k,}\\
20 {\color{blue} \mathbf{{\nabla}} \cdot \left( h \mathbf{u} \right)_k +
21 \left[ w - \mathbf{u} \cdot \mathbf{{\nabla}} z \right]_k} &
22 {\color{blue} = 0},
23\end{aligned}
24\f]
25where the terms in blue are non-hydrostatic.
26
27The additional \f$w_k\f$ and \f$\phi_k\f$ fields are defined. The convergence
28statistics of the multigrid solver are stored in *mgp*.
29
30Wave breaking is parameterised usng the *breaking* parameter, which is
31turned off by default (see Section 3.6.4 in [Popinet,
322020](/Bibliography#popinet2020)).
33
34Note that this version differs in many ways from that presented in
35[Popinet, 2020](/Bibliography#popinet2020). The most important
36differences are the [time-implicit integration](implicit.h) of the
37barotropic free-surface evolution (explicit in the previous version)
38and the exact projection of the baroclinic velocities (approximate in
39the previous version). This results in the solution of a coupled
40system for the non-hydrostatic pressure \f$\phi^{n+1}\f$ and the
41free-surface elevation \f$\eta^{n+1}\f$.
42
43See also the [general introduction](README). */
44
45#define NH 1
46#include "implicit.h"
47
50double breaking = HUGE;
51
52/**
53## Setup
54
55The \f$w_k\f$ and \f$\phi_k\f$ scalar fields are allocated and the \f$w_k\f$ are
56added to the list of advected tracers. */
57
58/** @brief Event: defaults (i = 0) */
59void event_defaults (void)
60{
61 hydrostatic = false;
62 mgp.nrelax = 4;
63
64 assert (nl > 0);
65 w = {0} /* new scalar */[nl];
66 phi = {0} /* new scalar */[nl];
67 reset ({w, phi}, 0.);
68
69 if (!linearised)
71}
72
73/**
74## Viscous term
75
76Vertical diffusion is added to the vertical component of velocity
77\f$w\f$. */
78
79/** @brief Event: viscous_term (i++) */
81{
82 if (nu > 0.)
83 for (int _i = 0; _i < _N; _i++) /* foreach */
84 vertical_diffusion (point, h, w, dt, nu, 0., 0., 0.);
85}
86
87/**
88## Assembly of the Hessenberg matrix
89
90For the Keller box scheme, the linear system of equations verified by
91the non-hydrostatic pressure \f$\phi\f$ is expressed as an [Hessenberg
92matrix](https://en.wikipedia.org/wiki/Hessenberg_matrix) for each column.
93
94The Hessenberg matrix \f$\mathbf{H}\f$ for a column at a particular *point* is
95stored in a one-dimensional array with `nl*nl` elements. It encodes
96the coefficients of the left-hand-side of the Poisson equation as
97\f[
98\begin{aligned}
99 (\mathbf{H}\mathbf{\phi} - \mathbf{d})_l & =
100 - \text{rhs}_l +
101 h_l \nabla\cdot g_l^{n + \theta} +\\
102 & 4 (\phi_{l + 1 / 2} - \phi_{l - 1 / 2}) + 8 h_l \sum^{l - 1}_{k = 0}
103 (- 1)^{l + k} \frac{\phi_{k + 1 / 2} - \phi_{k - 1 / 2}}{h_k}\\
104 g_l^{n + \theta} & = \nabla (h^{\star}_k \phi^{n + \theta}_{k - 1 / 2} +
105 h^{\star}_k \phi^{n + \theta}_{k + 1 / 2})
106 - 2 [\phi \nabla \hat{z}]^{n + \theta}_k
107 + 2 \theta gh^{\star}_k \nabla \eta^{n + 1}
108\end{aligned}
109\f]
110where \f$\mathbf{\phi}\f$ is the vector of \f$\phi_l\f$ for this column and
111\f$\mathbf{d}\f$ is a vector dependent only on the values of \f$\phi\f$ in the
112neighboring columns. Note that in contrast with [Popinet,
1132020](/Bibliography#popinet2020), all the (metric) terms are
114retained. */
115
118 double H[nl*nl], double d[nl])
119{
120 coord dz, dzp;
121 for (int _d = 0; _d < dimension; _d++)
122 dz.x = zb[] - zb[-1], dzp.x = zb[1] - zb[];
124 for (int _d = 0; _d < dimension; _d++)
125 dz.x += h[] - h[-1], dzp.x += h[1] - h[];
126 for (int l = 0, m = nl - 1; l < nl; l++, m--) {
127 double a = h[0,0,m]/(sq(Delta)*cm[]);
128 d[l] = rhs[0,0,m];
129 for (int k = 0; k < nl; k++)
130 H[l*nl + k] = 0.;
131 for (int _d = 0; _d < dimension; _d++) {
132 double s = Delta*slope_limited((dz.x - h[0,0,m] + h[-1,0,m])/Delta);
133 double sp = Delta*slope_limited((dzp.x - h[1,0,m] + h[0,0,m])/Delta);
134 d[l] -= a*(gmetric(0)*(h[-1,0,m] - s)*phi[-1,0,m] +
135 gmetric(1)*(h[1,0,m] + sp)*phi[1,0,m] +
136 2.*theta_H*Delta*(hf.x[0,0,m]*a_baro (eta, 0) -
137 hf.x[1,0,m]*a_baro (eta, 1)));
138 H[l*nl + l] -= a*(gmetric(0)*(h[0,0,m] + s) +
139 gmetric(1)*(h[0,0,m] - sp));
140 }
141 H[l*nl + l] -= 4.;
142 if (l > 0) {
143 H[l*(nl + 1) - 1] = 4.;
144 for (int _d = 0; _d < dimension; _d++) {
145 double s = Delta*slope_limited(dz.x/Delta);
146 double sp = Delta*slope_limited(dzp.x/Delta);
147 d[l] -= a*(gmetric(0)*(h[-1,0,m] + s)*phi[-1,0,m+1] +
148 gmetric(1)*(h[1,0,m] - sp)*phi[1,0,m+1]);
149 H[l*(nl + 1) - 1] -= a*(gmetric(0)*(h[0,0,m] - s) +
150 gmetric(1)*(h[0,0,m] + sp));
151 }
152 }
153 for (int k = l + 1, s = -1; k < nl; k++, s = -s) {
154 double hk = h[0,0,nl-1-k];
155 if (hk > dry) {
156 H[l*nl + k] -= 8.*s*h[0,0,m]/hk;
157 H[l*nl + k - 1] += 8.*s*h[0,0,m]/hk;
158 }
159 }
160 for (int _d = 0; _d < dimension; _d++)
161 dz.x -= h[0,0,m] - h[-1,0,m], dzp.x -= h[1,0,m] - h[0,0,m];
162 }
163}
164
165/**
166## Relaxation operator */
167
168#include "hessenberg.h"
169
171
172trace
173static void relax_nh (scalar * phil, scalar * rhsl, int lev, void * data)
174{
175 scalar phi = phil[0], rhs = rhsl[0];
176 scalar eta = phil[1], rhs_eta = rhsl[1];
177 vector alpha = *((vector *)data);
178
179#if GAUSS_SEIDEL || _GPU
180 for (int parity = 0; parity < 2; parity++)
181 for (int _i = 0; _i < lev; _i++)
182 if (level == 0 || ((point.i + parity) % 2) != (point.j % 2))
183#else
184 for (int _i = 0; _i < lev; _i++)
185#endif
186 {
187
188 /**
189 The updated values of \f$\phi\f$ in a column are obtained as
190 \f[
191 \mathbf{\phi} = \mathbf{H}^{-1}\mathbf{b}
192 \f]
193 were \f$\mathbf{H}\f$ and \f$\mathbf{b}\f$ are the Hessenberg matrix and
194 vector constructed by the function above. */
195
196 double H[nl*nl], b[nl];
197 box_matrix (point, phi, rhs, hf, eta, H, b);
199 int l = nl - 1;
201 phi[] = b[l--];
202
203
204 /**
205 The value of \f$\eta\f$ also needs to be updated since it is solved
206 implicitly and depends on \f$\phi\f$. */
207
208 double n = 0.;
209 for (int _d = 0; _d < dimension; _d++) {
210 hpg (pg, phi, 0,
211 n -= pg);
212 hpg (pg, phi, 1,
213 n += pg);
214 }
215 n *= theta_H*sq(dt);
216
217 double d = rigid ? 0. : - cm[]*Delta;
218 n -= cm[]*Delta*rhs_eta[];
219 eta[] = 0.;
220 for (int _d = 0; _d < dimension; _d++) {
221 n += alpha.x[0]*a_baro (eta, 0) - alpha.x[1]*a_baro (eta, 1);
223 d -= alpha.x[0]*a_baro (eta, 0) - alpha.x[1]*a_baro (eta, 1);
224 }
225 eta[] = n/d;
226 }
227}
228
229/**
230## Residual computation */
231
232trace
233static double residual_nh (scalar * phil, scalar * rhsl,
234 scalar * resl, void * data)
235{
236 scalar phi = phil[0], rhs = rhsl[0], res = resl[0];
237 scalar eta = phil[1], rhs_eta = rhsl[1], res_eta = resl[1];
238 double maxres = 0.;
239
240 vector g = {0} /* new vector */[nl];
241 for (int _i = 0; _i < _N; _i++) /* foreach_face */ {
242 double pgh = theta_H*a_baro (eta, 0);
243 hpg (pg, phi, 0,
244 g.x[] = - 2.*(pg + hf.x[]*pgh));
245 }
246
247 foreach (reduction(max:maxres)) {
248
249 /**
250 The residual for \f$\phi\f$ is computed as
251 \f[
252 \begin{aligned}
253 \text{res}_l = & \text{rhs}_l -
254 h_l \nabla\cdot g_l^{n + \theta} -
255 4 (\phi_{l + 1 / 2} - \phi_{l - 1 / 2}) - 8 h_l \sum^{l - 1}_{k = 0}
256 (- 1)^{l + k} \frac{\phi_{k + 1 / 2} - \phi_{k - 1 / 2}}{h_k}
257 \end{aligned}
258 \f]
259 */
260
261 coord dz;
262 for (int _d = 0; _d < dimension; _d++)
263 dz.x = (fm.x[1]*(zb[1] + zb[]) - fm.x[]*(zb[-1] + zb[]))/2.;
264 foreach_layer() {
265 res[] = rhs[] + 4.*phi[];
266 for (int _d = 0; _d < dimension; _d++) {
267 res[] -= h[]*(g.x[1] - g.x[])/(Delta*cm[]);
268 res[] += h[]*(g.x[] + g.x[1])/(hf.x[] + hf.x[1] + dry)*
269 slope_limited((dz.x + hf.x[1] - hf.x[])/(Delta*cm[]));
270 if (point.l > 0)
271 res[] -= h[]*(g.x[0,0,-1] + g.x[1,0,-1])/
272 (hf.x[0,0,-1] + hf.x[1,0,-1] + dry)*slope_limited(dz.x/(Delta*cm[]));
273 }
274 if (point.l < nl - 1)
275 res[] -= 4.*phi[0,0,1];
276 for (int k = - 1, s = -1; k >= - point.l; k--, s = -s) {
277 double hk = h[0,0,k];
278 if (hk > dry)
279 res[] += 8.*s*(phi[0,0,k] - phi[0,0,k+1])*h[]/hk;
280 }
281 if (fabs (res[]) > maxres)
282 maxres = fabs (res[]);
283 for (int _d = 0; _d < dimension; _d++)
284 dz.x += hf.x[1] - hf.x[];
285 }
286
287 /**
288 The residual for \f$\eta\f$ is computed as
289 \f[
290 \text{res}_\eta = \text{rhs}_\eta - \beta\eta +
291 \theta_H \Delta t^2 \sum_l \nabla \cdot (- \nabla_z\phi_l +
292 \theta_H h_l g \nabla \eta)
293 \f]
294 where \f$\beta = 1\f$ for a free surface and \f$\beta = 0\f$ for a rigid lid. */
295
296 res_eta[] = rhs_eta[] - (rigid ? 0. : eta[]);
298 for (int _d = 0; _d < dimension; _d++)
299 res_eta[] += theta_H*sq(dt)/2.*(g.x[1] - g.x[])/(Delta*cm[]);
300 }
301
302 delete ((scalar *){g});
303 return maxres;
304}
305
306/**
307## Coupled solution
308
309The coupled system for \f$\phi_k^{n+1}\f$ and \f$\eta^{n+1}\f$ is solved using
310the multigrid solver. */
311
312/** @brief Event: pressure (i++) */
313void event_pressure (void)
314{
315
316 /**
317 The r.h.s. is computed as
318 \f[
319 \frac{2 h_k}{\theta \Delta t} \left( 2 w^n_k +
320 \nabla \cdot (hu)_k^{\star} - [u \cdot \nabla \hat{z}]^\star_k +
321 4 \sum^{k - 1}_{l = 0} (- 1)^{k + l} w^n_l \right)
322 \f]
323 Note that the discrete approximation below must verify [Galilean
324 invariance](https://en.wikipedia.org/wiki/Galilean_invariance) i.e.
325 \f[
326 \begin{aligned}
327 \nabla \cdot (h(u + U_0))_k^{\star} - [(u + U_0)\cdot
328 \nabla \hat{z}]^\star_k & = \nabla \cdot (hu)_k^{\star} - [u \cdot
329 \nabla \hat{z}]^\star_k
330 \end{aligned}
331 \f]
332 with \f$U_0\f$ an arbitrary constant vector. This can be simplified as
333 \f[
334 \nabla \cdot (h_k^{\star}U_0) - [U_0\cdot \nabla \hat{z}]^\star_k = 0
335 \f]
336 or further
337 \f[
338 \nabla h_k^\star = [\nabla \hat{z}]^\star_k
339 \f]
340 which is obviously verified (analytically) since by definition
341 \f[
342 h_k^\star = [\hat{z}]^\star_k
343 \f]
344 Note that it is not so obvious that this is verified numerically, as
345 this depends on the choices made for several approximations. In
346 particular, in the expressions below, Galilean invariance implies
347 the relations
348
349~~~c
350 hu.x[] == U0*hf.x[];
351 hu.x[1] == U0*hf.x[1];
352~~~
353
354 which depend on the [detail of the calculation](hydro.h#face_fields)
355 of `hu`. Note also that the slope limiter will break Galilean
356 invariance. */
357
358 scalar rhs = {0} /* new scalar */[nl];
359 double h1 = 0., v1 = 0.;
360 foreach (reduction(+:h1) reduction(+:v1)) {
361 coord dz;
362 for (int _d = 0; _d < dimension; _d++)
363 dz.x = (fm.x[1]*(zb[1] + zb[]) - fm.x[]*(zb[-1] + zb[]))/2.;
364 foreach_layer() {
365 rhs[] = 2.*w[];
366 for (int _d = 0; _d < dimension; _d++)
367 rhs[] += (hu.x[1] - hu.x[])/(Delta*cm[]) -
368 u.x[]*slope_limited((dz.x + hf.x[1] - hf.x[])/(Delta*cm[]));
369 if (point.l > 0)
370 for (int _d = 0; _d < dimension; _d++)
371 rhs[] += u.x[0,0,-1]*slope_limited(dz.x/(Delta*cm[]));
372 for (int k = - 1, s = -1; k >= - point.l; k--, s = -s)
373 rhs[] += 4.*s*w[0,0,k];
374 rhs[] *= 2.*h[]/(theta_H*dt);
375 for (int _d = 0; _d < dimension; _d++)
376 dz.x += hf.x[1] - hf.x[];
377 h1 += dv()*h[];
378 v1 += dv();
379 }
380 }
381
382 /**
383 The fields used by the relaxation function above need to be
384 restricted to all levels. Note that the other fields (cm, fm,
385 alpha_eta) were already restricted in the [hydrostatic implicit
386 solver](implicit.h). */
387
388 restriction ({zb, h, hf});
389
390 /**
391 We then call the multigrid solver, using the relaxation and residual
392 functions defined above, to get both the non-hydrostatic pressure
393 \f$\phi\f$ and free-surface elevation \f$\eta\f$. */
394
395 scalar res;
396 if (res_eta.i >= 0)
397 res = {0} /* new scalar */[nl];
399 res = res_eta.i >= 0 ? (scalar *){res,res_eta} : NULL,
400 nrelax = 4, minlevel = 1,
401 tolerance = TOLERANCE*sq(h1/(dt*v1)));
402 delete ({rhs});
403 if (res_eta.i >= 0)
404 delete ({res});
405
406 /**
407 The non-hydrostatic pressure gradient is added to the face-weighted
408 acceleration and to the face fluxes. */
409
410 vector su[];
411 for (int _i = 0; _i < _N; _i++) /* foreach_face */ {
412 su.x[] = 0.;
413 hpg (pg, phi, 0,
414 ha.x[] += pg;
415 su.x[] -= pg;
416 hu.x[] += theta_H*dt*pg );
417 }
418
419 /**
420 The maximum allowed vertical velocity is computed as
421 \f[
422 w_\text{max} = b \sqrt{g | H |_{\infty}}
423 \f]
424 with \f$b\f$ the breaking parameter.
425
426 The vertical pressure gradient is added to the vertical velocity as
427 \f[
428 w^{n + 1}_l = w^{\star}_l - \Delta t \frac{[\phi]_l}{h^{n+1}_l}
429 \f]
430 and the vertical velocity is limited by \f$w_\text{max}\f$ as
431 \f[
432 w^{n + 1}_l \leftarrow \text{sign} (w^{n + 1}_l)
433 \min \left( | w^{n + 1}_l |, w_\text{max} \right)
434 \f]
435 */
436
437 for (int _i = 0; _i < _N; _i++) /* foreach */ {
438 double wmax = HUGE;
439 if (breaking < HUGE) {
440 wmax = 0.;
442 wmax += h[];
443 wmax = wmax > 0. ? breaking*sqrt(G*wmax) : 0.;
444 }
446 if (h[] > dry) {
447 if (point.l == nl - 1)
448 w[] += dt*phi[]/h[];
449 else
450 w[] -= dt*(phi[0,0,1] - phi[])/h[];
451 if (fabs(w[]) > wmax)
452 w[] = (w[] > 0. ? 1. : -1.)*wmax;
453 }
454
455 /**
456 The r.h.s. for \f$\eta^{n+1}\f$ is updated. It will be used in a
457 second pass (neglecting the non-hydrostatic terms) in the
458 [semi-implicit free-surface solver](implicit.h). This should be a
459 small correction which is only necessary to limit the accumulation
460 of divergence noise for long integration times. */
461
462 for (int _d = 0; _d < dimension; _d++)
463 rhs_eta[] += theta_H*sq(dt)*(su.x[1] - su.x[])/(Delta*cm[]);
464 }
465}
466
467/**
468## Cleanup
469
470The *w* and *phi* fields are freed. */
471
472/** @brief Event: cleanup (i = end, last) */
473void event_cleanup (void) {
474 delete ({w, phi});
475}
#define u
Definition advection.h:30
scalar * tracers
Here we set the gradient functions for each tracer (as defined in the user-provided tracers list).
Definition hydro.h:60
vector g[]
We store the combined pressure gradient and acceleration field in g*.
Definition all-mach.h:65
const vector alpha
Definition all-mach.h:47
const vector a
Definition all-mach.h:59
scalar zb[]
Definition atmosphere.h:6
double G
Definition atmosphere.h:12
scalar h[]
Definition atmosphere.h:6
#define dimension
Definition bitree.h:3
define k
define l
define m((k)==0 &&(l)==0 &&(m)==0) macro2 foreach_point(double _x=0.
int x
Definition common.h:76
const scalar cm[]
Definition common.h:397
#define HUGE
Definition common.h:6
static number sq(number x)
Definition common.h:11
scalar * list_append(scalar *list, scalar s)
Definition common.h:188
#define dv()
Definition common.h:92
const vector fm[]
Definition common.h:396
#define assert(a)
Definition config.h:107
macro2 diagonalize(int a)
Definition config.h:701
Point point
Definition conserving.h:86
else return n
Definition curvature.h:101
double dt
scalar s
Definition embed-tree.h:56
double d[2]
Definition embed.h:383
#define wmax(h, hmax)
Definition entrainment.h:25
double dry
Definition entrainment.h:30
#define reset(...)
Definition grid.h:1388
void solve_hessenberg(double H[nl *nl], double x[nl])
Definition hessenberg.h:54
#define a_baro(eta, i)
scalar eta_r
Definition hydro.h:60
scalar eta
Definition hydro.h:50
#define slope_limited(dz)
Definition hydro.h:522
#define gmetric(i)
The macro below can be overloaded to define the barotropic acceleration.
Definition hydro.h:195
vector hu
Definition hydro.h:209
static bool hydrostatic
Definition hydro.h:208
vector ha
Definition hydro.h:209
#define hpg(pg, phi, i, code)
Definition hydro.h:525
bool linearised
Definition hydro.h:61
void vertical_diffusion(Point point, scalar h, scalar s, double dt, double D, double dst, double s_b, double lambda_b)
Definition diffusion.h:33
double nu
Definition diffusion.h:135
double theta_H
Definition implicit.h:27
scalar res_eta
This can be used to optionally store the residual (for debugging).
Definition implicit.h:135
scalar rhs_eta
Definition implicit.h:137
bool rigid
The free-surface can be replaced with a "rigid lid" by setting rigid to true.
Definition implicit.h:48
vector alpha_eta
Definition implicit.h:138
int nl
Definition layers.h:9
#define foreach_layer()
#define data(k, l)
Definition linear.h:26
size_t max
Definition mtrace.h:8
void(* restriction)(Point, scalar)
size *double * b
int int int level
scalar * phil
double breaking
Definition nh.h:50
scalar w
Definition nh.h:48
void event_pressure(void)
Event: pressure (i++)
Definition nh.h:313
vector hf
Definition nh.h:170
static void box_matrix(Point point, scalar phi, scalar rhs, vector hf, scalar eta, double H[nl *nl], double d[nl])
Definition nh.h:116
void event_viscous_term(void)
Event: viscous_term (i++)
Definition nh.h:80
void event_cleanup(void)
Event: cleanup (i = end, last)
Definition nh.h:473
void event_defaults(void)
Event: defaults (i = 0)
Definition nh.h:59
static trace void relax_nh(scalar *phil, scalar *rhsl, int lev, void *data)
Definition nh.h:173
scalar phi
The electric potential and the volume charge density are scalars while the permittivity and conductiv...
Definition nh.h:48
static trace double residual_nh(scalar *phil, scalar *rhsl, scalar *resl, void *data)
Definition nh.h:233
mgstats mgp
Definition nh.h:49
trace mgstats mg_solve(scalar *a, scalar *b, double(*residual)(scalar *a, scalar *b, scalar *res, void *data), void(*relax)(scalar *da, scalar *res, int depth, void *data), void *data=NULL, int nrelax=4, scalar *res=NULL, int minlevel=0, double tolerance=TOLERANCE)
The user needs to provide a function which computes the residual field (and returns its maximum) as w...
Definition poisson.h:130
double TOLERANCE
Definition poisson.h:107
def _i
Definition stencils.h:405
Definition linear.h:21
int i
Definition linear.h:23
Definition common.h:78
double x
Definition common.h:79
Information about the convergence of the solver is returned in a structure.
Definition poisson.h:112
int nrelax
Definition poisson.h:116
int i
Definition common.h:44
scalar x
Definition common.h:47