Basilisk CFD
Adaptive Cartesian mesh PDE framework
Loading...
Searching...
No Matches
two-phase.h
Go to the documentation of this file.
1/** @file two-phase.h
2 */
3/**
4# A compressible two-phase flow solver
5
6This solves the two-phase compressible Navier-Stokes equations
7including the total energy equation.
8\f[
9\frac{\partial (f \rho_i)}{\partial t } +
10\nabla \cdot (f \rho_i \mathbf{u}) = 0
11\f]
12\f[
13\frac{\partial (\rho_i \mathbf{u})}{\partial t } +
14\nabla \cdot ( \rho_i \mathbf{u} \mathbf{u}) =
15-\nabla p + \nabla \cdot \tau_i'
16\f]
17\f[
18\frac{\partial [\rho_i (e_i + \mathbf{u}^2/2)]}{\partial t }
19+ \nabla \cdot [ \rho_i \mathbf{u} (e_i + \mathbf{u}^2/2)] =
20-\nabla \cdot (\mathbf{u} p_i) +
21\nabla \cdot \left( \tau'_i \mathbf{u} \right)
22\f]
23an advection equation for the volume fraction \f$f\f$
24\f[
25\frac{\partial f}{\partial t} + \mathbf{u} \cdot \nabla f = 0
26\f]
27and an Equation Of State (EOS) that needs to be defined.
28
29The method is described in detail in [Fuster & Popinet,
302018](#fuster2018) and relies on a time splitting technique were we
31first solve the advection step using the VOF method for \f$f\f$, \f$f_i
32\rho_i\f$, \f$f_i \rho_i \mathbf{u}\f$ \f$f_i \rho_i e_{tot,i}\f$ and then
33perform the projection step using the all-Mach solver. */
34
35#include "all-mach.h"
36
37/**
38We transport VOF tracers without the one-dimensional compressive term. */
39
40#define NO_1D_COMPRESSION 1
41#include "vof.h"
42
43/**
44The primary fields are:
45\f[
46\begin{aligned}
47\text{frho1} & = f \rho_1, \\
48\text{frho2} & = (1-f) \rho_2, \\
49\text{q} & = f \rho_1 \mathbf{u} + (1-f) \rho_2 \mathbf{u}, \\
50\text{fE1} & = f \rho_1 (e_1 + \mathbf{u}^2/2), \\
51\text{fE2} & = (1-f) \rho_2 (e_2 + \mathbf{u}^2/2)
52\end{aligned}
53\f]
54*/
55
58
59/**
60The timestep can be limited by a CFL based on the speed of sound. */
61
62double CFLac = HUGE;
63
64/**
65The dynamic viscosities for each phase, as well as the volumetric
66viscosity coefficients. */
67
68double mu1 = 0., mu2 = 0.;
69double lambdav1 = 0., lambdav2 = 0. ;
70
71/**
72These functions are provided by the Equation Of State. See for example
73[the Mie--Gruneisen Equation of State](Mie-Gruneisen.h). */
74
75extern double sound_speed (Point point);
76extern double average_pressure (Point point);
77extern double bulk_compressibility (Point point);
78extern double internal_energy (Point point, double fc);
79
80/**
81By default the Harmonic mean is used to compute the phase-averaged
82dynamic viscosity. */
83
84#ifndef mu
85# define mu(f) (mu1*mu2/(clamp(f,0,1)*(mu2 - mu1) + mu1))
86#endif
87
88/**
89The volumetric viscosity uses arithmetic average by default. */
90
91// fixme: Include the term depending on \f$\nabla \cdot u\f$. It is tricky because this
92// quantity can be very different upon the phase
93#ifndef lambdav
94# define lambdav(f) (clamp(f,0,1)*(lambdav1 - lambdav2) + lambdav2)
95// # define lambdav(f) (clamp(f,0.,1.)*(lambdav1 - lambdav2 - 2./3*(mu1 - mu2)) + lambdav2 - 2./3*mu2)
96#endif
97
98/**
99## Auxilliary fields
100
101Auxilliary fields need to be allocated. The quantity \f$\rho c^2\f$, the
102average density `rhov` and its inverse `alphav`. */
103
107const vector lambdav0[] = {0,0,0};
109
110/**
111## Time step restriction based on the speed of sound */
112
113/** @brief Event: stability (i++) */
114void event_stability (void)
115{
116 if (CFLac < HUGE)
117 foreach (reduction (min:dtmax)) {
118 double c = sound_speed (point);
119 if (CFLac*Delta < c*dtmax)
120 dtmax = CFLac*Delta/c;
121 }
122}
123
124#if TREE
125/**
126## Energy refinement function
127
128The energy is refined from the refined pressures, momentum and
129densities, using the equation of state. */
131{
132 for (int _c = 0; _c < 4; _c++) /* foreach_child */ {
133 double Ek = 0.;
134 for (int _d = 0; _d < dimension; _d++)
135 Ek += sq(q.x[]);
136 Ek /= 2.*(frho1[] + frho2[]);
137 fE[] = fE.inverse ?
138 (internal_energy (point, 0.) + Ek)*(1. - f[]) :
139 (internal_energy (point, 1.) + Ek)*f[];
140 }
141}
142#endif // TREE
143
144/**
145## Initialisation
146
147We set the default values. */
148
149/** @brief Event: defaults (i = 0) */
150void event_defaults (void)
151{
152 alpha = alphav;
153 rho = rhov;
154
155 /**
156 If the viscosity is non-zero, we need to allocate the face-centered
157 viscosity field. */
158
159 if (mu1 || mu2) {
160 mu = {0} /* new vector */;
161 lambdav = {0} /* new vector */;
162 }
163
164 rhoc2 = rhoc2v;
165 for (int _i = 0; _i < _N; _i++) /* foreach */ {
166 frho1[] = 1., fE1[] = 1.;
167 frho2[] = 0., fE2[] = 0.;
168 p[] = 1.;
169 f[] = 1.;
170 }
171
172 /**
173 We also initialize the list of tracers to be advected with the VOF
174 function \f$f\f$ (or its complementary function). */
175
176 f.tracers = list_copy ({frho1, frho2, fE1, fE2});
177 for (int _s = 0; _s < 1; _s++) /* scalar in {frho2, fE2} */
178 s.inverse = true;
179
180 /**
181 We set limiting. */
182
183 for (int _s = 0; _s < 1; _s++) /* scalar in {frho1, frho2, fE1, fE2, q} */ {
184 s.gradient = minmod2;
185#if TREE
186 /**
187 On trees, we ensure that limiting is also applied to prolongation
188 and refinement. */
189
190 s.prolongation = s.refine = refine_linear;
191#endif
192 }
193
194 /**
195 We add the interface and the density to the default display. */
196
197 display ("draw_vof (c = 'f');");
198 display ("squares (color = 'rhov', spread = -1);");
199}
200
201/** @brief Event: init (i = 0) */
202void event_init (void)
203{
204 /* trash: uf */;
205 for (int _i = 0; _i < _N; _i++) /* foreach_face */
206 uf.x[] = fm.x[]*(q.x[]/(frho1[] + frho2[]) + q.x[-1]/(frho1[-1] + frho2[-1]))/2.;
207
208 /**
209 We update the fluid properties. */
210
211 event ("properties");
212
213 /**
214 We set the initial timestep (this is useful only when restoring from
215 a previous run). */
216
217 dtmax = DT;
218 event ("stability");
219
220 /**
221 For the associated tracers we use the gradient defined by
222 f.gradient. */
223
224 if (f.gradient)
225 for (int _s = 0; _s < 1; _s++) /* scalar in {frho1, frho2, fE1, fE2, q} */
226 s.gradient = f.gradient;
227}
228
229/**
230## VOF advection of momentum
231
232We overload the *vof()* event to transport consistently the volume
233fraction and the momentum of each phase. */
234
236
237/** @brief Event: vof (i++) */
238void event_vof (void)
239{
240
241 /**
242 We split the total momentum \f$q\f$ into its two components \f$q1\f$ and
243 \f$q2\f$ associated with \f$f\f$ and \f$1 - f\f$ respectively. */
244
245 vector q1 = q, q2[];
246 for (int _i = 0; _i < _N; _i++) /* foreach */
247 for (int _d = 0; _d < dimension; _d++) {
248 double u = q.x[]/(frho1[] + frho2[]);
249 q1.x[] = frho1[]*u;
250 q2.x[] = frho2[]*u;
251 }
252
253 /**
254 Momentum \f$q2\f$ is associated with \f$1 - f\f$, so we set the *inverse*
255 attribute to *true*. We use the same limiting for q1 and q2. */
256
257 for (int _d = 0; _d < dimension; _d++) {
258 q2.x.inverse = true;
259 q2.x.gradient = q1.x.gradient;
260 }
261
262#if TREE
263 /**
264 The refinement function is modified by *vof_advection()*. To be able
265 to restore it, we store its value. */
266
267 void (* refine) (Point, scalar) = q1.x.refine;
268#endif
269
270 /**
271 We associate the transport of \f$q1\f$ and \f$q2\f$ with \f$f\f$ and transport
272 all fields consistently using the VOF scheme. */
273
274 scalar * tracers = f.tracers;
275 f.tracers = list_concat (tracers, (scalar *){q1, q2});
276 vof_advection ({f}, i);
277 free (f.tracers);
278 f.tracers = tracers;
279
280 /**
281 We recover the total momentum. */
282
283 for (int _i = 0; _i < _N; _i++) /* foreach */ {
284 for (int _d = 0; _d < dimension; _d++)
285 q.x[] = q1.x[] + q2.x[];
286
287 /**
288 We avoid negative densities and energies which may have been
289 caused by round-off during VOF advection. */
290
291 if (f[] <= 0.){
292 frho1[] = 0.;
293 fE1[] = 0.;
294 }
295 if (f[] >= 1.){
296 frho2[] = 0.;
297 fE2[] = 0.;
298 }
299 }
300
301#if TREE
302 /**
303 We restore the refinement function for the total momentum. */
304
305 for (int _s = 0; _s < 1; _s++) /* scalar in {q} */ {
306 s.refine = refine;
308 }
309
310#if 0
311 /**
312 This is switched off by default for now as the standard refinement
313 in [/src/vof.h#vof_concentration_refine]() seems to work fine. */
314
315 for (int _s = 0; _s < 1; _s++) /* scalar in {fE1,fE2} */ {
316 s.refine = fE_refine;
318 }
319#endif
320#endif // TREE
321
322 /**
323 We set the list of interfaces to NULL so that the default *vof()*
324 event does nothing (otherwise we would transport \f$f\f$ twice). */
325
327}
328
329/**
330We set the list of interfaces back to its default value. */
331
332/** @brief Event: tracer_advection (i++) */
336
337/**
338## Pressure and density
339
340During the projection step we compute the provisional pressure from
341the EOS using the values after advection. */
342
343/** @brief Event: properties (i++) */
345{
346 for (int _i = 0; _i < _N; _i++) /* foreach */ {
347 rhov[] = frho1[] + frho2[];
349
350 /**
351 We also compute \f$\rho c^2\f$. */
352
354 }
355
356 for (int _i = 0; _i < _N; _i++) /* foreach_face */ {
357
358 /**
359 If viscosity is present we obtain the averaged viscosity at the cell faces. */
360
361 if (mu1 || mu2) {
363 double ff = (f[] + f[-1])/2.;
364 muv.x[] = fm.x[]*mu(ff);
365 lambdavv.x[] = lambdav(ff);
366 }
367
368 /**
369 We also compute the averaged density at the cell faces. */
370
371 alphav.x[] = 2.*fm.x[]/(rhov[] + rhov[-1]);
372
373 }
374
375 /**
376 The all-Mach solver needs rho*cm. */
377
378 for (int _i = 0; _i < _N; _i++) /* foreach */
379 rhov[] *= cm[];
380
381 /* I still miss a source term in the divergence related to viscous
382 * dissipation. Usually it is small and a little bit complicated to
383 * calculate. In the current form of allmach it cannot be added
384 * because it does not allow user-defined divergence sources. */
385}
386
387/**
388## Update of the total energy
389
390After projection we update the values of the total energy adding the
391term missing from the projection step.
392
393For a Newtonian fluid, the stress tensor comprises the pressure term,
394the viscous uncompressible term and the viscous compressible term,
395
396\f[ \tau = -p \mathbf{I} + \mu (\nabla \mathbf{u} + \nabla \mathbf{u}^T)
397+ \lambda_v (\nabla \cdot \mathbf{u}) \mathbf{I}
398\f]
399
400where \f$\mathbf{I}\f$ is the unity tensor and \f$\lambda_v = \mu_v - 2/3
401\mu\f$. The volumetric viscosity \f$\mu_v\f$ is negligible in most cases. */
402
403/** @brief Event: end_timestep (i++) */
405{
406#if 0
407 /**
408 We first compute the divergence of the velocity at cell centers
409 using the cell face velocity. Note that the vector *uf*
410 incorporates the metric factor. */
411
412 scalar divU[];
413 for (int _i = 0; _i < _N; _i++) /* foreach */ {
414 divU[] = 0.;
415 for (int _d = 0; _d < dimension; _d++)
416 divU[] += (uf.x[1] - uf.x[])/(Delta*cm[]);
417 }
418
419 /**
420 We compute explicitly the contribution of the compressible viscous
421 term to the momentum equation and the total energy equation,
422 \f[
423 \partial_t \mathbf{q} = \nabla \cdot [\lambda_v(\nabla \cdot \mathbf{u}) \mathbf{I}]
424 \f]
425 \f[
426 \partial_t [\rho (e + \mathbf{u}^2/2)] =
427 \nabla \cdot [\lambda_v (\nabla \cdot \mathbf{u}) \mathbf{u}]
428 \f]
429
430 The axysimmetric case allows some simplifications. Since
431 \f[
432 \mathbf{S} = \lambda_v(\nabla \cdot \mathbf{u}) \mathbf{I} =
433 \left(
434 \begin{array}{ccc}
435 S_{xx} & 0 & 0 \ \
436 0 & S_{yy} & 0 \ \
437 0 & 0 & S_{\theta \theta}
438 \end{array}
439 \right)
440 \f]
441 with \f$S = S_{xx} = S_{yy} = S_{\theta \theta}= \lambda_v \nabla
442 \cdot \mathbf{u}\f$. Hence, the radial component of \f$\nabla \cdot
443 \mathbf{S}\f$ is simply
444 \f[
445 (\nabla \cdot \mathbf{S}) \cdot \mathbf{e}_y =
446 \frac{1}{y} \frac{\partial(y S_{yy}) }{\partial y}
447 - \frac{S_{\theta \theta}}{y}
448 = \frac{\partial S }{\partial y}
449 \f]
450 Also \f$u_\theta = 0\f$. */
451
452 for (int _i = 0; _i < _N; _i++) /* foreach */ {
453 double momentum = 0., energy = 0.;
454 for (int _d = 0; _d < dimension; _d++) {
455 double right = lambdav.x[1]*(divU[1] + divU[])/2.;
456 double left = lambdav.x[]*(divU[-1] + divU[])/2.;
457 momentum += right - left;
458 energy += uf.x[1]*right - uf.x[]*left;
459 }
460 for (int _d = 0; _d < dimension; _d++)
461 q.x[] += dt*momentum/Delta;
462 energy *= dt/(Delta*cm[]);
463 double fc = clamp(f[],0,1);
464 fE1[] += energy*fc;
465 fE2[] += energy*(1. - fc);
466 }
467#endif
468
469 /**
470 The contribution of the pressure to the energy of each phase is
471 lacking. */
472
473 {
474 vector upf[];
475 for (int _i = 0; _i < _N; _i++) /* foreach_face */
476 upf.x[] = - uf.x[]*(p[] + p[-1])/2.;
477
478 for (int _i = 0; _i < _N; _i++) /* foreach */ {
479 double energy = 0.;
480 for (int _d = 0; _d < dimension; _d++)
481 energy += upf.x[1] - upf.x[];
482 energy *= dt/(Delta*cm[]);
483 double fc = clamp(f[],0,1);
484 fE1[] += energy*fc;
485 fE2[] += energy*(1. - fc);
486 }
487 }
488
489 /**
490 This is the contribution of the incompressible viscous term. */
491
492 if (mu1 || mu2) {
493
494 /**
495 The velocity \f$\mathbf{u}\f$ is first obtained from the updated
496 momentum \f$\mathbf{q}\f$. */
497
498 vector u = q;
499 for (int _i = 0; _i < _N; _i++) /* foreach */
500 for (int _d = 0; _d < dimension; _d++)
501 u.x[] = q.x[]/(frho1[] + frho2[]);
502
503 /**
504 We add the incompressible contribution of the \f$\nabla \cdot (u_i
505 \tau'_i)\f$ term. Note that the compressible contribution, which is
506 typically small, is neglected. */
507
508 // fixme: add the compressible contribution (careful, \f$\nabla \cdot
509 // u\f$ can be very different upon the phase and also very different
510 // from the value defined for the mixture with an averaged velocity
511 vector ueijk[];
512 for (int _d = 0; _d < dimension; _d++) {
513 for (int _i = 0; _i < _N; _i++) /* foreach_face */
514 ueijk.x[] = 2.*uf.x[]*(u.x[] - u.x[-1])/Delta;
515#if dimension > 1
516 for (int _i = 0; _i < _N; _i++) /* foreach_face */
517 ueijk.y[] = uf.y[]*(u.x[] - u.x[0,-1] +
518 (u.y[1,-1] + u.y[1,0])/4. -
519 (u.y[-1,-1] + u.y[-1,0])/4.)/Delta;
520#endif
521#if dimension > 2
522 for (int _i = 0; _i < _N; _i++) /* foreach_face */
523 ueijk.z[] = uf.z[]*(u.x[] - u.x[0,0,-1] +
524 (u.z[1,0,-1] + u.z[1,0,0])/4. -
525 (u.z[-1,0,-1] + u.z[-1,0,0])/4.)/Delta;
526#endif
527 for (int _i = 0; _i < _N; _i++) /* foreach */ {
528 double energy = 0.;
529 for (int _d = 0; _d < dimension; _d++)
530 energy += ueijk.x[1] - ueijk.x[];
531 energy *= dt/(Delta*cm[]);
532 double fc = clamp(f[],0,1);
533 fE1[] += mu1*energy*fc;
534 fE2[] += mu2*energy*(1. - fc);
535 }
536
537 // fixme: Formally, we need to include a correction term due to
538 // the normal viscous stress jump (to be done)
539 }
540
541 /**
542 Finally we recover momentum.*/
543
544 for (int _i = 0; _i < _N; _i++) /* foreach */
545 for (int _d = 0; _d < dimension; _d++)
546 q.x[] *= frho1[] + frho2[];
547 }
548}
549
550/**
551At the end of the simulation we clean the tracer list. */
552
553/** @brief Event: cleanup (i = end) */
554void event_cleanup (void) {
555 free (f.tracers);
556 f.tracers = NULL;
557}
558
559/**
560## References
561
562~~~bib
563@hal{fuster2018, hal-01845218}
564~~~
565
566## See also
567
568* [The Mie--Gruneisen Equation of State](Mie-Gruneisen.h)
569*/
double q2
Definition NASG.h:20
double q1
Definition NASG.h:20
#define u
Definition advection.h:30
vector uf[]
We allocate the (face) velocity field.
Definition advection.h:29
scalar * tracers
Here we set the gradient functions for each tracer (as defined in the user-provided tracers list).
Definition hydro.h:60
const scalar rhoc2
Definition all-mach.h:58
double dtmax
The timestep is computed using the CFL condition on the face velocity field.
Definition all-mach.h:105
vector q[]
The primitive variables are the momentum , pressure , density , (face) specific volume ,...
Definition all-mach.h:44
scalar ps[]
The equation of state is defined by the pressure field ps and .
Definition all-mach.h:57
const vector alpha
Definition all-mach.h:47
trace void momentum(vector u, scalar h, vector du)
Definition atmosphere.h:57
int min
Definition balance.h:192
#define dimension
Definition bitree.h:3
free(list1)
int x
Definition common.h:76
const scalar cm[]
Definition common.h:397
void display(const char *commands, bool overwrite=false)
Definition common.h:527
scalar * list_concat(scalar *l1, scalar *l2)
Definition common.h:234
#define HUGE
Definition common.h:6
static number sq(number x)
Definition common.h:11
scalar * list_copy(scalar *l)
Definition common.h:225
static number clamp(number x, number a, number b)
Definition common.h:15
@ left
Definition common.h:123
@ right
Definition common.h:123
const vector fm[]
Definition common.h:396
scalar frho2[]
Definition two-phase.h:57
const vector lambdav0[]
Definition two-phase.h:107
static scalar * interfaces1
Definition two-phase.h:235
void event_stability(void)
Event: stability (i++)
Definition two-phase.h:114
#define lambdav(f)
The volumetric viscosity uses arithmetic average by default.
Definition two-phase.h:94
void event_end_timestep(void)
Event: end_timestep (i++)
Definition two-phase.h:404
double sound_speed(Point point)
These functions are provided by the Equation Of State.
void event_vof(void)
Event: vof (i++)
Definition two-phase.h:238
#define mu(f)
By default the Harmonic mean is used to compute the phase-averaged dynamic viscosity.
Definition two-phase.h:85
scalar rhoc2v[]
Definition two-phase.h:104
scalar frho1[]
Definition two-phase.h:57
void event_init(void)
Event: init (i = 0)
Definition two-phase.h:202
double mu1
The dynamic viscosities for each phase, as well as the volumetric viscosity coefficients.
Definition two-phase.h:68
void fE_refine(Point point, scalar fE)
Definition two-phase.h:130
double bulk_compressibility(Point point)
void event_cleanup(void)
At the end of the simulation we clean the tracer list.
Definition two-phase.h:554
double lambdav2
Definition two-phase.h:69
void event_defaults(void)
Event: defaults (i = 0)
Definition two-phase.h:150
double mu2
Definition two-phase.h:68
void event_properties(void)
Event: properties (i++)
Definition two-phase.h:344
double lambdav1
Definition two-phase.h:69
double average_pressure(Point point)
double CFLac
The timestep can be limited by a CFL based on the speed of sound.
Definition two-phase.h:62
void event_tracer_advection(void)
We set the list of interfaces back to its default value.
Definition two-phase.h:333
scalar rhov[]
Definition two-phase.h:106
scalar fE2[]
Definition two-phase.h:57
vector alphav[]
Definition two-phase.h:105
scalar fE1[]
Definition two-phase.h:57
double internal_energy(Point point, double fc)
#define p
Definition tree.h:320
Point point
Definition conserving.h:86
double dt
scalar s
Definition embed-tree.h:56
scalar int i
Definition embed.h:74
void event(const char *name)
Definition events.h:264
#define rho(f)
The density and viscosity are defined using arithmetic averages by default.
Definition momentum.h:45
vector muv[]
Definition momentum.h:22
static void refine_linear(Point point, scalar s)
void set_prolongation(scalar s, void(*prolongation)(Point, scalar))
double energy(double *PE=NULL, double *KE=NULL)
Definition rpe.h:234
def _i
Definition stencils.h:405
Definition linear.h:21
scalar x
Definition common.h:47
scalar y
Definition common.h:49
macro refine(bool cond)
scalar f[]
Definition two-phase.h:17
scalar * interfaces
The height functions are stored in the vector field associated with each VOF tracer.
Definition two-phase.h:17
double DT
Definition utils.h:10
double minmod2(double s0, double s1, double s2)
Definition utils.h:225
void vof_advection(scalar *interfaces, int i)
Definition vof.h:330
scalar c
Definition vof.h:57