Basilisk CFD
Adaptive Cartesian mesh PDE framework
Loading...
Searching...
No Matches
heights.h
Go to the documentation of this file.
1/** @file heights.h
2 */
3/**
4# Height-Functions
5
6The "height-function" is a vector field which gives the distance,
7along each coordinate axis, from the center of the cell to the closest
8interface defined by a volume fraction field. This distance is
9estimated using the "column integral" of the volume fraction in the
10corresponding direction. This integral is not always defined (for
11example because the interface is too far i.e. farther than 5.5 cells
12in our implementation) in which case the value of the field is set to
13*nodata*. See e.g. [Popinet, 2009](references.bib#popinet2009) for
14more details on height functions.
15
16We also store the "orientation" of the height function together with
17its value by adding *HSHIFT* if the volume fraction is unity on the
18"top" end. The function below applied to the value will return the
19corresponding height and orientation.
20
21The distance is normalised with the cell size so that the coordinates
22of the interface are given by
23
24~~~c
25(x, y + Delta*height(h.y[])) or (x + Delta*height(h.x[]), y)
26~~~
27*/
28
29#define HSHIFT 20.
30
31static inline double height (double H) {
32 return H > HSHIFT/2. ? H - HSHIFT : H < -HSHIFT/2. ? H + HSHIFT : H;
33}
34
35static inline int orientation (double H) {
36 return fabs(H) > HSHIFT/2.;
37}
38
39/**
40We make sure that two layers of ghost cells are defined on the
41boundaries (the default is one layer). */
42
43#define BGHOSTS 2
44
45/**
46## Half-column integration
47
48This helper function performs the integration on half a column, either
49"downward" (*j = -1*) or "upward" (*j = 1*). */
50
52{
53
54 /**
55 The 'state' of the height function can be: *complete* if both
56 ends were found, zero or one if one end was found and between zero
57 and one if only the interface was found. */
58
59 const int complete = -1;
60
61 for (int _d = 0; _d < dimension; _d++) {
62
63 /**
64 *S* is the state and *H* the (partial) value of the height
65 function. If we are on the (first) downward integration (*j =
66 -1*) we initialise *S* and *H* with the volume fraction in
67 the current cell. */
68
69 double S = c[], H = S, ci;
70
71 /**
72 On the upward integration (*j = 1*), we recover the state of the
73 downward integration. Both the state and the (possibly partial)
74 height value are encoded in a single number using a base 100
75 shift for the state. */
76
77 typedef struct { int s; double h; } HState;
78 HState state = {0, 0};
79 if (j == 1) {
80
81 /**
82 Check whether this is an inconsistent height. */
83
84 if (h.x[] == 300.)
85 state.s = complete, state.h = nodata;
86
87 /**
88 Otherwise, this is either a complete or a partial height. */
89
90 else {
91 int s = (h.x[] + HSHIFT/2.)/100.;
92 state.h = h.x[] - 100.*s;
93 state.s = s - 1;
94 }
95
96 /**
97 If this is a complete height, we start a fresh upward
98 integration. */
99
100 if (state.s != complete)
101 S = state.s, H = state.h;
102 }
103
104 /**
105 We consider the four neighboring cells of the half column, the
106 corresponding volume fraction *ci* is recovered either from the
107 standard volume fraction field *c* (first two cells) or from the
108 shifted field *cs* (last two cells). The construction of *cs* is
109 explained in the next section. */
110
111 for (int i = 1; i <= 4; i++) {
112 ci = i <= 2 ? c[i*j] : cs.x[(i - 2)*j];
113 H += ci;
114
115 /**
116 We then check whether the partial height is complete or not. */
117
118 if (S > 0. && S < 1.) {
119 S = ci;
120 if (ci <= 0. || ci >= 1.) {
121
122 /**
123 We just left an interfacial cell (*S*) and found a full or
124 empty cell (*ci*): this is a partial height and we can stop
125 the integration. If the cell is full (*ci = 1*) we shift
126 the origin of the height. */
127
128 H -= i*ci;
129 break;
130 }
131 }
132
133 /**
134 If *S* is empty or full and *ci* is full or empty, we went
135 right through he interface i.e. the height is complete and
136 we can stop the integration. The origin is shifted
137 appropriately and the orientation is encoded using the "HSHIFT
138 trick". */
139
140 else if (S >= 1. && ci <= 0.) {
141 H = (H - 0.5)*j + (j == -1)*HSHIFT;
142 S = complete;
143 break;
144 }
145 else if (S <= 0. && ci >= 1.) {
146 H = (i + 0.5 - H)*j + (j == 1)*HSHIFT;
147 S = complete;
148 break;
149 }
150
151 /**
152 If *ci* is identical to *S* (which is empty or full), we
153 check that *H* is an integer (i.e. its fractional value is
154 zero), otherwise we are in the case where we found an
155 interface but didn't go through it: this is an
156 inconsistent height and we stop the integration. */
157
158 else if (S == ci && trunc(H) != H)
159 break;
160 }
161
162 /**
163 We update the global state using the state *S* of the
164 half-integration. */
165
166 if (j == -1) {
167
168 /**
169 For the downward integration, we check that the partial heights
170 (*S != complete*) are consistent: if the first cell is full
171 or empty or if the last cell is interfacial, the partial
172 height is marked as inconsistent. */
173
174 if (S != complete && ((c[] <= 0. || c[] >= 1.) ||
175 (S > 0. && S < 1.)))
176 h.x[] = 300.; // inconsistent
177 else if (S == complete)
178 h.x[] = H;
179 else
180
181 /**
182 This is a partial height: we encode the state using a base 100
183 shift. */
184
185 h.x[] = H + 100.*(1. + (S >= 1.));
186 }
187 else { // j = 1
188
189 /**
190 For the upward integration, we update the current *state*
191 using the state of the half-integration *S* only if the
192 first downward integration returned a partial height, or if
193 the upward integration returned a complete height with a
194 smaller value than the (complete) height of the downward
195 integration. */
196
197 if (state.s != complete ||
198 (S == complete && fabs(height(H)) < fabs(height(state.h))))
199 state.s = S, state.h = H;
200
201 /**
202 Finally, we set the vector field *h* using the state and
203 height. */
204
205 if (state.s != complete)
206 h.x[] = nodata;
207 else
208 h.x[] = (state.h > 1e10 ? nodata : state.h);
209 }
210 }
211}
212
213/**
214## Column propagation
215
216Once columns are computed on a local 9-cells-high stencil, we will
217need to "propagate" these values upward or downward so that they are
218accessible at distances of up to 5.5 cells from the interface. This is
219important in 3D in particular where marginal (~45 degrees) cases may
220require such high stencils to compute consistent HF curvatures. We do
221this by selecting the smallest height in a 5-cells neighborhood along
222each direction. */
223
225{
226#if _OPENMP
227 foreach (serial) // not compatible with OpenMP
228#else
229 for (int _i = 0; _i < _N; _i++) /* foreach */
230#endif
231 for (int i = -2; i <= 2; i++)
232 for (int _d = 0; _d < dimension; _d++)
233 if (fabs(height(h.x[i])) <= 3.5 &&
234 fabs(height(h.x[i]) + i) < fabs(height(h.x[])))
235 h.x[] = h.x[i] + i;
236}
237
238/**
239## Multigrid implementation
240
241The *heights()* function takes a volume fraction field *c* and returns
242the height function vector field *h*. */
243
244#if !TREE
245trace
246void heights (scalar c, vector h)
247{
248
249 /**
250 We need a 9-points-high stencil (rather than the default
251 5-points). To do this we store in *s* the volume fraction field *c*
252 shifted by 2 grid points in the respective directions. We make sure
253 that this field uses the same boundary conditions as *c*. */
254
255 vector s[];
256 for (int _d = 0; _d < dimension; _d++)
257 for (int i = 0; i < nboundary; i++)
258 s.x.boundary[i] = c.boundary[i];
259
260 /**
261 To compute the height function, we sum the volume fractions in a
262 (half-)column starting at the current cell. We start by integrating
263 downward (*j = -1*) and then integrate upward (*j = 1*). */
264
265 for (int j = -1; j <= 1; j += 2) {
266
267 /**
268 We first build the shifted (by \f$\pm 2\f$) volume fraction field in each
269 direction. */
270
271 for (int _i = 0; _i < _N; _i++) /* foreach */
272 for (int _d = 0; _d < dimension; _d++)
273 s.x[] = c[2*j];
274
275 /**
276 We sum the half-column, downward or upward. We (exceptionally)
277 need to allow for stencil overflow. */
278
279 foreach (overflow)
280 half_column (point, c, h, s, j);
281 }
282
283 /**
284 Finally we "propagate" values along columns. */
285
287}
288
289/**
290## Tree implementation
291
292We first define the prolongation functions for heights. */
293
294#else // TREE
295for (int _d = 0; _d < dimension; _d++)
297{
298
299 /**
300 We try to prolongate columns from nearby non-prolongation cells. */
301
302 bool complete = true;
303 for (int _c = 0; _c < 4; _c++) /* foreach_child */ {
304 for (int i = -2; i <= 2; i++)
305 if (allocated(i) &&
307 fabs(height(h[i])) <= 3.5 &&
308 fabs(height(h[i]) + i) < fabs(height(h[])))
309 h[] = h[i] + i;
310 if (h[] == nodata)
311 complete = false;
312 }
314 return;
315
316 /**
317 If some children have not been initialised, we first check that the
318 (three in 2D, nine in 3D) coarse heights are defined and have
319 compatible orientations. If not, the children heights are
320 undefined. Otherwise, a (bi)quadratic fit of the coarse heights is
321 used to compute the children heights. */
322
323 int ori = orientation(h[]);
324#if dimension == 2
325 for (int i = -1; i <= 1; i++)
326 if (h[0,i] == nodata || orientation(h[0,i]) != ori)
327 return;
328
329 double h0 = (30.*height(h[]) + height(h[0,1]) + height(h[0,-1]))/16.
330 + HSHIFT*ori;
331 double dh = (height(h[0,1]) - height(h[0,-1]))/4.;
332 for (int _c = 0; _c < 4; _c++) /* foreach_child */
333 if (h[] == nodata)
334 h[] = h0 + dh*child.y - child.x/2.;
335#else // dimension == 3
336 double H[3][3], H0 = height(h[]);
337 for (int i = -1; i <= 1; i++)
338 for (int j = -1; j <= 1; j++) {
339 if (h[0,i,j] == nodata || orientation(h[0,i,j]) != ori)
340 return;
341 else
342 H[i+1][j+1] = height(h[0,i,j]) - H0;
343 }
344
345 double h0 =
346 2.*H0 + (H[2][2] + H[2][0] + H[0][0] + H[0][2] +
347 30.*(H[2][1] + H[0][1] + H[1][0] + H[1][2]))/512.
348 + HSHIFT*ori;
349 double h1 = (H[2][2] + H[2][0] - H[0][0] - H[0][2] +
350 30.*(H[2][1] - H[0][1]))/128.;
351 double h2 = (H[2][2] - H[2][0] - H[0][0] + H[0][2] +
352 30.*(H[1][2] - H[1][0]))/128.;
353 double h3 = (H[0][0] + H[2][2] - H[0][2] - H[2][0])/32.;
354 for (int _c = 0; _c < 4; _c++) /* foreach_child */
355 if (h[] == nodata)
356 h[] = h0 + h1*child.y + h2*child.z + h3*child.y*child.z - child.x/2.;
357#endif // dimension == 3
358}
359
360/**
361The *heights()* function implementation is similar to the multigrid
362case, but the construction of the shifted volume fraction field *cs*
363is more complex. */
364
365trace
367{
368 vector s[];
369 for (int _d = 0; _d < dimension; _d++)
370 for (int i = 0; i < nboundary; i++)
371 s.x.boundary[i] = c.boundary[i];
372
373 /**
374 To compute the shifted field, we first need to *restrict* the volume
375 fraction on all levels. */
376
377 restriction ({c});
378 for (int j = -1; j <= 1; j += 2) {
379
380 /**
381 We traverse the tree level by level, from coarse to fine.
382 On the root cell the height function is undefined. */
383
384 for (int _l = 0; _l < 0; _l++)
385 for (int _d = 0; _d < dimension; _d++)
386 h.x[] = nodata;
387
388 for (int l = 1; l <= depth(); l++) {
389
390 /**
391 We construct the (\f$\pm 2\f$) shifted field at this level. */
392
393 for (int _l = 0; _l < l; _l++)
394 for (int _d = 0; _d < dimension; _d++)
395 s.x[] = c[2*j];
396
397 /**
398 We then need to apply boundary conditions on the shifted
399 field. This is more complex than for a constant resolution grid.
400
401 We first construct the (\f$\pm 1\f$) shifted field for the
402 immediately coarser level. This is done by copying the volume
403 fraction field for pairs of adjacent cells. */
404
405 for (int _l = 0; _l < l - 1; _l++)
406 for (int _d = 0; _d < dimension; _d++) {
407 s.x[] = c[j];
408 s.x[j] = c[2*j];
409 }
410
411 /**
412 We can now use this shifted coarse field (which matches the
413 shifted fine field) to apply boundary conditions on coarse/fine
414 prolongation halos. */
415
416 foreach_halo (prolongation, l - 1)
417 for (int _d = 0; _d < dimension; _d++)
418 c.prolongation (point, s.x);
420
421 /**
422 We can now sum the half-column at this level, downward or upward
423 according to *j*. */
424
425 for (int _l = 0; _l < l; _l++)
426 half_column (point, c, h, s, j);
427 }
428 }
429
430 /**
431 We fill the prolongation cells with "nodata". The restriction
432 function does nothing as we have already defined *h* on all
433 levels. */
434
435 for (int _d = 0; _d < dimension; _d++) {
438 }
439
440 /**
441 Finally, we "propagate" values along columns. */
442
444
445 /**
446 Final prolongation cells will be filled with values obtained either
447 from neighboring columns or by interpolation from coarser levels
448 (see *refine_h_x()* above). */
449
450 for (int _d = 0; _d < dimension; _d++)
451 h.x.prolongation = refine_h_x;
452}
453
454#endif // TREE
455
456/**
457An attribute is added so that the height function field can be
458associated to a (VOF) tracer. */
459
462}
#define dimension
Definition bitree.h:3
#define boundary_iterate(type,...)
Definition boundaries.h:40
define l
define neighbor(o, p, q)((Point)
Definition cartesian.h:146
int x
Definition common.h:76
#define nodata
Definition common.h:7
int nboundary
Definition common.h:127
Point point
Definition conserving.h:86
scalar s
Definition embed-tree.h:56
scalar cs[]
The volume and area fractions are stored in these fields.
Definition embed.h:21
*cs[i, 0, 0] a *[i -1, 0, 0] j
Definition embed.h:88
#define depth()
Definition cartesian.h:14
static void half_column(Point point, scalar c, vector h, vector cs, int j)
Definition heights.h:51
scalar h
Definition heights.h:297
trace void heights(scalar c, vector h)
The heights() function implementation is similar to the multigrid case, but the construction of the s...
Definition heights.h:366
i
Definition heights.h:326
static int orientation(double H)
Definition heights.h:35
attribute
An attribute is added so that the height function field can be associated to a (VOF) tracer.
Definition heights.h:460
int ori
If some children have not been initialised, we first check that the (three in 2D, nine in 3D) coarse ...
Definition heights.h:323
double dh
Definition heights.h:331
double h0
Definition heights.h:329
#define HSHIFT
Definition heights.h:29
static void column_propagation(vector h)
Definition heights.h:224
static double height(double H)
Definition heights.h:31
scalar S
Definition gotm.h:16
void(* restriction)(Point, scalar)
void set_prolongation(scalar s, void(*prolongation)(Point, scalar))
void set_restriction(scalar s, void(*restriction)(Point, scalar))
static void no_restriction(Point point, scalar s)
static void no_data(Point point, scalar s)
int int int level
def _i
Definition stencils.h:405
Definition linear.h:21
def allocated(k, l, n)(mem_allocated(((Tree *) grid) -> L[point.level]->m, point.i+k, point.j+l)) @ @def NEIGHBOR(k, l, n)(((((Tree *) grid) ->L[point.level]->m) ->b[point.i+k][point.j+l])) @ @def PARENT(k, l, n)(((((Tree *) grid) ->L[point.level-1]->m) ->b[(point.i+2)/2+k][(point.j+2)/2+l])) @ @def allocated_child(k, l, n)(level< depth() &&mem_allocated(((Tree *) grid) ->L[point.level+1]->m, 2 *point.i- 2+k, 2 *point.j- 2+l)) @ @def CHILD(k, l, n)(((((Tree *) grid) ->L[point.level+1]->m) ->b[2 *point.i- 2+k][2 *point.j- 2+l])) @ @define CELL(m)(*((Cell *)(m))) @define depth()(grid->depth) @define aparent(k, l, n) CELL(PARENT(k, l, n)) @define child(k, l, n) CELL(CHILD(k, l, n)) @define cell CELL(NEIGHBOR(0, 0, 0)) @define neighbor(k, l, n) CELL(NEIGHBOR(k, l, n)) @def neighborp(l, m, n)(Point)
Definition tree.h:253
#define is_prolongation(cell)
Definition tree.h:411
#define foreach_halo(type, l)
Definition tree.h:514
#define is_boundary(cell)
Definition tree.h:412
int state
scalar c
Definition vof.h:57