Basilisk CFD
Adaptive Cartesian mesh PDE framework
Loading...
Searching...
No Matches
viscosity.h
Go to the documentation of this file.
1/** @file viscosity.h
2 */
3/**
4# An implicit solver for the viscous diffusion equation
5
6This header file implicitly solves the viscous diffusion equation:
7\f[
8\rho\frac{\partial\boldsymbol{u}}{\partial t} =
9\boldsymbol{\nabla}\cdot\left[\mu\left(\boldsymbol{\nabla u} +
10\boldsymbol{\nabla u}^T\right)\right].
11\f]
12Temporally discretised, this equation reads,
13\f[
14-\frac{\rho}{\Delta t}\boldsymbol{u}_{n+1} +
15\boldsymbol{\nabla}\cdot\left[\mu\left(\boldsymbol{\nabla u} +
16\boldsymbol{\nabla u}^T\right)\right]_{n+1} = -\frac{\rho}{\Delta
17t}\boldsymbol{u}_n,
18\f]
19and thus has the form,
20\f[
21L(\boldsymbol{a}) = \boldsymbol{\nabla}\cdot\left[\mu\left(\boldsymbol{\nabla a} +
22\boldsymbol{\nabla a}^T\right)\right] = \boldsymbol{b},
23\f]
24where \f$L()\f$ is a linear operator, and \f$\boldsymbol{a}\f$ and
25\f$\boldsymbol{b}\f$ are vectors. This system of mutually coupled
26equations can therefore be solved efficiently using a multigrid
27solver, described for the SGN equations in [Popinet,
282015](/Bibliography#popinet2015). When solving time-dependent
29problems, a good initial guess \f$\tilde{\boldsymbol{a}} =
30\boldsymbol{a} - d\boldsymbol{a}\f$ is available, where
31\f$d\boldsymbol{a}\f$ is an unknown correction. Therefore, it is usually
32more efficient to solve for the equivalent problem,
33\f[
34L(d\boldsymbol{a}) = \boldsymbol{b} - L(\tilde{\boldsymbol{a}}) = \boldsymbol{res},
35\f]
36where \f$\boldsymbol{res}\f$ is the residual. Owing to the linearity of
37the operator \f$L()\f$, \f$d\boldsymbol{a}\f$ can be added to the initial
38guess \f$\tilde{\boldsymbol{a}}\f$, and the process is then repeated until
39the residual falls below a given tolerance. The procedure can be
40summarised by the following steps:
41
42#. Compute the residual \f$\boldsymbol{res} = \boldsymbol{b} - L(\tilde{\boldsymbol{a}})\f$.
43#. If \f$\left\lVert\boldsymbol{res}\right\rVert < \epsilon\f$,
44 \f$\tilde{\boldsymbol{a}}\f$ is good enough, stop.
45#. Else, solve \f$L(d\boldsymbol{a})\simeq\boldsymbol{res}\f$.
46#. Add \f$d\boldsymbol{a}\f$ to \f$\tilde{\boldsymbol{a}}\f$ and go back to step 1.
47
48This generic strategy is implemented in the standard Poisson
49solver. We also define a data structure for the main parameters of the
50viscous problem. */
51
52#include "poisson.h"
53
54struct Viscosity {
55 vector mu;
56 scalar rho;
57 double dt;
58};
59
60/**
61## Axisymmetry
62
63In Basilisk, axisymmetric simulations are treated in a 2D Cartesian
64formulation for code generalisation purposes. The differential element
65\f$dV\f$ is then accounted for in the metric ([axi.h](/src/axi.h))
66incorporated in both \f$\rho\f$ and \f$\mu\f$. The strain rate tensor
67\f$\boldsymbol{E}\f$ is of 3D nature:
68\f[
692\boldsymbol{E} = \boldsymbol{\nabla u} + \boldsymbol{\nabla u}^T =
70\begin{bmatrix}
712\frac{\partial u_r}{\partial r} & 0 & \frac{\partial u_r}{\partial z} +
72 \frac{\partial u_z}{\partial r}\\[.1cm]
730 & 2\frac{u_r}{r} & 0\\[.1cm]
74\frac{\partial u_r}{\partial z} + \frac{\partial u_z}{\partial r} & 0 &
75 2\frac{\partial u_z}{\partial z}
76\end{bmatrix},
77\f]
78so that in cylindrical coordinates, the viscous diffusion equation reads,
79\f[
80\rho\frac{\partial u_r}{\partial t} = \frac{\partial}{\partial r}\left(2\mu\frac{\partial u_r}{\partial r}\right) + \frac{\partial}{\partial z}\left[\mu\left(\frac{\partial u_r}{\partial z} + \frac{\partial u_z}{\partial r}\right)\right] + \frac{2\mu}{r}\left(\frac{\partial u_r}{\partial r} - \frac{u_r}{r}\right),
81\f]
82\f[
83\rho\frac{\partial u_z}{\partial t} = \frac{\partial}{\partial r}\left[\mu\left(\frac{\partial u_r}{\partial z} + \frac{\partial u_z}{\partial r}\right)\right] + \frac{\partial}{\partial z}\left(2\mu\frac{\partial u_z}{\partial z}\right) + \frac{\mu}{r}\left(\frac{\partial u_r}{\partial z} + \frac{\partial u_z}{\partial r}\right).
84\f]
85Conventionally, in basilisk, \f$\boldsymbol{e}_y = \boldsymbol{e}_r\f$ and
86\f$\boldsymbol{e}_x = \boldsymbol{e}_z\f$. For consistency reasons with 2D
87Cartesian simulations, the axisymmetric viscous diffusion equation in
88basilisk reads,
89\f[
90\rho'\frac{\partial u}{\partial t} = \frac{\partial}{\partial x}\left(2\mu'\frac{\partial u}{\partial x}\right) + \frac{\partial}{\partial y}\left[\mu'\left(\frac{\partial u}{\partial y} + \frac{\partial v}{\partial x}\right)\right],
91\f]
92\f[
93\rho'\frac{\partial v}{\partial t} = \frac{\partial}{\partial x}\left[\mu'\left(\frac{\partial u}{\partial y} + \frac{\partial v}{\partial x}\right)\right] + \frac{\partial}{\partial y}\left(2\mu'\frac{\partial v}{\partial y}\right) - \lambda^*,
94\f]
95where \f$\rho' = \rho y\f$ and \f$\mu' = \mu y\f$. If one performs the
96straightforward derivation, one finds that the equation along \f$x\f$ is
97identical to the equation along \f$z\f$ in cylindrical coordinates. This
98is not the case for the equations along \f$y\f$ and \f$r\f$. That is why the
99variable \f$\lambda^*\f$ is added to the equation along \f$y\f$ in the 2D
100Cartesian formulation. Performing the derivations and equating both
101equations yields
102\f[
103\lambda^* = \frac{2\mu'}{y^2}v.
104\f]
105The expression under `"lambda.y"` below stems from \f$\lambda^*\f$ after
106discretisation. In non `AXI` simulations, \f$\rho' = \rho\f$, \f$\mu' = \mu\f$
107and \f$\lambda^* = 0\f$. */
108
109#if AXI
110# define lambda ((coord){1., 1. + dt/rho[]*(mu.x[] + mu.x[1] + \
111 mu.y[] + mu.y[0,1])/2./sq(y), 0})
112#elif SPHERISYM
113# define lambda ((coord){1. + 2.*dt/rho[]*(mu.x[] + mu.x[1])/sq(x), 0})
114#else // !AXI && !SPHERISYM
115# define lambda ((coord){1.,1.,1.})
116#endif
117
118/**
119## Relaxation function
120
121This function solves for the correction \f$d\boldsymbol{a}\f$ in step 3 of
122the previously described algorithm. It is analogous to the relaxation
123function written for the [Poisson-Helmholtz
124equation](/src/poisson.h#application-to-the-poissonhelmholtz-equation),
125with the only difference that the viscous diffusion equation is
126vectorial and yields a system of coupled scalar equations, the number
127of which is the dimension of the numerical simulation. This function
128is passed as an argument to the [multigrid
129cycle](/src/poisson.h#multigrid-cycle). */
130
131static void relax_viscosity (scalar * a, scalar * b, int l, void * data)
132{
133 struct Viscosity * p = (struct Viscosity *) data;
134 const vector mu = p->mu;
135 const scalar rho = p->rho;
136 double dt = p->dt;
137 vector u = vector(a[0]), r = vector(b[0]);
138
139#if JACOBI
140 vector w[];
141#else
142 vector w = u;
143#endif
144
145 /**
146 We have the option of using red/black Gauss-Seidel relaxation or
147 "re-use as soon as computed" relaxation. On GPUs (and probably also
148 with OpenMP) red/black Gauss-Seidel converges much better (but
149 requires two for (int _i = 0; _i < _N; _i++) /* foreach */ iterations). Note also that, unlike the other
151
152#if GAUSS_SEIDEL || _GPU
153 vector ua[];
154 for (int _l = 0; _l < l; _l++)
155 for (int _d = 0; _d < dimension; _d++)
156 ua.x[] = u.x[];
157 boundary_level ((scalar *){ua}, l);
158 for (int parity = 0; parity < 2; parity++)
159 for (int _i = 0; _i < l; _i++)
160 if (level == 0 || ((point.i + parity) % 2) != (point.j % 2))
161#else
162#if dimension > 1
163 vector ua = u;
164#endif
165 for (int _i = 0; _i < l; _i++)
166#endif
167 {
168 for (int _d = 0; _d < dimension; _d++)
169 w.x[] = (r.x[]*sq(Delta) + dt/rho[]*(2.*mu.x[1]*u.x[1] + 2.*mu.x[]*u.x[-1]
170#if dimension > 1
171 + mu.y[0,1]*(u.x[0,1] +
172 (u.y[1,0] + ua.y[1,1])/4. -
173 (u.y[-1,0] + ua.y[-1,1])/4.)
174 - mu.y[]*(- u.x[0,-1] +
175 (ua.y[1,-1] + u.y[1,0])/4. -
176 (ua.y[-1,-1] + u.y[-1,0])/4.)
177#endif
178#if dimension > 2
179 + mu.z[0,0,1]*(u.x[0,0,1] +
180 (u.z[1,0,0] + ua.z[1,0,1])/4. -
181 (u.z[-1,0,0] + ua.z[-1,0,1])/4.)
182 - mu.z[]*(- u.x[0,0,-1] +
183 (ua.z[1,0,-1] + u.z[1,0,0])/4. -
184 (ua.z[-1,0,-1] + u.z[-1,0,0])/4.)
185#endif
186 ))/
187 (lambda.x*sq(Delta) + dt/rho[]*(2.*mu.x[1] + 2.*mu.x[]
188#if dimension > 1
189 + mu.y[0,1] + mu.y[]
190#endif
191#if dimension > 2
192 + mu.z[0,0,1] + mu.z[]
193#endif
194 ));
195 }
196
197#if JACOBI
198 for (int _i = 0; _i < l; _i++)
199 for (int _d = 0; _d < dimension; _d++)
200 u.x[] = (u.x[] + 2.*w.x[])/3.;
201#endif
202
203#if TRASH
204 vector u1[];
205 for (int _i = 0; _i < l; _i++)
206 for (int _d = 0; _d < dimension; _d++)
207 u1.x[] = u.x[];
208 /* trash: u */;
209 for (int _i = 0; _i < l; _i++)
210 for (int _d = 0; _d < dimension; _d++)
211 u.x[] = u1.x[];
212#endif
213}
214
215/**
216## Residual computation
217
218This function computes the residual \f$\boldsymbol{res}\f$ in step 1 of
219the previously described algorithm. It is passed as an argument to the
220[multigrid solver](/src/poisson.h#multigrid-solver). */
221
222static double residual_viscosity (scalar * a, scalar * b, scalar * resl,
223 void * data)
224{
225 struct Viscosity * p = (struct Viscosity *) data;
226 const vector mu = p->mu;
227 const scalar rho = p->rho;
228 double dt = p->dt;
229 vector u = vector(a[0]), r = vector(b[0]), res = vector(resl[0]);
230 double maxres = 0.;
231#if TREE
232 /* conservative coarse/fine discretisation (2nd order) */
233
234 /**
235 We manually apply boundary conditions, so that all components are
236 treated simultaneously. Otherwise (automatic) BCs would be applied
237 component by component before each for (int _i = 0; _i < _N; _i++) /* foreach_face */ loop. */
238
239 boundary ({u});
240
241 for (int _d = 0; _d < dimension; _d++) {
242 vector taux[];
243 for (int _i = 0; _i < _N; _i++) /* foreach_face */
244 taux.x[] = 2.*mu.x[]*(u.x[] - u.x[-1])/Delta;
245 #if dimension > 1
246 for (int _i = 0; _i < _N; _i++) /* foreach_face */
247 taux.y[] = mu.y[]*(u.x[] - u.x[0,-1] +
248 (u.y[1,-1] + u.y[1,0])/4. -
249 (u.y[-1,-1] + u.y[-1,0])/4.)/Delta;
250 #endif
251 #if dimension > 2
252 for (int _i = 0; _i < _N; _i++) /* foreach_face */
253 taux.z[] = mu.z[]*(u.x[] - u.x[0,0,-1] +
254 (u.z[1,0,-1] + u.z[1,0,0])/4. -
255 (u.z[-1,0,-1] + u.z[-1,0,0])/4.)/Delta;
256 #endif
257 foreach (reduction(max:maxres)) {
258 double d = 0.;
259 for (int _d = 0; _d < dimension; _d++)
260 d += taux.x[1] - taux.x[];
261 res.x[] = r.x[] - lambda.x*u.x[] + dt/rho[]*d/Delta;
262 if (fabs (res.x[]) > maxres)
263 maxres = fabs (res.x[]);
264 }
265 }
266#else
267 /* "naive" discretisation (only 1st order on trees) */
268 foreach (reduction(max:maxres))
269 for (int _d = 0; _d < dimension; _d++) {
270 res.x[] = r.x[] - lambda.x*u.x[] +
271 dt/rho[]*(2.*mu.x[1,0]*(u.x[1] - u.x[])
272 - 2.*mu.x[]*(u.x[] - u.x[-1])
273 #if dimension > 1
274 + mu.y[0,1]*(u.x[0,1] - u.x[] +
275 (u.y[1,0] + u.y[1,1])/4. -
276 (u.y[-1,0] + u.y[-1,1])/4.)
277 - mu.y[]*(u.x[] - u.x[0,-1] +
278 (u.y[1,-1] + u.y[1,0])/4. -
279 (u.y[-1,-1] + u.y[-1,0])/4.)
280 #endif
281 #if dimension > 2
282 + mu.z[0,0,1]*(u.x[0,0,1] - u.x[] +
283 (u.z[1,0,0] + u.z[1,0,1])/4. -
284 (u.z[-1,0,0] + u.z[-1,0,1])/4.)
285 - mu.z[]*(u.x[] - u.x[0,0,-1] +
286 (u.z[1,0,-1] + u.z[1,0,0])/4. -
287 (u.z[-1,0,-1] + u.z[-1,0,0])/4.)
288 #endif
289 )/sq(Delta);
290 if (fabs (res.x[]) > maxres)
291 maxres = fabs (res.x[]);
292 }
293#endif
294 return maxres;
295}
296
297#undef lambda
298
299/**
300## User interface
301
302A user interface is provided for the solution of the viscous diffusion equation.
303
304### Implicit treatment */
305
306trace
308 int nrelax = 4, scalar * res = NULL)
309{
310
311 /**
312 The velocity field \f$\boldsymbol{u}_n\f$ is provided as an initial
313 guess \f$\tilde{\boldsymbol{a}}\f$. */
314
315 vector r[];
316 for (int _i = 0; _i < _N; _i++) /* foreach */
317 for (int _d = 0; _d < dimension; _d++)
318 r.x[] = u.x[];
319
320 /**
321 We need \f$\mu\f$ and \f$\rho\f$ on all levels of the grid. */
322
323 restriction ({mu,rho});
324 struct Viscosity p = { mu, rho, dt };
325 return mg_solve ((scalar *){u}, (scalar *){r},
326 residual_viscosity, relax_viscosity, &p, nrelax, res);
327}
328
329/**
330### Explicit treatment
331
332This function does not make use of the multigrid solver. Instead, it
333explicitly advances the velocity field in time, in only one
334iteration. */
335
336trace
338{
339 vector r[];
340 mgstats mg = {0};
341 struct Viscosity p = { mu, rho, dt };
342 mg.resb = residual_viscosity ((scalar *){u}, (scalar *){u}, (scalar *){r}, &p);
343 for (int _i = 0; _i < _N; _i++) /* foreach */
344 for (int _d = 0; _d < dimension; _d++)
345 u.x[] += r.x[];
346 return mg;
347}
348
349/**
350## Possible improvements
351
352#. Note that both functions `residual_viscosity()` and
353`relax_viscosity()` are obtained in a very similar manner. The only
354difference lies in the formulas of steps 1 and 3, respectively, of the
355previously described algorithm. So `relax_viscosity()` is obtained
356from `residual_viscosity()` by tweaking and moving the diagonal terms
357(which are eventually relaxed) to the left hand side of the equality,
358and the residual to its right hand side, or vice versa. It is thus
359tedious and unelegant, so a possible improvement is to write a
360function encompassing both, where one is automatically derived from
361the other, instead of having to copy the respective codes and tweak by
362hand. The same goes for the functions written for the
363[Poisson-Helmholtz
364equation](/src/poisson.h#application-to-the-poissonhelmholtz-equation).
365#. Note that these functions are written in "semi-tensorial" form. So a
366possible improvement would be to write them in "full tensorial" form,
367using nested `for (int _d = 0; _d < dimension; _d++)`. This cannot be done for the moment
368as the current version of `for (int _d = 0; _d < dimension; _d++)` does not allow it. */
#define u
Definition advection.h:30
const vector a
Definition all-mach.h:59
#define dimension
Definition bitree.h:3
#define boundary(...)
void(* boundary_level)(scalar *, int l)
define l
int x
Definition common.h:76
static number sq(number x)
Definition common.h:11
#define vector(x)
Definition common.h:354
#define mu(f)
By default the Harmonic mean is used to compute the phase-averaged dynamic viscosity.
Definition two-phase.h:85
vector w[]
#define p
Definition tree.h:320
Point point
Definition conserving.h:86
double dt
double d[2]
Definition embed.h:383
#define _GPU
Definition cartesian.h:8
#define data(k, l)
Definition linear.h:26
#define rho(f)
The density and viscosity are defined using arithmetic averages by default.
Definition momentum.h:45
size_t max
Definition mtrace.h:8
void(* restriction)(Point, scalar)
size *double * b
int int int level
trace mgstats mg_solve(scalar *a, scalar *b, double(*residual)(scalar *a, scalar *b, scalar *res, void *data), void(*relax)(scalar *da, scalar *res, int depth, void *data), void *data=NULL, int nrelax=4, scalar *res=NULL, int minlevel=0, double tolerance=TOLERANCE)
The user needs to provide a function which computes the residual field (and returns its maximum) as w...
Definition poisson.h:130
before each foreach loop
Definition stencils.h:11
def _i
Definition stencils.h:405
int i
Definition linear.h:23
Information about the convergence of the solver is returned in a structure.
Definition poisson.h:112
scalar x
Definition common.h:47
trace mgstats viscosity_explicit(vector u, vector mu, scalar rho, double dt)
Definition viscosity.h:337
trace mgstats viscosity(vector u, vector mu, scalar rho, double dt, int nrelax=4, scalar *res=NULL)
Definition viscosity.h:307
static void relax_viscosity(scalar *a, scalar *b, int l, void *data)
Definition viscosity.h:131
static double residual_viscosity(scalar *a, scalar *b, scalar *resl, void *data)
Definition viscosity.h:222
#define lambda
Definition viscosity.h:115