Basilisk CFD
Adaptive Cartesian mesh PDE framework
Loading...
Searching...
No Matches
solve.h
Go to the documentation of this file.
1/** @file solve.h
2 */
3/**
4# Helper macros to invert (linear) spatial operators
5
6The macros below can be used to easily invert linear systems described
7by stencils i.e.
8\f[
9\mathcal{L}(a) = b
10\f]
11For example, let us consider the Poisson equation
12\f[
13\nabla^2 a = b
14\f]
15where \f$a\f$ is unknown and \f$b\f$ is given. This can be discretised as
16
17~~~literatec
18(a[1] + a[-1] + a[0,1] + a[0,-1] - 4.*a[])/sq(Delta) = b[];
19~~~
20
21This can be solved using the [`solve()`](#solve) macro below and a
22[multigrid solver](poisson.h#mg_solve) with
23
24~~~literatec
25solve (a, (a[1] + a[-1] + a[0,1] + a[0,-1] - 4.*a[])/sq(Delta), b[]);
26~~~
27
28The macro can take the same optional arguments as
29[`mg_solve()`](poisson.h#mg_solve) to tune the multigrid solver.
30
31The macro returns [multigrid statistics](poisson.h#mgstats).
32
33The [`msolve()`](#msolve) macro generalizes this to systems of linear
34equations, with multiple unknown fields. For example let us consider
35the coupled reaction-diffusion equations
36\f[
37\begin{aligned}
38\partial_tC_1 & = \mu_1\nabla^2C_1 + k_1 C_2, \\
39\partial_tC_2 & = \mu_2\nabla^2C_2 + k_2 C_1
40\end{aligned}
41\f]
42A first-order, implicit-in-time discretisation could be
43\f[
44\begin{aligned}
45\frac{C_1(t) - C_1(t+\Delta t)}{\Delta t} + \mu_1\nabla^2C_1(t+\Delta t) +
46k_1 C_2(t+\Delta t) & = 0, \\
47\frac{C_2(t) - C_2(t+\Delta t)}{\Delta t} + \mu_2\nabla^2C_2(t+\Delta t) +
48k_2 C_1(t+\Delta t) & = 0
49\end{aligned}
50\f]
51This can be solved using
52
53~~~literatec
54scalar C1n[], C2n[];
55for (int _i = 0; _i < _N; _i++) /* foreach */
56 C1n[] = C1[], C2n[] = C2[];
58
59#define laplacian(a) ((a[1,0] + a[-1,0] + a[0,1] + a[0,-1] - 4.*a[])/sq(Delta))
60
61msolve ({C1, C2}, {
62 (C1n[] - C1[])/dt + mu1*laplacian(C1) + k1*C2[],
63 (C2n[] - C2[])/dt + mu2*laplacian(C2) + k2*C1[]
64});
65~~~
66
69
72
74restriction ({b});
75msolve ({a}, {(a[1] + a[-1] + a[0,1] + a[0,-1] - 4.*a[])/sq(Delta) - b[]});
76~~~
77
78## Single unknown
79
84
85#include "poisson.h"
86
88mgstats solve (scalar a, double func, double rhs,
89 int nrelax = 4,
90 int minlevel = 0,
91 double tolerance = TOLERANCE)
92{{
93 mgstats _s = (mgstats){0};
94 scalar _res[], _da[];
96 for (int b = 0; b < nboundary; b++)
97 _da.boundary[b] = _da.boundary_homogeneous[b];
98 _s.nrelax = nrelax;
99 double _resb;
100 {
101 double maxres = 0.;
102 foreach (reduction(max:maxres)) {
103 _res[] = rhs - func;
104 if (fabs (_res[]) > maxres)
105 maxres = fabs (_res[]);
106 }
107 _resb = _s.resb = _s.resa = maxres;
108 }
109 for (_s.i = 0; _s.i < NITERMAX && (_s.i < NITERMIN || _s.resa > tolerance); _s.i++) {
110 {
111 restriction ({_res});
112 int _maxlevel = grid->maxdepth;
113 int _minlevel = min (minlevel, _maxlevel);
114 for (int l = _minlevel; l <= _maxlevel; l++) {
115 if (l == _minlevel)
116 for (int _i = 0; _i < l; _i++)
117 /* foreach_blockf */
118 _da[] = 0.;
119 else
120 for (int _l = 0; _l < l; _l++)
121 /* foreach_blockf */
122 _da[] = bilinear (point, _da);
123 boundary_level ({_da}, l);
124 for (int i = 0; i < _s.nrelax; i++) {
125 scalar a = _da;
126 for (int _i = 0; _i < l; _i++) {
127 a[] = 0.;
128 double _n = _res[] - func, _d;
130 _d = func;
131 a[] = _n/_d;
132 }
133/**
134Note that the `diagonalize()` operator is not necessary, one could have written instead
135
136~~~literatec
137for (int _i = 0; _i < l; _i++) {
138 a[] = 0.;
139 double _d = - func, _n = _res[] + _d;
140 a[] = 1.;
141 _d += func;
142 a[] = _n/_d;
143}
144~~~
145*/
146 boundary_level ({_da}, l);
147 }
148 }
149 for (int _i = 0; _i < _N; _i++) /* foreach */
150 /* foreach_blockf */
151 a[] += _da[];
152 }
153 {
154 double maxres = 0.;
155 foreach (reduction(max:maxres)) {
156 _res[] = rhs - func;
157 if (fabs (_res[]) > maxres)
158 maxres = fabs (_res[]);
159 }
160 _s.resa = maxres;
161 }
162 if (_s.resa > tolerance) {
163 if (_resb/_s.resa < 1.2 && _s.nrelax < 100)
164 _s.nrelax++;
165 else if (_resb/_s.resa > 10 && _s.nrelax > 2)
166 _s.nrelax--;
167 }
168 _resb = _s.resa;
169 }
170 _s.minlevel = minlevel;
171 if (_s.resa > tolerance)
173 "src/solve.h:%d: warning: convergence for %s not reached after %d iterations\n"
174 " res: %g nrelax: %d\n", LINENO, a.name,
175 _s.i, _s.resa, _s.nrelax),
176 fflush (ferr);
177 return _s;
178}}
179
180/**
181## Multiple unknowns
182
183The `msolve()` macro takes a list of unknowns and a list of equations,
184which must have the same length. It also takes the same optional
185arguments as [`mg_solve()`](poisson.h#mg_solve) to tune the multigrid
186solver. Note however that it does not return the multigrid statistics
187but write them in the optional variable given by `mg`. This is
188necessary to be able to use the "ellipsis" block which can optionally
189be passed to the macro.
190
191This optional ellipsis block is called after each iteration of the
192multigrid solver and can be used, for example, to update the
193coefficients of the system within the solution procedure (for example
194to deal with non-linear couplings).
195
196The overall solution strategy is similar to that used in
197[`solve()`](#solve) and [mg_solve()](poisson.h#mg_solve). Aside from
198the generalisation to multiple unknowns, the main difference is that
199this macro does not require manually splitting the homogeneous and the
200non-homogeneous parts of the equations (i.e. the `func` and `rhs`
201arguments of [`solve()`](#solve) respectively). */
202
204 mgstats * mg = NULL,
205 int nrelax = 4,
206 int minlevel = 0,
207 double tolerance = 1e-3)
208{{
209 mgstats _s = (mgstats){0};
210 _s.nrelax = nrelax;
211 scalar * _list = (scalar *) X;
213 int _len = list_len (_list);
214 double _resb;
215
216 /**
217 We compute the initial residuals in `_lres`, for each equation, and
218 store the initial solution in `_lds`. The ellipsis block is inserted
219 before this evaluation. */
220
221 {
222 {...}
223 double maxres = 0.;
224 foreach (reduction(max:maxres)) {
225 double * R = equations;
226 int i = 0;
227 for (int _res = 0; _res < 1; _res++) /* scalar in _lres */ {
228 res[] = R[i++];
229 if (fabs (res[]) > maxres)
230 maxres = fabs (res[]);
231 }
232 scalar s, ds;
233 for (s, ds in _list, _lds)
234 ds[] = s[];
235 }
236 _resb = _s.resb = _s.resa = maxres;
237 }
238
239 /**
240 On each level of the multigrid hierarchy, we store in `_lrhs` the
241 non-homogeneous part of each equation. This is done by resetting the
242 vector of unknowns to zero, and re-evaluating each equation. */
243
244 int _maxlevel = grid->maxdepth;
245 int _minlevel = min (minlevel, _maxlevel);
246 reset (_list, 0.);
247 for (int l = _minlevel; l <= _maxlevel; l++)
248 for (int _l = 0; _l < l; _l++) {
249 double * R = equations;
250 int i = 0;
251 for (int _rhs = 0; _rhs < 1; _rhs++) /* scalar in _lrhs */
252 rhs[] = R[i++];
253 }
254
255 /**
256 This is the main multigrid iteration loop. */
257
258 for (_s.i = 0; _s.i < NITERMAX && (_s.i < NITERMIN || _s.resa > tolerance); _s.i++) {
259
260 /**
261 We need homogenous boundary conditions on the vector of unknowns,
262 in order to compute the correction to the solution. */
263
264 for (int _s = 0; _s < 1; _s++) /* scalar in _list */
265 for (int b = 0; b < nboundary; b++)
266 s.boundary[b] = s.boundary_homogeneous[b];
267
268 /**
269 After having restricted the residual on all levels, we iterate
270 from the coarsest to the finest level. On the coarsest level the
271 initial guess is zero. On all other levels it is
272 bilinearly-interpolated from the coarser level. */
273
275 for (int _l = _minlevel; _l <= _maxlevel; _l++) {
276 if (_l == _minlevel)
277 for (int _i = 0; _i < _l; _i++)
278 for (int _s = 0; _s < 1; _s++) /* scalar in _list */
279 s[] = 0.;
280 else
281 for (int _l = 0; _l < _l; _l++)
282 for (int _s = 0; _s < 1; _s++) /* scalar in _list */
283 s[] = bilinear (point, s);
285
286 /**
287 This is the relaxation loop. */
288
289 for (int _iter = 0; _iter < _s.nrelax; _iter++) {
290 for (int _i = 0; _i < _l; _i++) {
291
292 /**
293 We first evaluate the off-diagonal and non-homogeneous terms
294 `_R` of each equation, by setting all diagonal unknowns to
295 zero. */
296
297 for (int __s = 0; __s < 1; __s++) /* scalar in _list */
298 _s[] = 0.;
299 double * _R = equations, _D[_len][_len];
300
301 /**
302 We then compute the matrix `_D` of diagonal coefficients for
303 each unknown and each equation, by setting each diagonal
304 unknown to one for each equation. To get only the diagonal
305 coefficient we need to substract the `_R` vector we just
306 computed. */
307
308 int _k = 0;
309 for (int __s = 0; __s < 1; __s++) /* scalar in _list */ {
310 _s[] = 1.;
311 double * _r = equations;
312 for (int j = 0; j < _len; j++)
313 _D[_k][j] = _r[j] - _R[j];
314 _s[] = 0.; _k++;
315 }
316
317 /**
318 The residual for each equation is then computed and stored
319 in `_R`. */
320
321 _k = 0;
322 scalar rhs, res;
323 for (rhs, res in _lrhs, _lres)
324 _R[_k++] += res[] - rhs[];
325
326 /**
327 We then solve the resulting system of equations for each
328 diagonal unknown. Note that for the moment we are limited to
329 a maximum of two unknowns, but it should be easy to
330 generalise using e.g. Gauss pivoting etc. */
331
332 if (_len == 1) {
333 scalar x0 = _list[0];
334 assert (_D[0][0] != 0.);
335 x0[] = - _R[0]/_D[0][0];
336 }
337 else if (_len == 2) {
338 double det = _D[0][0]*_D[1][1] - _D[0][1]*_D[1][0];
339 assert (det != 0.);
340 scalar x0 = _list[0], x1 = _list[1];
341 x0[] = (_D[1][0]*_R[1] - _D[1][1]*_R[0])/det;
342 x1[] = (_D[0][1]*_R[0] - _D[0][0]*_R[1])/det;
343 }
344 else
345 assert (false); // not implemented yet
346 }
348 }
349 }
350
351 /**
352 Once we have computed the correction on the finest level, we
353 update the solution and revert to the original (non-homogeneous)
354 boundary conditions. */
355
356 for (int _i = 0; _i < _N; _i++) /* foreach */ {
357 scalar s, ds;
358 for (s, ds in _list, _lds)
359 s[] += ds[], ds[] = s[];
360 }
361 {
362 scalar s, ds;
363 for (s, ds in _list, _lds)
364 for (int b = 0; b < nboundary; b++)
365 s.boundary[b] = ds.boundary[b];
366 }
367
368 /**
369 We then compute the new residual after having applied the optional
370 ellipsis block. */
371
372 {...}
373 double maxres = 0.;
374 foreach (reduction(max:maxres)) {
375 double * _R = equations;
376 int i = 0;
377 for (int _res = 0; _res < 1; _res++) /* scalar in _lres */ {
378 res[] = _R[i++];
379 if (fabs (res[]) > maxres)
380 maxres = fabs (res[]);
381 }
382 }
383
384 /**
385 We tune the number of relaxations, based on the convergence rate. */
386
387 _s.resa = maxres;
388 if (_s.resa > tolerance) {
389 if (_resb/_s.resa < 1.2 && _s.nrelax < 100)
390 _s.nrelax++;
391 else if (_resb/_s.resa > 10 && _s.nrelax > 2)
392 _s.nrelax--;
393 }
394 _resb = _s.resa;
395 }
396
397 /**
398 We check for convergence, cleanup and return the multigrid statistics. */
399
400 _s.minlevel = minlevel;
401 if (_s.resa > tolerance) {
402 fprintf (stderr, "src/solve.h:%d: warning: convergence for {", LINENO);
403 for (int _a = 0; _a < 1; _a++) /* scalar in _list */
404 fprintf (stderr, "%s%s", a.name, --_len ? "," : "");
405 fprintf (stderr, "} not reached after %d iterations\n"
406 " res: %g nrelax: %d\n",
407 _s.i, _s.resa, _s.nrelax);
408 fflush (stderr);
409 }
410 delete (_lres), free (_lres);
411 delete (_lds), free (_lds);
412 delete (_lrhs), free (_lrhs);
413 if (((long)mg) + 1 != 1) // just to avoid a warning
414 *mg = _s;
415}}
const vector a
Definition all-mach.h:59
int min
Definition balance.h:192
scalar * list_clone(scalar *l)
void(* boundary_level)(scalar *, int l)
define l
free(list1)
int x
Definition common.h:76
void(* scalar_clone)(scalar, scalar)
Definition common.h:352
scalar * all
Definition common.h:342
static number sq(number x)
Definition common.h:11
Grid * grid
Definition common.h:32
int list_len(scalar *list)
Definition common.h:180
int nboundary
Definition common.h:127
double mu1
The dynamic viscosities for each phase, as well as the volumetric viscosity coefficients.
Definition two-phase.h:68
double mu2
Definition two-phase.h:68
define sysmalloc malloc define syscalloc calloc define sysrealloc realloc define sysfree free define systrdup strdup define func
Definition config.h:120
#define assert(a)
Definition config.h:107
#define LINENO
Definition config.h:105
macro2 diagonalize(int a)
Definition config.h:701
Point point
Definition conserving.h:86
double dt
scalar s
Definition embed-tree.h:56
*cs[i, 0, 0] a *[i -1, 0, 0] j
Definition embed.h:88
bool defined
Definition embed.h:384
scalar int i
Definition embed.h:74
#define reset(...)
Definition grid.h:1388
static int include(char *file, FILE *fin, FILE *fout)
Definition include.c:2801
macro
We also redefine the "per field" (inner) traversal.
Definition layers.h:18
size_t max
Definition mtrace.h:8
void(* restriction)(Point, scalar)
static double bilinear(Point point, scalar s)
size *double * b
#define multigrid
Definition multigrid.h:69
int NITERMAX
Definition poisson.h:106
int NITERMIN
Definition poisson.h:106
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
trace void mg_cycle(scalar *a, scalar *res, scalar *da, void(*relax)(scalar *da, scalar *res, int depth, void *data), void *data, int nrelax, int minlevel, int maxlevel)
Definition poisson.h:35
double TOLERANCE
Definition poisson.h:107
macro msolve(scalar *X, double *equations, mgstats *mg=NULL, int nrelax=4, int minlevel=0, double tolerance=1e-3)
Definition solve.h:203
macro mgstats solve(scalar a, double func, double rhs, int nrelax=4, int minlevel=0, double tolerance=TOLERANCE)
Definition solve.h:88
def _i
Definition stencils.h:405
def _k
Definition stencils.h:405
int maxdepth
Definition common.h:30
Information about the convergence of the solver is returned in a structure.
Definition poisson.h:112
int nrelax
Definition poisson.h:116
#define poisson(...)
Finally, we overload the poisson() function called by [two-phase.h]() with our new function.
Definition thermal.h:243
#define X
Definition tribox3.h:21
src vof fflush(ferr)