Basilisk CFD
Adaptive Cartesian mesh PDE framework
Loading...
Searching...
No Matches
embed.h
Go to the documentation of this file.
1/** @file embed.h
2 */
3/**
4# Embedded boundaries
5
6Boundaries of general shape can be described using an integral
7(i.e. finite volume) formulation which takes into account the volume
8and area fractions of intersection of the embedded boundary with the
9Cartesian mesh.
10
11We will need to deal with volume fractions. Interpolations (for
12Dirichlet boundary conditions) assume a 5x5 stencil. */
13
14#include "fractions.h"
15#define BGHOSTS 2
16#define EMBED 1
17
18/**
19The volume and area fractions are stored in these fields. */
20
23
25
26/**
27Embedded boundary operators specific to trees are defined in this
28file. */
29
30#if TREE
31# include "embed-tree.h"
32#endif
33
34/**
35## Operator overloading
36
37Several standard operators, defined in [common.h]() need to be tuned
38to take into account the embedded fractions.
39
40The *SEPS* constant is used to avoid division by zero. */
41
42#undef SEPS
43#define SEPS 1e-30
44
45/**
46When combining third-order Dirichlet conditions and [approximate
47projections](navier-stokes/centered.h#approximate-projection),
48instabilities may occur due to feedback between the pressure mode and
49the velocity, amplified by the third-order derivative. This can be
50stabilised using the weighted average below when computing face
51velocities. The corresponding test case is [test/uf.c](). Note that if
52only second-order Dirichlet fluxes are used, simple averaging is
53stable. */
54
55#define cs_avg(a,i,j,k) \
56 ((a[i,j,k]*(1.5 + cs[i,j,k]) + a[i-1,j,k]*(1.5 + cs[i-1,j,k]))/ \
57 (cs[i,j,k] + cs[i-1,j,k] + 3.))
58
59/**
60Face gradients and face values, computed from cell-centered values
61must be tuned to take into account the area fractions of the embedded
62boundary. This follows the procedure described in [Johansen and
63Colella, 1998](#johansen1998), figure 3 and equation (16) in
64particular. Note that this is only used when using second-order
65fluxes. */
66
67#if dimension == 2
68#define face_condition(fs, cs) \
69 (fs.x[i,j] > 0.5 && fs.y[i,j + (j < 0)] && fs.y[i-1,j + (j < 0)] && \
70 cs[i,j] && cs[i-1,j])
71
72for (int _d = 0; _d < dimension; _d++)
73static inline double embed_face_gradient_x (Point point, scalar a, int i)
74{
75 int j = sign(fs.x[i,1] - fs.x[i,-1]);
76 assert (cs[i] && cs[i-1]);
78 return ((1. + fs.x[i])*(a[i] - a[i-1]) +
79 (1. - fs.x[i])*(a[i,j] - a[i-1,j]))/(2.*Delta);
80 return (a[i] - a[i-1])/Delta;
81}
82
83for (int _d = 0; _d < dimension; _d++)
84static inline double embed_face_value_x (Point point, scalar a, int i)
85{
86 int j = sign(fs.x[i,1] - fs.x[i,-1]);
87 return face_condition (fs, cs) ?
88 ((1. + fs.x[i])*cs_avg(a,i,0,0) + (1. - fs.x[i])*cs_avg(a,i,j,0))/2. :
89 cs_avg(a,i,0,0);
90}
91
92/**
93The generalisation to 3D is a bit more complicated. See Fig. 1 of
94[Schwartz et al, 2006](#schwartz2006). */
95
96#else // dimension == 3
97for (int _d = 0; _d < dimension; _d++)
98static inline coord embed_face_barycentre_z (Point point, int i)
99{
100 // Young's normal calculation
101 coord n1 = {0};
102 double nn = 0.;
103 scalar f = fs.z;
105 n1.x = (f[-1,-1,i] + 2.*f[-1,0,i] + f[-1,1,i] -
106 f[+1,-1,i] - 2.*f[+1,0,i] - f[+1,1,i]);
107 nn += fabs(n1.x);
108 }
109 if (!nn)
110 return (coord){0.,0.,0.};
112 n1.x /= nn;
113 // Position `p` of the face barycentre
114 coord n, p1, p;
115 ((double *)&n)[0] = n1.x, ((double *)&n)[1] = n1.y;
116 double alpha = line_alpha (f[0,0,i], n);
117 line_center (n, alpha, f[0,0,i], &p1);
118 p.x = ((double *)&p1)[0], p.y = ((double *)&p1)[1], p.z = 0.;
119 return p;
120}
121
122/**
123The macro below defines the condition which must be verified when
124applying the second-order flux interpolation and/or face
125averaging. The goal is to avoid using values from areas of the mesh
126which are not topologically connected. Doing so would couple
127disconnected subproblems (for example when solving a Laplacian) which
128would most probably lead to lack of convergence. This is very
129important for robustness when dealing with complex boundaries. */
130
131#define face_condition(fs, cs) \
132 (fs.x[i,j,k] > 0.5 && (fs.x[i,j,0] > 0.5 || fs.x[i,0,k] > 0.5) && \
133 fs.y[i,j + (j < 0),0] && fs.y[i-1,j + (j < 0),0] && \
134 fs.y[i,j + (j < 0),k] && fs.y[i-1,j + (j < 0),k] && \
135 fs.z[i,0,k + (k < 0)] && fs.z[i-1,0,k + (k < 0)] && \
136 fs.z[i,j,k + (k < 0)] && fs.z[i-1,j,k + (k < 0)] && \
137 cs[i-1,j,0] && cs[i-1,0,k] && cs[i-1,j,k] && \
138 cs[i,j,0] && cs[i,0,k] && cs[i,j,k])
139
140for (int _d = 0; _d < dimension; _d++)
141static inline double embed_face_gradient_x (Point point, scalar a, int i)
142{
143 assert (cs[i] && cs[i-1]);
145 // Bilinear interpolation of the gradient (see Fig. 1 of Schwartz et al., 2006)
146 int j = sign(p.y), k = sign(p.z);
147 if (face_condition(fs, cs)) {
148 p.y = fabs(p.y), p.z = fabs(p.z);
149 return (((a[i,0,0] - a[i-1,0,0])*(1. - p.y) +
150 (a[i,j,0] - a[i-1,j,0])*p.y)*(1. - p.z) +
151 ((a[i,0,k] - a[i-1,0,k])*(1. - p.y) +
152 (a[i,j,k] - a[i-1,j,k])*p.y)*p.z)/Delta;
153 }
154 return (a[i] - a[i-1])/Delta;
155}
156
157for (int _d = 0; _d < dimension; _d++)
158static inline double embed_face_value_x (Point point, scalar a, int i)
159{
161 // Bilinear interpolation
162 int j = sign(p.y), k = sign(p.z);
163 if (face_condition(fs, cs)) {
164 p.y = fabs(p.y), p.z = fabs(p.z);
165 return ((cs_avg(a,i,0,0)*(1. - p.y) + cs_avg(a,i,j,0)*p.y)*(1. - p.z) +
166 (cs_avg(a,i,0,k)*(1. - p.y) + cs_avg(a,i,j,k)*p.y)*p.z);
167 }
168 return cs_avg(a,i,0,0);
169}
170#endif // dimension == 3
171
172/**
173We use the functions above to redefine the face gradient macros. Note
174that the second-order face gradients and averaging are used only if
175the corresponding scalar attribute below (`third` because of
176third-order accuracy when using Dirichlet conditions, see
177[test/neumann.c]) is set to `true`. The default is `false`. */
178
180 bool third;
181}
182
183#undef face_gradient_x
184#define face_gradient_x(a,i) \
185 (a.third && fs.x[i] < 1. && fs.x[i] > 0. ? \
186 embed_face_gradient_x (point, a, i) : \
187 (a[i] - a[i-1])/Delta)
188
189#undef face_gradient_y
190#define face_gradient_y(a,i) \
191 (a.third && fs.y[0,i] < 1. && fs.y[0,i] > 0. ? \
192 embed_face_gradient_y (point, a, i) : \
193 (a[0,i] - a[0,i-1])/Delta)
194
195#undef face_gradient_z
196#define face_gradient_z(a,i) \
197 (a.third && fs.z[0,0,i] < 1. && fs.z[0,0,i] > 0. ? \
198 embed_face_gradient_z (point, a, i) : \
199 (a[0,0,i] - a[0,0,i-1])/Delta)
200
201#undef face_value
202#define face_value(a,i) \
203 (a.third && fs.x[i] < 1. && fs.x[i] > 0. ? \
204 embed_face_value_x (point, a, i) : \
205 cs_avg(a,i,0,0))
206
207/**
208The centered gradient must not use values of fields entirely contained
209within the embedded boundary (for which *cs* is zero). */
210
211#undef center_gradient
212#define center_gradient(a) (fs.x[] && fs.x[1] ? (a[1] - a[-1])/(2.*Delta) : \
213 fs.x[1] ? (a[1] - a[])/Delta : \
214 fs.x[] ? (a[] - a[-1])/Delta : 0.)
215
216/**
217## Utility functions for the geometry of embedded boundaries
218
219For a cell containing a fragment of embedded boundary (i.e. for which
220\f$0 < cs < 1\f$), *embed_geometry()* returns the area of the fragment,
221the relative position *p* of the barycenter of the fragment and the
222boundary normal *n*. */
223
224static inline
226{
227 *n = facet_normal (point, cs, fs);
228 double alpha = plane_alpha (cs[], *n);
229 double area = plane_area_center (*n, alpha, p);
230 normalize (n);
231 return area;
232}
233
234/**
235This function and the macro below shift the position $(x1,y1,z1)$ to
236the position of the barycenter of the embedded fragment. */
237
238static inline
239double embed_area_center (Point point, double * x1, double * y1, double * z1)
240{
241 double area = 0.;
242 if (cs[] > 0. && cs[] < 1.) {
243 coord n, p;
244 area = embed_geometry (point, &p, &n);
245 *x1 += p.x*Delta, *y1 += p.y*Delta, *z1 += p.z*Delta;
246 }
247 return area;
248}
249
250#define embed_pos() embed_area_center (point, &x, &y, &z)
251
252/**
253This function returns the value of field *s* interpolated linearly at
254the barycenter *p* of the fragment of embedded boundary contained
255within the cell. */
256
258{
259 assert (dimension == 2);
260 int i = sign(p.x), j = sign(p.y);
261 if (cs[i] && cs[0,j] && cs[i,j])
262 // bilinear interpolation when all neighbors are defined
263 return ((s[]*(1. - fabs(p.x)) + s[i]*fabs(p.x))*(1. - fabs(p.y)) +
264 (s[0,j]*(1. - fabs(p.x)) + s[i,j]*fabs(p.x))*fabs(p.y));
265 else {
266 // linear interpolation with gradients biased toward the
267 // cells which are defined
268 double val = s[];
269 for (int _d = 0; _d < dimension; _d++) {
270 int i = sign(p.x);
271 if (cs[i])
272 val += fabs(p.x)*(s[i] - s[]);
273 else if (cs[-i])
274 val += fabs(p.x)*(s[] - s[-i]);
275 }
276 return val;
277 }
278}
279
280/**
281This function "removes" (by setting their volume fraction to zero)
282cells which have inconsistent volume/surface fractions. This is
283important to guarantee the robustness of the solution for complex (and
284under-resolved) boundaries. The functions returns the number of cells
285removed. */
286
287struct Cleanup {
290 double smin; // minimum surface fraction (optional)
291 bool opposite; // whether to eliminate 'thin tubes' (optional)
292};
293
294trace
296 double smin = 0., bool opposite = false)
297{
298
299 /**
300 Since both surface and volume fractions are altered, iterations are
301 needed. This reflects the fact that changes are coupled spatially
302 through the topology of the domain: for examples, long, unresolved
303 "filaments" may need many iterations to be fully removed. */
304
305 int changed = 1, schanged = 0, i;
306 for (i = 0; i < 100 && changed; i++) {
307
308 /**
309 Face fractions of empty cells must be zero. */
310
311 for (int _i = 0; _i < _N; _i++) /* foreach_face */
312 if (s.x[] && ((!c[] || !c[-1]) || s.x[] < smin))
313 s.x[] = 0.;
314
315 changed = 0;
316 foreach(reduction(+:changed))
317 if (c[] > 0. && c[] < 1.) {
318 int n = 0;
319 for (int _d = 0; _d < dimension; _d++) {
320 for (int i = 0; i <= 1; i++)
321 if (s.x[i] > 0.)
322 n++;
323
324 /**
325 If opposite surface fractions are zero (and volume fraction
326 is non-zero), then we are dealing with a thin "tube", which
327 we just remove because it can sometimes lead to
328 non-convergence when
329 [projecting](navier-stokes/centered.h#approximate-projection)
330 the velocity field. */
331
332 if (opposite && s.x[] == 0. && s.x[1] == 0.)
333 c[] = 0., changed++;
334 }
335
336 /**
337 The number of "non-empty" faces (i.e. faces which have a
338 surface fraction larger than epsilon) cannot be smaller than
339 the dimension (the limiting cases correspond to a triangle in
340 2D and a tetrahedron in 3D). */
341
342 if (n < dimension)
343 c[] = 0., changed++;
344 }
345
346 schanged += changed;
347 }
348 if (changed)
349 fprintf (stderr, "src/embed.h:%d: warning: fractions_cleanup() did not converge after "
350 "%d iterations\n", LINENO, i);
351 return schanged;
352}
353
354/**
355## Dirichlet boundary condition
356
357This function returns the gradient of scalar *s*, normal to the
358embedded boundary defined by *cs*, of unit normal vector *n*
359(normalised using the Euclidean norm, not the box norm) and of
360centroid *p*. The Dirichlet boundary condition *bc* is imposed on the
361embedded boundary.
362
363The calculation follows [Johansen and Colella, 1998](#johansen1998)
364and is summarised in the figure below (see also Figure 4 of Johansen
365and Colella and Figure 2 of [Schwartz et al, 2006](#schwartz2006) for
366the 3D implementation).
367
368![Third-order normal gradient scheme](figures/dirichlet_gradient.svg)
369
370For degenerate cases, a non-zero value of *coef* is returned and
371`coef*s[]` must be added to the value returned to obtain the gradient. */
372
373#define quadratic(x,a1,a2,a3) \
374 (((a1)*((x) - 1.) + (a3)*((x) + 1.))*(x)/2. - (a2)*((x) - 1.)*((x) + 1.))
375
376for (int _d = 0; _d < dimension; _d++)
378 coord n, coord p, double bc,
379 double * coef)
380{
381 for (int _d = 0; _d < dimension; _d++)
382 n.x = - n.x;
383 double d[2], v[2] = {nodata,nodata};
384 bool defined = true;
385 for (int _d = 0; _d < dimension; _d++)
386 if (defined && !fs.x[(n.x > 0.)])
387 defined = false;
389 for (int l = 0; l <= 1; l++) {
390 int i = (l + 1)*sign(n.x);
391 d[l] = (i - p.x)/n.x;
392 double y1 = p.y + d[l]*n.y;
393 int j = y1 > 0.5 ? 1 : y1 < -0.5 ? -1 : 0;
394 y1 -= j;
395#if dimension == 2
396 if (fs.x[i + (i < 0),j] && fs.y[i,j] && fs.y[i,j+1] &&
397 cs[i,j-1] && cs[i,j] && cs[i,j+1])
398 v[l] = quadratic (y1, (s[i,j-1]), (s[i,j]), (s[i,j+1]));
399#else // dimension == 3
400 double z = p.z + d[l]*n.z;
401 int k = z > 0.5 ? 1 : z < -0.5 ? -1 : 0;
402 z -= k;
403 bool defined = fs.x[i + (i < 0),j,k];
404 for (int m = -1; m <= 1 && defined; m++)
405 if (!fs.y[i,j,k+m] || !fs.y[i,j+1,k+m] ||
406 !fs.z[i,j+m,k] || !fs.z[i,j+m,k+1] ||
407 !cs[i,j+m,k-1] || !cs[i,j+m,k] || !cs[i,j+m,k+1])
408 defined = false;
409 if (defined)
410 // bi-quadratic interpolation
411 v[l] =
412 quadratic (z,
413 quadratic (y1,
414 (s[i,j-1,k-1]), (s[i,j,k-1]), (s[i,j+1,k-1])),
415 quadratic (y1,
416 (s[i,j-1,k]), (s[i,j,k]), (s[i,j+1,k])),
417 quadratic (y1,
418 (s[i,j-1,k+1]), (s[i,j,k+1]), (s[i,j+1,k+1])));
419#endif // dimension == 3
420 else
421 break;
422 }
423 if (v[0] == nodata) {
424
425 /**
426 This is a degenerate case, we use the boundary value and the
427 cell-center value to define the gradient. */
428
429 d[0] = max(1e-3, fabs(p.x/n.x));
430 *coef = - 1./(d[0]*Delta);
431 return bc/(d[0]*Delta);
432 }
433
434 /**
435 For non-degenerate cases, the gradient is obtained using either
436 second- or third-order estimates. */
437
438 *coef = 0.;
439 if (v[1] != nodata) // third-order gradient
440 return (d[1]*(bc - v[0])/d[0] - d[0]*(bc - v[1])/d[1])/((d[1] - d[0])*Delta);
441 return (bc - v[0])/(d[0]*Delta); // second-order gradient
442}
443
445 coord n, coord p, double bc, double * coef)
446{
447#if dimension == 2
448 for (int _d = 0; _d < dimension; _d++)
449 if (fabs(n.x) >= fabs(n.y))
450 return dirichlet_gradient_x (point, s, cs, n, p, bc, coef);
451#else // dimension == 3
452 if (fabs(n.x) >= fabs(n.y)) {
453 if (fabs(n.x) >= fabs(n.z))
454 return dirichlet_gradient_x (point, s, cs, n, p, bc, coef);
455 }
456 else if (fabs(n.y) >= fabs(n.z))
457 return dirichlet_gradient_y (point, s, cs, n, p, bc, coef);
458 return dirichlet_gradient_z (point, s, cs, n, p, bc, coef);
459#endif // dimension == 3
460 return nodata;
461}
462
464
465/**
466## Surface force and vorticity
467
468We first define a function which computes
469\f$\mathbf{\nabla}\mathbf{u}\cdot\mathbf{n}\f$ while taking the boundary
470conditions on the embedded surface into account. */
471
472static inline
474{
475 coord dudn;
476 for (int _d = 0; _d < dimension; _d++) {
477 bool dirichlet = false;
478 double vb = u.x.boundary[embed] (point, point, u.x, &dirichlet);
479 if (dirichlet) {
480 double val;
481 dudn.x = dirichlet_gradient (point, u.x, cs, n, p, vb, &val);
482 }
483 else // Neumann
484 dudn.x = vb;
485 if (dudn.x == nodata)
486 dudn.x = 0.;
487 }
488 return dudn;
489}
490
491/**
492The force exerted by the fluid on the solid can be written
493\f[
494\mathbf{F}_{\Gamma} = - \int_{\partial \Gamma} ( - p\mathbf{I} +
4952 \mu \mathbf{D}) \cdot \mathbf{n}d \partial \Gamma
496\f]
497with \f$\Gamma\f$ the solid boundary. It can be further decomposed into a
498pressure (i.e. "form") drag
499\f[
500\mathbf{F}_p = \int_{\partial \Gamma} p \mathbf{n}d \partial \Gamma
501\f]
502and a viscous drag
503\f[
504\mathbf{F}_{\mu} = - \int_{\partial \Gamma}
5052 \mu \mathbf{D} \cdot \mathbf{n}d \partial \Gamma
506\f]
507These two vectors are computed by the *embed_force()* function.
508*/
509
510trace
512{
513 coord Fps = {0}, Fmus = {0};
514 foreach (reduction(+:Fps) reduction(+:Fmus), nowarning)
515 if (cs[] > 0. && cs[] < 1.) {
516
517 /**
518 To compute the pressure force, we first get the coordinates of
519 the barycentre of the embedded fragment, its area and normal,
520 and then interpolate the pressure field on the surface. */
521
522 coord n, b;
523 double area = embed_geometry (point, &b, &n);
524 area *= pow (Delta, dimension - 1);
525 double Fn = area*embed_interpolate (point, p, b);
526 for (int _d = 0; _d < dimension; _d++)
527 Fps.x += Fn*n.x;
528
529 /**
530 To compute the viscous force, we first need to retrieve the
531 local value of the viscosity (ideally at the barycentre of the
532 embedded fragment). This is not completely trivial since it is
533 defined on the faces of the cell. We use a
534 surface-fraction-weighted average value. */
535
536 if (constant(mu.x) != 0.) {
537 double mua = 0., fa = 0.;
538 for (int _d = 0; _d < dimension; _d++) {
539 mua += mu.x[] + mu.x[1];
540 fa += fm.x[] + fm.x[1];
541 }
542 mua /= fa;
543
544 /**
545 To compute the viscous force, we need to take into account the
546 (Dirichlet) boundary conditions for the velocity on the
547 surface. We only know how to do this when computing the normal
548 gradient \f$\mathbf{\nabla}\mathbf{u}\cdot\mathbf{n}\f$ using the
549 [embed_gradient()](#embed_gradient) function. We thus
550 need to re-express the viscous force using only normal
551 derivatives of the velocity field.
552
553 If we assume that \f$\mathbf{u}\f$ is constant on the boundary, then
554 \f[
555 \mathbf{{\nabla}} \mathbf{u} \cdot \mathbf{t}= \mathbf{0}
556 \f]
557 with \f$\mathbf{t}\f$ the unit tangent vector to the boundary. We
558 thus have the relations
559 \f[
560 \mathbf{{\nabla}} \mathbf{u} = \left( \mathbf{{\nabla}} \mathbf{u}
561 \cdot \mathbf{n} \right) \mathbf{n} + \left( \mathbf{{\nabla}}
562 \mathbf{u} \cdot \mathbf{t} \right) \mathbf{t} = \left(
563 \mathbf{{\nabla}} \mathbf{u} \cdot \mathbf{n} \right) \mathbf{n}
564 \f]
565 \f[
566 \mathbf{D}= \frac{1}{2} \left( \mathbf{{\nabla}} \mathbf{u} +
567 \mathbf{{\nabla}}^T \mathbf{u} \right) = \frac{1}{2}
568 \left(\begin{array}{cc}
569 2 \left( \mathbf{{\nabla}} u \cdot \mathbf{n} \right) n_x & \left(
570 \mathbf{{\nabla}} u \cdot \mathbf{n} \right) n_y + \left(
571 \mathbf{{\nabla}} v \cdot \mathbf{n} \right) n_x\\
572 \left( \mathbf{{\nabla}} u \cdot \mathbf{n} \right) n_y + \left(
573 \mathbf{{\nabla}} v \cdot \mathbf{n} \right) n_x & 2 \left(
574 \mathbf{{\nabla}} v \cdot \mathbf{n} \right) n_y
575 \end{array}\right)
576 \f]
577 \f[
578 \mathbf{F}_{\mu} = - \int_{\Gamma} \left(\begin{array}{c}
579 \left[2 \mu \left( \mathbf{{\nabla}} u \cdot \mathbf{n} \right)
580 n_x \right] n_x + \mu \left[ \left( \mathbf{{\nabla}} u \cdot \mathbf{n}
581 \right) n_y + \left( \mathbf{{\nabla}} v \cdot \mathbf{n} \right) n_x
582 \right] n_y\\
583 \left[2 \mu \left( \mathbf{{\nabla}} v \cdot \mathbf{n} \right)
584 n_y \right] n_y + \mu \left[ \left( \mathbf{{\nabla}} u \cdot \mathbf{n}
585 \right) n_y + \left( \mathbf{{\nabla}} v \cdot \mathbf{n} \right) n_x
586 \right] n_x
587 \end{array}\right)
588 \f]
589 \f[
590 \mathbf{F}_{\mu} = - \int_{\Gamma} \left(\begin{array}{c}
591 \mu \left[ \left( \mathbf{{\nabla}} u \cdot \mathbf{n} \right)
592 (n^2_x + 1) + \left( \mathbf{{\nabla}} v \cdot \mathbf{n} \right) n_x
593 n_y \right]\\
594 \mu \left[ \left( \mathbf{{\nabla}} v \cdot \mathbf{n} \right)
595 (n^2_y + 1) + \left( \mathbf{{\nabla}} u \cdot \mathbf{n} \right) n_x
596 n_y \right]
597 \end{array}\right)
598 \f]
599 */
600
601 assert (dimension == 2);
603 for (int _d = 0; _d < dimension; _d++)
604 Fmus.x -= area*mua*(dudn.x*(sq(n.x) + 1.) + dudn.y*n.x*n.y);
605 }
606 }
607
608 *Fp = Fps; *Fmu = Fmus;
609}
610
611/**
612In two dimensions, *embed_vorticity()* returns the vorticity of
613velocity field *u*, on the surface of the embedded boundary contained
614in the cell. *p* is the relative position of the barycentre of the
615embedded fragment and *n* its normal. */
616
617#if dimension == 2
619{
620 /**
621 We compute \f$\mathbf{{\nabla}}\mathbf{u}\cdot\mathbf{n}\f$, taking
622 the boundary conditions into account. */
623
625
626 /**
627 The vorticity is then obtained using the relations
628 \f[
629 \omega = \partial_x v - \partial_y u =
630 \left( \mathbf{{\nabla}} v \cdot \mathbf{n} \right) n_x -
631 \left( \mathbf{{\nabla}} u \cdot \mathbf{n} \right) n_y
632 \f]
633 */
634
635 return dudn.y*n.x - dudn.x*n.y;
636}
637#endif // dimension == 2
638
639/**
640## Flux through the embedded boundary
641
642This function computes the flux through the embedded boundary contained
643within a cell
644\f[
645\int_b \mu \nabla s\cdot\mathbf{n} db
646\f]
647with \f$db\f$ the elementary boundary surface and \f$\mathbf{n}\f$ the embedded
648boundary (outward-pointing) normal.
649
650Boundary conditions for *s* are taken into account.
651
652The result is returned in *val*.
653
654For degenerate cases, the value returned by the function must be
655multiplied by `s[]` and added to *val*. */
656
657double embed_flux (Point point, scalar s, vector mu, double * val)
658{
659
660 /**
661 If the cell does not contain a fragment of embedded boundary, the
662 flux is zero. */
663
664 *val = 0.;
665 if (cs[] >= 1. || cs[] <= 0.)
666 return 0.;
667
668 /**
669 If the boundary condition is homogeneous Neumann, the flux is
670 zero. */
671
672 bool dirichlet = false;
673 double grad = s.boundary[embed] (point, point, s, &dirichlet);
674 if (!grad && !dirichlet)
675 return 0.;
676
677 /**
678 We compute the normal, area and barycenter of the fragment of embedded
679 boundary contained within the cell. */
680
681 coord n = facet_normal (point, cs, fs), p;
682 double alpha = plane_alpha (cs[], n);
683 double area = plane_area_center (n, alpha, &p);
686
687 /**
688 If the boundary condition is Dirichlet, we need to compute the
689 normal gradient. */
690
691 double coef = 0.;
692 if (dirichlet) {
693 normalize (&n);
695 }
696
697 /**
698 We retrieve the (average) value of \f$\mu\f$ without the metric. */
699
700 double mua = 0., fa = 0.;
701 for (int _d = 0; _d < dimension; _d++) {
702 mua += mu.x[] + mu.x[1];
703 fa += fm.x[] + fm.x[1];
704 }
705 *val = - mua/(fa + SEPS)*grad*area/Delta;
706 return - mua/(fa + SEPS)*coef*area/Delta;
707}
708
709/**
710For ease of use, we replace the Neumann and Dirichlet functions with
711macros so that they can be used either for standard domain boundaries
712or for embedded boundaries. The distinction between the two cases is
713based on whether the `dirichlet` parameter is passed to the boundary
714function (using the `data` parameter). */
715
716macro2
717double dirichlet (double expr, Point point = point,
718 scalar s = _s, bool * data = data)
719{
720 return data ? embed_area_center (point, &x, &y, &z),
721 *((bool *)data) = true, expr : 2.*expr - s[];
722}
723
724macro2
725double dirichlet_homogeneous (double expr, Point point = point,
726 scalar s = _s, bool * data = data)
727{
728 return data ? *((bool *)data) = true, 0 : - s[];
729}
730
731macro2
732double neumann (double expr, Point point = point,
733 scalar s = _s, bool * data = data)
734{
735 return data ? embed_area_center (point, &x, &y, &z),
736 *((bool *)data) = false, expr : Delta*expr + s[];
737}
738
739macro2
740double neumann_homogeneous (double expr, Point point = point,
741 scalar s = _s, bool * data = data)
742{
743 return data ? *((bool *)data) = false, 0 : s[];
744}
745
746/**
747## Prolongation for the multigrid solver
748
749We use a simplified prolongation operator for the [multigrid
750solver](poisson.h#mg_solve) i.e. simple injection if bilinear
751interpolation would use values which are fully contained within the
752embedded boundary. */
753
754#if MULTIGRID
755static inline double bilinear_embed (Point point, scalar s)
756{
757 if (!coarse(cs) || !coarse(cs,child.x))
758 return coarse(s);
759 #if dimension >= 2
760 if (!coarse(cs,0,child.y) || !coarse(cs,child.x,child.y))
761 return coarse(s);
762 #endif
763 #if dimension >= 3
764 if (!coarse(cs,0,0,child.z) || !coarse(cs,child.x,0,child.z) ||
765 !coarse(cs,0,child.y,child.z) ||
766 !coarse(cs,child.x,child.y,child.z))
767 return coarse(s);
768 #endif
769 return bilinear (point, s);
770}
771
772#define bilinear(point, s) bilinear_embed(point, s)
773#endif // MULTIGRID
774
775/**
776## Lifting the "small cell" CFL restriction
777
778For explicit advection schemes, the timestep is limited by the CFL
779conditions
780\f[
781\Delta t < \frac{c_s\Delta}{f_i|u_i|}
782\f]
783where \f$i\f$ is the index of each face, and \f$c_s\f$ and \f$f_i\f$ are the
784embedded volume and face fractions respectively. It is clear that the
785timestep may need to be arbitrarily small if \f$c_s/f_s\f$ tends toward
786zero. This is the "small cell" restriction of cut-cell finite-volume
787techniques.
788
789A classical technique to avoid this limitation is to use a "cell
790merging" procedure, where the fluxes from cells which would "overflow"
791are redistributed to neighboring cells.
792
793The function below uses this approach to update a field *f*, advected
794by the face velocity field *uf*, with corresponding advection fluxes
795*flux*, during timestep *dt* which only verifies the standard CFL
796condition
797\f[
798\Delta t < \frac{\Delta}{|u_i|}
799\f]
800*/
801
802trace
804{
805
806 /**
807 Note that the distinction should be made between \f$c_m\f$, the cell
808 fraction metric, and \f$c_s\f$, the embedded fraction. This is not done
809 now so that embedded boundaries cannot be combined with a metric
810 yet.
811
812 The field *e* will store the "overflowing" sum of fluxes for each cell. */
813
814 scalar e[];
815 for (int _i = 0; _i < _N; _i++) /* foreach */ {
816
817 /**
818 If the cell is empty, it cannot overflow. */
819
820 if (cs[] <= 0.)
821 e[] = 0.;
822
823 /**
824 If the cell does not contain an embedded boundary, it cannot
825 overflow either and the sum of the fluxes can be added to advance
826 *f* in time. */
827
828 else if (cs[] >= 1.) {
829 for (int _d = 0; _d < dimension; _d++)
830 f[] += dt*(flux.x[] - flux.x[1])/Delta;
831 e[] = 0.;
832 }
833
834 /**
835 If the cell contains the embedded boundary, we compute the maximum
836 timestep verifying the restrictive CFL condition
837 \f[
838 \Delta t_{max} = \frac{c_s\Delta}{max(f_i|u_i|)}
839 \f]
840 Note that *fs* does not appear in the code below because *uf*
841 already stores the product \f$u_f \, f_m\f$. */
842
843 else {
844 double umax = 0.;
845 for (int i = 0; i <= 1; i++)
846 for (int _d = 0; _d < dimension; _d++)
847 if (fabs(uf.x[i]) > umax)
848 umax = fabs(uf.x[i]);
849 double dtmax = Delta*cm[]/(umax + SEPS);
850
851 /**
852 We compute the sum of the fluxes. */
853
854 double F = 0.;
855 for (int _d = 0; _d < dimension; _d++)
856 F += flux.x[] - flux.x[1];
857 F /= Delta*cm[];
858
859 /**
860 If the timestep is smaller than \f$\Delta t_{max}\f$, the cell
861 cannot overflow and *f* is advanced in time using the entire
862 flux. */
863
864 if (dt <= dtmax) {
865 f[] += dt*F;
866 e[] = 0.;
867 }
868
869 /**
870 Otherwise, the cell is filled "to the brim" by advancing *f*
871 using the maximum allowable timestep. The field *e* is used to
872 store the excess flux, weighted by the sum of the neighboring
873 embedded fractions. */
874
875 else {
876 f[] += dtmax*F;
877 double scs = 0.;
878 for (int _n = 0; _n < 1; _n++)
879 scs += sq(cm[]); //Correct?
880 e[] = (dt - dtmax)*F*cm[]/scs;
881 }
882 }
883 }
884
885 /**
886 In a second phase, the excesses in each cell are added to the
887 neighboring cells in proportion of their embedded fractions. */
888
889 for (int _i = 0; _i < _N; _i++) /* foreach */ {
890 double se = 0.;
891 for (int _n = 0; _n < 1; _n++)
892 se += e[];
893 f[] += cs[]*se;
894 }
895}
896
897/**
898## Default settings
899
900To apply the volume/area fraction-weighting to the solvers, we define
901the embedded solid using the embedded fractions. The fields are no
902longer constant and must be allocated.
903
904The *cm* and *fm* fields contain the product of the metric and solid
905factors. For a Cartesian coordinate system *cm* and *fm* are thus
906identical to the solid factor fields *cs* and *fs*. */
907
908/** @brief Event: metric (i = 0) */
909void event_metric (void)
910{
911 if (is_constant (fm.x)) {
912 for (int _d = 0; _d < dimension; _d++)
913 assert (constant (fm.x) == 1.);
914 fm = fs;
915 }
916 for (int _i = 0; _i < _N; _i++) /* foreach_face */
917 fs.x[] = 1.;
918 if (is_constant (cm)) {
919 assert (constant (cm) == 1.);
920 cm = cs;
921 }
922 for (int _i = 0; _i < _N; _i++) /* foreach */
923 cs[] = 1.;
924
925#if TREE
926 cs.refine = embed_fraction_refine;
927
928 /**
929 For prolongation we cannot use the same function since the surface
930 fraction field *fs* is not necessarily defined for prolongation
931 cells. So we switch back to the default fraction refinement (which
932 is less accurate but only relies on *cs*). */
933
934 // fixme: could work better with embed_fraction_refine
935 // see porous1.tst
936 cs.prolongation = fraction_refine;
937 for (int _d = 0; _d < dimension; _d++)
938 fs.x.prolongation = embed_face_fraction_refine_x;
939
940 /**
941 Note that we do not need to change the `refine` method since the
942 default `refine` method calls the prolongation method for each
943 component. */
944
945#endif
946 restriction ({cs, fs});
947}
948
949/**
950We add the embedded boundary to the default display. */
951
952/** @brief Event: defaults (i = 0 ) */
953void event_defaults (void) {
954 display ("draw_vof (c = 'cs', s = 'fs', filled = -1, "
955 "fc = {0.5,0.5,0.5}, order = 2);");
956}
957
958/**
959## References
960
961~~~bib
962@article{johansen1998,
963 title={A Cartesian grid embedded boundary method for Poisson's
964 equation on irregular domains},
965 author={Johansen, Hans and Colella, Phillip},
966 journal={Journal of Computational Physics},
967 volume={147},
968 number={1},
969 pages={60--85},
970 year={1998},
971 publisher={Elsevier},
972 url={https://pdfs.semanticscholar.org/17cd/babecd054d58da05c2ba009cccb3c687f58f.pdf}
973}
974
975@article{schwartz2006,
976 title={A Cartesian grid embedded boundary method for the heat equation
977 and Poisson’s equation in three dimensions},
978 author={Schwartz, Peter and Barad, Michael and Colella, Phillip and Ligocki,
979 Terry},
980 journal={Journal of Computational Physics},
981 volume={211},
982 number={2},
983 pages={531--550},
984 year={2006},
985 publisher={Elsevier},
986 url={https://cloudfront.escholarship.org/dist/prd/content/qt0fp606kk/qt0fp606kk.pdf}
987}
988~~~
989
990## See also
991
992* [Notes on drag force computation](/src/notes/drag.tm)
993*/
#define u
Definition advection.h:30
vector uf[]
We allocate the (face) velocity field.
Definition advection.h:29
double dtmax
The timestep is computed using the CFL condition on the face velocity field.
Definition all-mach.h:105
const vector alpha
Definition all-mach.h:47
if TRASH define &&NewPid *& val(newpid, 0, 0, 0)) -> pid > 0) @else @ define is_newpid()(((NewPid *)&val(newpid, 0, 0, 0)) ->pid > 0) @endif Array *linear_tree(size_t size, scalar newpid)
Definition balance.h:13
#define dimension
Definition bitree.h:3
define k
int bid
define l
define m((k)==0 &&(l)==0 &&(m)==0) macro2 foreach_point(double _x=0.
int y
Definition common.h:76
int x
Definition common.h:76
int z
Definition common.h:76
const scalar cm[]
Definition common.h:397
void display(const char *commands, bool overwrite=false)
Definition common.h:527
#define nodata
Definition common.h:7
void normalize(coord *n)
Definition common.h:98
static number sq(number x)
Definition common.h:11
static int sign(number x)
Definition common.h:13
const vector fm[]
Definition common.h:396
#define mu(f)
By default the Harmonic mean is used to compute the phase-averaged dynamic viscosity.
Definition two-phase.h:85
scalar f[]
The primary fields are:
Definition two-phase.h:56
#define assert(a)
Definition config.h:107
#define LINENO
Definition config.h:105
Point point
Definition conserving.h:86
return hxx pow(1.+sq(hx), 3/2.)
double nn
Definition curvature.h:102
double dt
static void embed_fraction_refine(Point point, scalar cs)
Definition embed-tree.h:16
scalar cs[]
The volume and area fractions are stored in these fields.
Definition embed.h:21
static coord embed_gradient(Point point, vector u, coord p, coord n)
Definition embed.h:473
macro2 double neumann(double expr, Point point=point, scalar s=_s, bool *data=data)
Definition embed.h:732
scalar a
Definition embed.h:73
double embed_interpolate(Point point, scalar s, coord p)
This function returns the value of field s interpolated linearly at the barycenter p of the fragment ...
Definition embed.h:257
*cs[i, 0, 0] a *[i -1, 0, 0] j
Definition embed.h:88
double embed_flux(Point point, scalar s, vector mu, double *val)
Definition embed.h:657
macro2 double neumann_homogeneous(double expr, Point point=point, scalar s=_s, bool *data=data)
Definition embed.h:740
double v[2]
Definition embed.h:383
vector fs[]
Definition embed.h:22
bool defined
Definition embed.h:384
scalar scalar coord coord p
Definition embed.h:378
double d[2]
Definition embed.h:383
scalar scalar coord coord double double * coef
For non-degenerate cases, the gradient is obtained using either second- or third-order estimates.
Definition embed.h:380
scalar int i
Definition embed.h:74
bid embed
Definition embed.h:463
double(* metric_embed_factor)(Point, coord)
Definition embed.h:24
#define quadratic(x, a1, a2, a3)
Definition embed.h:373
static double embed_geometry(Point point, coord *p, coord *n)
Definition embed.h:225
macro2 double dirichlet(double expr, Point point=point, scalar s=_s, bool *data=data)
For ease of use, we replace the Neumann and Dirichlet functions with macros so that they can be used ...
Definition embed.h:717
scalar scalar coord coord double bc
Definition embed.h:378
y1
Definition embed.h:394
static double embed_area_center(Point point, double *x1, double *y1, double *z1)
This function and the macro below shift the position $(x1,y1,z1)$ to the position of the barycenter o...
Definition embed.h:239
*cs[i, 0, 0] a *[i -1, 0, 0] *cs[i, j, 0] a *[i -1, j, 0] cs(cs[i, j, 0]+cs[i -1, j, 0]+3.)))/2. attribute
The generalisation to 3D is a bit more complicated.
Definition embed.h:179
void event_defaults(void)
We add the embedded boundary to the default display.
Definition embed.h:953
scalar s
Definition embed.h:377
scalar scalar coord n
Definition embed.h:378
trace int fractions_cleanup(scalar c, vector s, double smin=0., bool opposite=false)
Definition embed.h:295
trace void embed_force(scalar p, vector u, vector mu, coord *Fp, coord *Fmu)
The force exerted by the fluid on the solid can be written.
Definition embed.h:511
trace void update_tracer(scalar f, vector uf, vector flux, double dt)
Definition embed.h:803
#define face_condition(fs, cs)
Face gradients and face values, computed from cell-centered values must be tuned to take into account...
Definition embed.h:68
macro2 double dirichlet_homogeneous(double expr, Point point=point, scalar s=_s, bool *data=data)
Definition embed.h:725
double embed_vorticity(Point point, vector u, coord p, coord n)
In two dimensions, embed_vorticity() returns the vorticity of velocity field u, on the surface of the...
Definition embed.h:618
void event_metric(void)
Event: metric (i = 0)
Definition embed.h:909
#define cs_avg(a, i, j, k)
When combining third-order Dirichlet conditions and approximate projections, instabilities may occur ...
Definition embed.h:55
double dirichlet_gradient(Point point, scalar s, scalar cs, coord n, coord p, double bc, double *coef)
Definition embed.h:444
#define SEPS
Embedded boundary operators specific to trees are defined in this file.
Definition embed.h:43
void fraction_refine(Point point, scalar c)
Definition fractions.h:42
coord facet_normal(Point point, scalar c, vector s)
Definition fractions.h:426
double line_alpha(double c, coord n)
Definition geometry.h:37
void line_center(coord m, double alpha, double a, coord *p)
This function fills the coordinates p of the centroid of the fraction a of a square cell lying under ...
Definition geometry.h:514
#define plane_alpha
Definition geometry.h:133
#define plane_area_center(m, a, p)
Definition geometry.h:438
static float area(const KdtRect rect)
Definition kdt.c:439
#define data(k, l)
Definition linear.h:26
size_t max
Definition mtrace.h:8
void(* restriction)(Point, scalar)
static double bilinear(Point point, scalar s)
size *double * b
def _i
Definition stencils.h:405
This function "removes" (by setting their volume fraction to zero) cells which have inconsistent volu...
Definition embed.h:287
scalar c
Definition embed.h:288
vector s
Definition embed.h:289
double smin
Definition embed.h:290
bool opposite
Definition embed.h:291
Definition linear.h:21
Definition common.h:78
double x
Definition common.h:79
scalar x
Definition common.h:47
scalar y
Definition common.h:49
define n n define coarse(a, k, p, n)((double *)(PARENT(k
scalar flux[]
Definition vof.h:166
scalar c
Definition vof.h:57
return
Definition vof.h:77