Basilisk CFD
Adaptive Cartesian mesh PDE framework
Loading...
Searching...
No Matches
no-coalescence.h
Go to the documentation of this file.
1/** @file no-coalescence.h
2 */
3/**
4# Avoiding coalescence of VOF interfaces
5
6When two interfaces defined by the same VOF tracer are close enough
7(of the order of the cell size), they automatically merge. This is one
8of the strength and weakness of the VOF method.
9
10In some cases, it may be desirable to avoid coalescence entirely, for
11example in the case of foams, emulsions, bubble clouds etc...
12
13A simple way to do this is to use a different VOF tracer for each
14bubble/droplet. When one wants to simulate more than a few bubbles,
15this can of course become very expensive (both in CPU and memory).
16
17This simple idea can be improved by noting that it would be sufficient
18to use different VOF tracers only for bubbles which are "too close" to
19one another. Determining the minimum number of VOF tracers required,
20for a given arrangement of bubbles, is clearly a variant of the [graph
21coloring](https://en.wikipedia.org/wiki/Graph_coloring) problem. In
22two dimensions, the famous [four color
23theorem](https://en.wikipedia.org/wiki/Four_color_theorem) states that
24a maximum of four VOF tracers are required. Note however that finding
25this optimal coloring can be very difficult
26([NP-complete](https://en.wikipedia.org/wiki/Graph_coloring#Computational_complexity)). The
27important point here is that one can expect that even a non-optimal
28number of VOF tracers will be much smaller than the number of bubbles.
29
30## User interface
31
32This file is typically combined with the [two-phase
33solver](/src/two-phase.h). From the user point-of-view, the only thing
34to be aware of is that the default *f* volume fraction field is not
35transported using VOF anymore, but is the sum of all VOF
36tracers. Individual VOF tracers are named *f0*, *f1*, *f2*, ... and
37are stored in the *interfaces* list (defined by
38[vof.h](/src/vof.h)). They should be used in particular to display the
39actual interfaces, as displaying interfaces using *f* will result in
40coalescence artefacts.
41
42## Possible improvements
43
441. Find a way to relax the capillary time step in the context of Multi-VoF.
452. Rare events might generate new tracers that are not necessary most of the
46simulation time: find a way to detect these "useless" tracers and delete them.
47
48## Utility functions
49
50We will need to "tag" individual bubbles. EPS is the threshold used
51for tagging.
52threshold_volume is the minimal volum to which we ignore coalesence event */
53
54#include "tag.h"
55
56/**
57This function returns a renamed clone of the default volume fraction
58field *f*, with *i* its index. */
59
60static scalar fclone (int i)
61{
62 scalar c = {0} /* new scalar */;
63 scalar_clone (c, f);
64 free (c.name);
65 char s[80];
66 sprintf (s, "%s%d", f.name, i);
67 c.name = strdup (s);
68 return c;
69}
70
71/**
72This function does a fast detection of cases which may correspond to
73two interfaces being close to one another. */
74
75#define EPS 1e-10
76
78{
79 if (c[] > EPS)
80 return false;
81 for (int i = 0; i <= 2; i++)
82 for (int j = -2; j <= 2; j++)
83#if dimension > 2
84 for (int k = -2; k <= 2; k++)
85#endif // dimension > 2
86 if (c[i,j,k] > EPS && c[-i,-j,-k] > EPS)
87 return true;
88 return false;
89}
90
91/**
92This is similar to the function above, but now takes into account
93whether the two interfaces belong to different bubbles (identified by
94the tag field *b*). If they do then the indices of the two bubbles are
95returned in *b1* and *b2*. */
96
98 int * b1, int * b2)
99{
100 if (c[] > EPS)
101 return false;
102 for (int i = 0; i <= 2; i++)
103 for (int j = -2; j <= 2; j++)
104#if dimension > 2
105 for (int k = -2; k <= 2; k++)
106#endif // dimension > 2
107 if (c[i,j,k] > EPS && c[-i,-j,-k] > EPS &&
108 b[i,j,k] && b[-i,-j,-k] && b[i,j,k] != b[-i,-j,-k]) {
109 *b1 = b[i,j,k] - 1; *b2 = b[-i,-j,-k] - 1;
110 return true;
111 }
112 return false;
113}
114
115#if _MPI
116static void reduce_bubbles_op (void * pin, void * pout, int * len,
118{
119 int * in = pin, leni, * out = pout, leno;
120 for (leni = 0; leni < *len && in[leni] >= 0; leni++);
121 for (leno = 0; leno < *len && out[leno] >= 0; leno++);
122 int add = leno;
123 for (int i = 0; i < leni; i += 2) {
124 bool found = false;
125 for (int j = 0; j < leno && !found; j++)
126 if ((in[i] == out[j] && in[i + 1] == out[j+1]) ||
127 (in[i] == out[j+1] && in[i + 1] == out[j]))
128 found = true;
129 if (!found) {
130 assert (add < *len);
131 out[add++] = in[i];
132 assert (add < *len);
133 out[add++] = in[i + 1];
134 }
135 }
136}
137
138trace
139static void reduce_bubbles (Array * tc)
140{
141 if (npe() > 1) {
142 int len1 = tc->len/sizeof(int), len = len1;
144 if (len > 0) {
145 tc->max = len*sizeof(int);
146 tc->p = realloc (tc->p, tc->max);
147 for (int i = len1; i < len; i++)
148 ((int *)tc->p)[i] = -1;
149 MPI_Op op;
152 MPI_Op_free (&op);
153 for (len1 = 0; len1 < len && ((int *)tc->p)[len1] >= 0; len1++);
154 tc->len = len1*sizeof(int);
155 }
156 }
157}
158#endif // _MPI
159
160/**
161## Algorithm */
162
163trace
165{
166
167 /**
168 We first make a quick test to check which VOF tracers may correspond
169 to bubbles which are too close to one another. This is essentially
170 an optimisation which avoids calling the relatively expensive
171 *tag()* function if it is obviously not necessary. */
172
173 int nvar = datasize/sizeof(double), too_close[nvar];
174 for (int i = 0; i < nvar; i++)
175 too_close[i] = false;
176 foreach(serial) // no openMP
177 for (int _c = 0; _c < 1; _c++) /* scalar in interfaces */
178 if (tracer_is_close (point, c))
179 too_close[c.i] = true;
180 #if _MPI
183 #endif
185 // scalar * not_close = NULL;
186 for (int _c = 0; _c < 1; _c++) /* scalar in interfaces */
187 if (too_close[c.i])
189#if 0
190 else
192
193 if (list_len (interfaces) > 2) {
194 scalar b[] = {interfaces[0].i};
195 for (int _c = 0; _c < 1; _c++) /* scalar in not_close */
196 if (c.i != b.i)
197 for (int _i = 0; _i < _N; _i++) /* foreach */{
198 b[] += c[];
199 c[] = 0;
200 }
201 }
202 free (not_close);
203#endif
204
205 for (int _c = 0; _c < 1; _c++) /* scalar in maybe_close */ {
206
207 /**
208 For each VOF tracer which may be too close, we first tag the
209 corresponding bubbles. */
210
211
212 scalar b[];
213 for (int _i = 0; _i < _N; _i++) /* foreach */
214 b[] = c[] > EPS;
215 tag (b);
216
217 /**
218 The next step is to build the array *tc* of the bubbles which are
219 indeed too close to one another. */
220
221 Array * tc = array_new();
222 foreach (serial) { // no openMP
223 int b1 = 0, b2 = 0;
224 if (bubbles_are_close (point, c, b, &b1, &b2)) {
225 for (int l = 0, * p = tc->p; l < tc->len/sizeof(int); l += 2, p += 2)
226 if ((*p == b1 && p[1] == b2) ||
227 (p[1] == b1 && *p == b2)) {
228 // the pair of bubbles is already in the list
229 b1 = -1; break;
230 }
231 // Add these bubbles to the list if the pair is not already there
232 if (b1 != -1) {
233 assert (b1 >= 0 && b2 >= 0);
234 array_append (tc, &b1, sizeof (int));
235 array_append (tc, &b2, sizeof (int));
236 }
237 }
238 }
239
240#if _MPI
242#endif
243
244 int len = tc->len/sizeof(int);
245 if (len > 0) {
246
247 /**
248 ### Neighboring tracers
249
250 We need to know which tracer fields are neigboring each
251 bubble. If the tracer of index *j* is neighboring the bubble
252 of index *i* (in *tc*), then *adj[i*nvar + j]* is set to *true*.
253 Besides we add two to nvar since 3 scalar fields might be added
254 simulataneously in one step. */
255
256 int nvar = datasize/sizeof(double) + 3, adj[len*nvar];
257 for (int i = 0; i < len*nvar; i++)
258 adj[i] = false;
259
260 /**
261 Since we are updating *adj*, we cannot use openMP. */
262
263 foreach (serial) // no openMP
264 if (b[])
265 for (int i = 0, * p = tc->p; i < len; i++, p++)
266 if (b[] == *p + 1)
267
268 /**
269 We check whether bubble b[] neighbors cells containing
270 another tracer. */
271
272 for (int _n = 0; _n < ; _n++)
273 for (int _s = 0; _s < 1; _s++) /* scalar in interfaces */
274 if (s.i != c.i && s[] > EPS)
275 adj[i*nvar + s.i] = true;
276
277#if _MPI
280#endif
281
282 /**
283 ### Finding a replacement tracer
284
285 If this is the first bubble we need to replace, then the only
286 existing VOF interface is *f*. We create a new interface, add it
287 to the list and remove *f* from the list of interfaces (since it
288 is not advected by VOF anymore). */
289
290 if (c.i == f.i) {
291 scalar f1 = fclone (0);
292 for (int _i = 0; _i < _N; _i++) /* foreach */
293 f1[] = f[];
294 interfaces = list_copy ({f});
295 swap (char *, f.name, f1.name);
296 f.i = f1.i;
297 }
298
299 /**
300 Array *rep* will contain the index of the replacement VOF tracer
301 for each bubble. */
302
303 int rep[len/2];
304 for (int i = 0, * p = tc->p; i < len; i += 2) {
305
306 /**
307 The indices of the pair of neighboring bubbles are stored in
308 `p[i]` and `p[i+1]`. We need to replace only one of the two
309 bubbles. We choose to replace the bubble with the smallest
310 number of neighboring tracers. */
311
312 int n1 = 0, n2 = 0, j = i;
313 for (int _s = 0; _s < 1; _s++) /* scalar in interfaces */{
314 if (adj[i*nvar + s.i]) n1++;
315 if (adj[(i + 1)*nvar + s.i]) n2++;
316 }
317
318 /**
319 We check if the tags are already modified, in which case
320 we do not want to modify them again. */
321
323 for (int e = 0; e < i; e++) {
324 if (p[i] == p[e])
325 first_modified = 1;
326 if (p[i + 1] == p[e])
327 second_modified = 1;
328 }
329
331 p[i] = -1;
332 p[i + 1] = -1;
333 }
334 else {
335 if (n2 < n1) {
337 p[i] = p[i + 1];
338 j++;
339 }
340 else {
341 tag_not_modified = p[i + 1];
342 p[i + 1] = p[i];
343 }
344 }
345
346 /**
347 We look for a replacement VOF tracer which is not already
348 neighboring the bubble. */
349
350 rep[i/2] = -1;
351 for (int _s = 0; _s < 1; _s++) /* scalar in interfaces */
352 if (s.i != c.i && !adj[j*nvar + s.i]) {
353 rep[i/2] = s.i;
354 break;
355 }
356
357
358 /**
359 If we didn't find any, we create a new one. */
360
361 if (rep[i/2] < 0) {
363 reset ({t}, 0.);
365 rep[i/2] = t.i;
366 }
367
368 /**
369 Refresh the adj list for all pairs of bubbles which contain
370 an index adj to p[i] or to tag_not_modified. */
371
372 if (p[i] != -1)
373 for (int e = i; e < len; e += 2) {
374 if (p[e] == p[i])
375 for (int k = 0; k < len; k++)
376 if (p[k] == p[e + 1])
377 adj[k*nvar + rep[i/2]] = 1;
378 if (p[e+1] == p[i])
379 for (int k = 0; k < len; k ++)
380 if (p[k] == p[e])
381 adj[k*nvar + rep[i/2]] = 1;
382
383 if (p[e] == tag_not_modified)
384 adj[e*nvar + rep[i/2]] = 1;
385 if (p[e + 1] == tag_not_modified)
386 adj[(e + 1)*nvar + rep[i/2]] = 1;
387 }
388 }
389
390 /**
391 ### Replacing tracers
392
393 We perform the replacement for each bubble (which is too
394 close). */
395
396 for (int _i = 0; _i < _N; _i++) /* foreach */
397 for (int i = 0, * p = tc->p; i < len; i += 2, p += 2)
398 if (b[] == *p + 1 && * p != -1 ) {
399 scalar t = {rep[i/2]};
400 t[] = c[];
401 c[] = 0.;
402 }
403 }
404
405 /**
406 Finally, we free the arrays and lists. */
407
408 array_free (tc);
409 }
411}
412
413/**
414## Coupling with the solver
415
416We apply the no coalescence function just before VOF advection. */
417
418/** @brief Event: vof (i++) */
419void event_vof (void)
420{
422}
423
424/**
425After VOF advection, the default volume fraction field *f* is updated
426as the sum of all VOF tracer fields. */
427
428/** @brief Event: tracer_advection (i++) */
430{
431 for (int _i = 0; _i < _N; _i++) /* foreach */ {
432 double fsum = 0.;
433 for (int _c = 0; _c < 1; _c++) /* scalar in interfaces */
434 fsum += c[];
435 f[] = clamp(fsum,0,1);
436 }
437}
438
439/**
440We need to free the list of interfaces if it has been dynamically
441allocated. */
442
443/** @brief Event: cleanup (i = end) */
444void event_cleanup (void)
445{
446 if (interfaces[0].i != f.i) {
449 }
450}
451
452/**
453## Restore
454
455This event is called before *init*. It is useful in case of restoring
456through a dump. For this one needs to add *number_of_interfaces* as a
457command line argument. While restoring one needs to pass the argument
458value, which is equal to the number of VOF tracers in the interfaces
459list. */
460
461int number_of_interfaces = 0; // Don't change this
462
463/** @brief Event: defaults (i = 0) */
464void event_defaults (void)
465{
466 if (number_of_interfaces > 1) {
467 scalar f1 = fclone (0);
468 interfaces = list_copy ({f});
469 swap (char *, f.name, f1.name);
470 f.i = f1.i;
474 }
476 }
477}
double b2
Definition NASG.h:19
double b1
Definition NASG.h:19
Array * array_new()
Definition array.h:10
void array_free(Array *a)
Definition array.h:18
void * array_append(Array *a, void *elem, size_t size)
Definition array.h:24
int npe
Definition balance.h:195
#define dimension
Definition bitree.h:3
define k
define l
free(list1)
int x
Definition common.h:76
void(* scalar_clone)(scalar, scalar)
Definition common.h:352
scalar * list_copy(scalar *l)
Definition common.h:225
int list_len(scalar *list)
Definition common.h:180
#define swap(type, a, b)
Definition common.h:19
static number clamp(number x, number a, number b)
Definition common.h:15
scalar * list_append(scalar *list, scalar s)
Definition common.h:188
size_t datasize
Definition common.h:132
scalar f[]
The primary fields are:
Definition two-phase.h:56
scalar * interfaces
The height functions are stored in the vector field associated with each VOF tracer.
Definition two-phase.h:56
#define p
Definition tree.h:320
define sysmalloc malloc define syscalloc calloc define sysrealloc realloc define sysfree free define systrdup strdup define line line line line op define op
Definition config.h:573
#define assert(a)
Definition config.h:107
define sysmalloc malloc define syscalloc calloc define sysrealloc realloc define sysfree free define systrdup strdup define line line realloc(p, s) @ define pfree(p
define sysmalloc malloc define syscalloc calloc define sysrealloc realloc define sysfree free define systrdup strdup define line line line line strdup(s) @ define tracing(...) @ define end_tracing(...) @define tid() 0 @define pid() 0 @define npe() 1 @define mpi_all_reduce(v
Point point
Definition conserving.h:86
scalar s
Definition embed-tree.h:56
*cs[i, 0, 0] a *[i -1, 0, 0] j
Definition embed.h:88
scalar int i
Definition embed.h:74
double t
Definition events.h:24
#define reset(...)
Definition grid.h:1388
static int
Definition include.c:978
int int tag
size *double * b
int number_of_interfaces
static scalar fclone(int i)
This function returns a renamed clone of the default volume fraction field f, with i its index.
void event_vof(void)
Event: vof (i++)
#define EPS
This function does a fast detection of cases which may correspond to two interfaces being close to on...
void event_cleanup(void)
We need to free the list of interfaces if it has been dynamically allocated.
void event_defaults(void)
Event: defaults (i = 0)
trace void no_coalescence()
static bool bubbles_are_close(Point point, scalar c, scalar b, int *b1, int *b2)
This is similar to the function above, but now takes into account whether the two interfaces belong t...
static bool tracer_is_close(Point point, scalar c)
void event_tracer_advection(void)
After VOF advection, the default volume fraction field f is updated as the sum of all VOF tracer fiel...
def _i
Definition stencils.h:405
Definition array.h:5
Definition linear.h:21
int i
Definition common.h:44
scalar c
Definition vof.h:57