Basilisk CFD
Adaptive Cartesian mesh PDE framework
Loading...
Searching...
No Matches
remapc.h
Go to the documentation of this file.
1/** @file remapc.h
2 */
3/**
4# Piecewise Polynomial Reconstruction
5
6This is inspired by the [PPR Fortran90
7library](https://github.com/dengwirda/PPR) of Darren Engwirda and
8Maxwell Kelley, which is included in [/src/ppr]().
9
10The initial version was written by Antoine Aubert.
11
12It is used for [layer remapping](/src/layered/remap.h) within the
13[multilayer framework](/src/layered/README).
14
15The basic idea is to construct a second-order polynomial (a parabola)
16fitting the integral over each layer and layer-interface values
17obtained with fourth-order polynomial fits. Monotonic limiting can
18also be applied.
19
20A significant difference with the PPR library is that explicit boundary
21conditions (Neumann or Robin/Navier) are imposed on the left and right
22(or top and bottom) boundaries. Also, being written in C99, this code
23runs transparently on GPUs.
24
25## Neumann boundary conditions
26
27We start with the simplest case i.e. the reconstruction of a parabola
28with Neumann conditions on the left boundary and a Dirichlet condition
29on the right boundary.
30
31The polynomial is given by
32\f[
33P(x) = \sum_i C_i x^i
34\f]
35It must verify the following conditions
36\f[
37\begin{aligned}
38P'(0) &= df_b, \\
39\int_0^1P(x)dx &= f \\
40P(1) &= s_r
41\end{aligned}
42\f]
43which leads to the code below. */
44
45static void remap_neumann (double s_r,
46 double f,
47 double df_b,
48 double C[3])
49{
50 C[1] = df_b;
51 C[2] = 3.*(s_r - df_b/2. - f)/2.;
52 C[0] = s_r - df_b - C[2];
53}
54
55/**
56## Robin/Navier boundary conditions
57
58This is a bit more complicated, with the following conditions
59\f[
60\begin{aligned}
61P(0) &= f_b + \lambda_b P'(0), \\
62\int_0^1P(x)dx &= f \\
63P(1) &= s_r
64\end{aligned}
65\f]
66which leads to the solution of the linear system coded below. */
67
68static void remap_robin (double s_r,
69 double f,
70 double f_b, double lambda_b,
71 double C[3])
72{
73 double a = - lambda_b, s = - a + (a - 1.)/3. + 1/2. [0];
74 double M[3][3] = {
75 { - a, 1./6., a/3. },
76 { 1., - 2./3., - 1./3.[0] },
77 { a - 1., 1./2, 1/2. - a }
78 };
79 double B[3] = { f, f_b, s_r };
80 for (int i = 0; i < 3; i++) {
81 C[i] = 0.;
82 for (int j = 0; j < 3; j++)
83 C[i] += M[i][j]*B[j];
84 C[i] /= s;
85 }
86}
87
88/**
89## Fourth-order polynomial for interface values
90
91The values between layers are approximated by fitting a fourth-order
92polynomial to the two layers above and below the interface. This gives
93the 4x4 linear system
94\f[
95\int_{x_i}^{x_{i+1}} P(x') dx' = f_i
96\f]
97with \f$k - 1 \leq i \leq k + 2\f$. The integration is normalized using
98\f[
99x' = \frac{x}{x_{k+1} - x_k}
100\f]
101*/
102
103static void right_value (int nvar, double s_right[nvar],
104 int k, int n,
105 const double x[n], double f[n-1][nvar],
106 double f_b, double lambda_b, double df_b,
107 double f_t, double lambda_t, double df_t)
108{
109 double xk = x[k], dx = x[k+1] - xk;
110 double M[4][4];
111 for (int i = (k == 0); i < (k == n - 3 ? 3 : 4); i++) {
112 double zeta1 = (x[i+k-1] - xk)/dx;
113 double z1n = zeta1;
114 double zeta0 = (x[i+k] - xk)/dx;
115 double z0n = zeta0;
116 for (int j = 0; j < 4; j++, z1n *= zeta1, z0n *= zeta0)
117 M[i][j] = (z0n - z1n)/(j + 1);
118 }
119
120 /**
121 The left (or bottom) and right (or top) layers must use the left and
122 right boundary conditions to close the system.
123
124 For the bottom layer we have: */
125
126 if (k == 0) {
127
128 /**
129 If `df_b` is non-zero we apply a Neumann boundary condition, which
130 changes one equation of the default linear system to */
131
132 if (df_b) {
133 M[0][0] = 0.;
134 M[0][1] = 1.;
135 M[0][2] = 0.;
136 M[0][3] = 0.;
137 }
138
139 /**
140 Otherwise we apply a Robin condition, as */
141
142 else {
143 M[0][0] = 1. [0];
144 M[0][1] = - lambda_b/dx;
145 M[0][2] = 0.;
146 M[0][3] = 0.;
147 }
148 }
149
150 /**
151 This is the equivalent for the top layer. The systems are different
152 at this location because \f$x'=1\f$ for the top layer and \f$x'=0\f$ for the
153 bottom layer. */
154
155 if (k == n - 3) {
156 double zetab = (x[k+2] - xk)/dx;
157 if (df_t) {
158 M[3][0] = 0.;
159 M[3][1] = 1.;
160 M[3][2] = 2.*zetab;
161 M[3][3] = 3.*sq(zetab);
162 }
163 else {
164 M[3][0] = 1.;
165 M[3][1] = zetab - lambda_t/dx;
166 M[3][2] = zetab*(zetab - 2.*lambda_t/dx);
167 M[3][3] = sq(zetab)*(zetab - 3.*lambda_t/dx);
168 }
169 }
170
171 /**
172 ### Solution of the linear system
173
174 We invert the linear system to get the coefficients \f$C_i\f$ of the
175 polynomial and return the value on the "right" side of the interval
176 by computing \f$P(x'=1)\f$. */
177
178 assert (smatrix_inverse (4, M, 1.e-30) > 1.e-20);
179
180 for (int v = 0; v < nvar; v++) {
181 double B[4];
182 for (int i = (k == 0); i < (k == n - 3 ? 3 : 4); i++)
183 B[i] = (x[k+i] - x[k-1+i])/dx*f[k-1+i][v];
184 if (k == 0) {
185 if (df_b)
186 B[0] = df_b*dx;
187 else
188 B[0] = f_b;
189 }
190 if (k == n - 3) {
191 if (df_t)
192 B[3] = df_t*dx;
193 else
194 B[3] = f_t;
195 }
196 double c[4] = {0.,0.,0.,0.};
197 for (int i = 0; i < 4; i++)
198 for (int j = 0; j < 4; j++)
199 c[i] += M[i][j]*B[j];
200 s_right[v] = c[3];
201 for (int i = 0; i < 3; i++)
202 s_right[v] += c[i];
203 }
204}
205
206/**
207## Polynomial reconstruction for "central" layers
208
209We first define a simple monotonic limiter. */
210
211static double minmodremap (double a, double b) {
212 return a*b <= 0. ? 0. :
213 fabs(a) < fabs(b) ? a : b;
214}
215
216/**
217The reconstruction function take the left and right values, the layers
218positions `x` and corresponding average values `f`, a limiting option
219and returns the polynomial `C` on the interval \f$[x_k:x_{k+1}]\f$. */
220
221static void remap_central (int nvar,
222 const double s_l[nvar], const double s_r[nvar],
223 const int n, const double x[n], double f[n-1][nvar],
224 const int k,
225 double C[3][nvar],
226 const bool limiter)
227{
228 double xk = x[k], dx = x[k+1] - xk;
229 for (int v = 0; v < nvar; v++) {
230 double sl = s_l[v], sr = s_r[v];
231
232 /**
233 If limiting is used, we first limit the left and right values. */
234
235 if (limiter) {
236 if ((f[k+1][v] - f[k][v])*(f[k][v] - f[k-1][v]) < 0.) {
237 sl = f[k][v];
238 sr = f[k][v];
239 }
240 else {
241 double sigma_left = 2.*(f[k][v] - f[k-1][v])/dx;
242 double sigma_center = 2.*(f[k+1][v] - f[k-1][v])/(x[k+2] - x[k-1] + dx);
243 double sigma_right = 2.*(f[k+1][v] - f[k][v])/dx;
245 if ((sl - f[k-1][v])*(f[k][v] - sl) < 0.)
246 sl = f[k][v] - 1./2.*dx*sigma;
247 if ((sr - f[k][v])*(f[k+1][v] - sr) < 0.)
248 sr = f[k][v] + 1./2.*dx*sigma;
249 }
250 }
251
252 /**
253 The polynomial fit is given by the linear system
254 \f[
255 \begin{aligned}
256 P(0) &= s_l, \\
257 \int_0^1P(x)dx &= f_k \\
258 P(1) &= s_r
259 \end{aligned}
260 \f]
261 which has for solution */
262
263 C[0][v] = sl;
264 C[1][v] = 6.*f[k][v] - 2.*sr - 4.*sl;
265 C[2][v] = 3.*(sr + sl - 2.*f[k][v]);
266
267 /**
268 If limiting is used, we check that the extrema of the polynomial do
269 not over- or undershoot. For a second-order polynomial, the extrema (if
270 \f$C_2\neq0\f$) is at
271 \f$x=-\frac{C_1}{2C_2}\f$ which can be developed as
272 \f[
273 x=-\frac{6f_k-2s_r-4s_l}{6(s_r+s_l-3f_k)}
274 \f]
275 If \f$x\in[0,0.5]\f$, we change \f$s_r\f$ such that
276 \f$x=0\f$. The new value is then \f$s_r=3f_k-2s_l\f$.
277
278 If \f$x\in[0.5,1]\f$, we change \f$s_l\f$ such that
279 \f$x=1\f$. The new value is then \f$s_l=3f_k-2s_r\f$.
280 */
281
282 if (limiter && C[1][v]*C[2][v] < 0. && C[1][v]/C[2][v] > - 2.) {
283 if (C[1][v]/C[2][v] > - 1.)
284 sr = 3.*f[k][v] - 2.*sl;
285 else
286 sl = 3.*f[k][v] - 2.*sr;
287
288 /**
289 We update the polynomial coefficients using these new bounds. */
290
291 C[0][v] = sl;
292 C[1][v] = 6.*f[k][v] - 2.*sr - 4.*sl;
293 C[2][v] = 3.*(sr + sl - 2.*f[k][v]);
294 }
295 }
296}
297
298/**
299## Remapping function
300
301This is the remapping interface. It takes as arguments the number of
302initial positions `npos`, the number of "remapped" positions `nnew`
303and the corresponding arrays of initial and remapped positions `xpos`
304and `xnew` respectively, as well as the initial averaged values on the
305corresponding intervals `fdat`. The remapped averaged values will be
306stored in `fnew`.
307
308The `fdat` and `fnew` arrays are also indexed by `nvar` which allows
309to remap several fields simultaneously, for performance. Note however
310that the interface does not allow to apply different boundary
311conditions for each of these fields.
312
313Note that the `xpos` and `xnew` must be stored in strict increasing
314order i.e. negative interval values \f$x_{i+1} - x_i\f$ are not allowed.
315
316The "bottom" and "top" boundary conditions are given by the
317$(f_b,\lambda_b,df_b)$ parameters (resp. $(f_t,\lambda_t,df_t)$). If
318\f$df_b\f$ (resp. \f$df_t\f$) is non-zero, then Neumann boundary conditions
319are applied i.e.
320\f[
321\partial_n P(0) = df_b
322\f]
323where \f$n\f$ is the direction "normal" to the boundary i.e. \f$+x\f$ for the
324bottom/left boundary and \f$-x\f$ for the top/right boundary.
325
326If \f$df_b\f$ (resp. \f$df_t\f$) is zero, then Robin/Navier boundary conditions
327are applied i.e.
328\f[
329P(0) = f_b + \lambda_b \partial_n P(0)
330\f]
331
332To impose a Neumann zero boundary condition one can set \f$\lambda_b\f$ to
333`HUGE` and \f$f_b\f$ to zero.
334
335If limiter is `true` then monotonic limiters are applied. In this case
336the Neumann zero condition leads to a constant profile in the top or
337bottom layer, which guarantees boundedness of the remapping. Note that
338other boundary conditions will not guarantee boundedness. */
339
340void remap_c (int npos, int nnew,
341 const double xpos[npos], const double xnew[nnew],
342 const int nvar,
343 double fdat[npos-1][nvar], double fnew[nnew-1][nvar],
344 double f_b, double lambda_b, double df_b,
345 double f_t, double lambda_t, double df_t,
346 bool limiter)
347{
348
349 /**
350 We can only remap on the same interval. It would be possible to
351 remap on a smaller interval (a subset) but there is no need for now
352 and this restriction makes things simpler. */
353
354 assert (xnew[0] == xpos[0] && xnew[nnew-1] == xpos[npos - 1]);
355
356 /**
357 We go through each new interval. */
358
359 double x = xnew[0], s_left[nvar], C[3][nvar];
360 for (int v = 0; v < nvar; v++)
361 fnew[0][v] = 0., s_left[v] = 0.;
362 for (int inew = 0, k = -1; inew < nnew - 1 && x < xpos[npos - 1];) {
363
364 /**
365 ## Integration interval
366
367 We first check if the starting point (x) of the integration
368 interval is beyond the current interval [xpos[k]:xpos[k+1]]. */
369
370 if (x >= xpos[k + 1]) {
371
372 /**
373 If this is the case, we need to consider the next interval and
374 compute the corresponding polynomial coefficients. */
375
376 k++;
377
378 /**
379 The top layer is a special case. */
380
381 if (k == npos - 2) {
382
383 /**
384 If limiting is used together with Neumann zero fluxes, the
385 value must be constant in the last layer. */
386
387 if (limiter && lambda_t == HUGE)
388 for (int v = 0; v < nvar; v++) {
389 C[0][v] = fdat[k][v];
390 C[1][v] = 0.;
391 C[2][v] = 0.;
392 }
393
394 /**
395 Otherwise we use Neumann or Robin
396 boundary conditions to compute the polynomial. */
397
398 else
399 for (int v = 0; v < nvar; v++) {
400 double B[3];
401 if (df_t)
402 remap_neumann (s_left[v], fdat[k][v], - df_t*(xpos[k+1] - xpos[k]), B);
403 else
404 remap_robin (s_left[v], fdat[k][v], f_t, - lambda_t/(xpos[k+1] - xpos[k]), B);
405
406 /**
407 Since the functions above are written for the bottom layer, we
408 need to apply symmetry conditions i.e. transform \f$x\f$ into \f$1 -
409 x\f$. This gives the following polynomial coefficients. */
410
411 C[0][v] = B[0] + B[1] + B[2];
412 C[1][v] = - B[1] - 2.*B[2];
413 C[2][v] = B[2];
414 }
415 }
416 else {
417
418 /**
419 For all other layers, we need to compute the right value. If
420 limiting is used together with Neumann zero fluxes we impose a
421 constant profile in the bottom or top layer. */
422
423 double s_right[nvar];
424 if (limiter && lambda_b == HUGE && k == 0)
425 for (int v = 0; v < nvar; v++)
426 s_right[v] = fdat[0][v];
427 else if (limiter && lambda_t == HUGE && k == npos - 3)
428 for (int v = 0; v < nvar; v++)
429 s_right[v] = fdat[npos - 2][v];
430 else
432
433 /**
434 We use boundary conditions for the bottom layer. */
435
436 if (k == 0)
437 for (int v = 0; v < nvar; v++) {
438 double B[3];
439 if (df_b)
440 remap_neumann (s_right[v], fdat[0][v], df_b*(xpos[1] - xpos[0]), B);
441 else
442 remap_robin (s_right[v], fdat[0][v], f_b, lambda_b/(xpos[1] - xpos[0]), B);
443 for (int i = 0; i < 3; i++)
444 C[i][v] = B[i];
445 }
446
447 /**
448 Or central remapping, with optional limiting, using the left
449 and right values for all the other layers. */
450
451 else
453
454 /**
455 The new left value is just the old right value. */
456
457 for (int v = 0; v < nvar; v++)
458 s_left[v] = s_right[v];
459 }
460 }
461
462 /**
463 ## End of integration interval
464
465 We determine the value of `xe`, the end of the integration
466 interval, by comparing the end of the new interval, `xnew[inew+1]`
467 with the end of the current interval `xpos[k+1]`. `jnew` records
468 whether we need to change interval after the integration. */
469
470 double xe;
471 int jnew = inew;
472 if (xnew[inew + 1] < xpos[k + 1]) {
473 jnew = inew + 1;
474 xe = xnew[jnew];
475 for (int v = 0; v < nvar; v++)
476 fnew[jnew][v] = 0.;
477 }
478 else
479 xe = xpos[k + 1];
480
481 /**
482 ## Integration
483
484 We compute the integral of the current polynomial between `x` and
485 `xe`. Since the polynomial is defined on the normalized interval
486 [0:1], we first normalize the values of `x` and `xe`, in `a` and
487 `b` respectively. We also need to de-normalize the integral with
488 the ratio `dx1`. Note that it is particularly important to perform
489 the integration in normalized space to minimize round-off errors
490 which can cause instabilities when using single-precision on
491 GPUs. */
492
493 double xk = xpos[k], dx = xpos[k+1] - xk, dx1 = dx/(xnew[inew + 1] - xnew[inew]);
494 double a = (x - xk)/dx, b = (xe - xk)/dx;
495 assert (a >= 0. && a <= b && b <= 1.); // xpos and xnew must be ordered properly
496 for (int v = 0; v < nvar; v++) {
497 double an = a, bn = b;
498 for (int i = 0; i < 3; i++, an *= a, bn *= b)
499 fnew[inew][v] += C[i][v]/(i + 1)*(bn - an)*dx1;
500 }
501
502 /**
503 ## Update of integration bounds
504
505 The new integration interval starts at `xe` and concerns `jnew`. */
506
507 x = xe;
508 inew = jnew;
509 }
510}
const vector a
Definition all-mach.h:59
define k
int x
Definition common.h:76
#define HUGE
Definition common.h:6
static number sq(number x)
Definition common.h:11
double smatrix_inverse(const int n, double m[n][n], double pivmin)
Definition common.h:434
scalar f[]
The primary fields are:
Definition two-phase.h:56
#define assert(a)
Definition config.h:107
else return n
Definition curvature.h:101
scalar s
Definition embed-tree.h:56
*cs[i, 0, 0] a *[i -1, 0, 0] j
Definition embed.h:88
double v[2]
Definition embed.h:383
scalar int i
Definition embed.h:74
#define dx(s)
We first define some useful macros, following the notations in Bonneton et al, 2011.
const scalar sigma[]
const vector lambda_b[]
Definition diffusion.h:136
size *double * b
static void remap_robin(double s_r, double f, double f_b, double lambda_b, double C[3])
Definition remapc.h:68
static void remap_central(int nvar, const double s_l[nvar], const double s_r[nvar], const int n, const double x[n], double f[n-1][nvar], const int k, double C[3][nvar], const bool limiter)
The reconstruction function take the left and right values, the layers positions x and corresponding ...
Definition remapc.h:221
static void right_value(int nvar, double s_right[nvar], int k, int n, const double x[n], double f[n-1][nvar], double f_b, double lambda_b, double df_b, double f_t, double lambda_t, double df_t)
Definition remapc.h:103
static void remap_neumann(double s_r, double f, double df_b, double C[3])
Definition remapc.h:45
static double minmodremap(double a, double b)
Definition remapc.h:211
void remap_c(int npos, int nnew, const double xpos[npos], const double xnew[nnew], const int nvar, double fdat[npos-1][nvar], double fnew[nnew-1][nvar], double f_b, double lambda_b, double df_b, double f_t, double lambda_t, double df_t, bool limiter)
Definition remapc.h:340
scalar c
Definition vof.h:57