PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
poisson.c
Go to the documentation of this file.
1/**
2 * @file poisson.c
3 * @brief Pressure-Poisson projection: operator, right-hand side, multigrid solve,
4 * pressure update, and velocity correction.
5 *
6 * Grid layout. Cell-centred quantities live at DMDA indices 1..m-2 on each axis; indices 0
7 * and m-1 are dummy layers that carry no unknown. On a periodic axis the interior wraps
8 * from m-2 to 1. The contravariant flux `Ucont[k][j][i].x` sits on the face between cells
9 * i and i+1, and likewise for the other two components.
10 *
11 * Face gradient. The flux of grad(Phi) through the face between cell c and c + e_n is
12 *
13 * sum_b g_nb D_b(Phi), g_nb = (F_b . F_n) * aj_n,
14 *
15 * where F_b are the contravariant base vectors stored on the n-faces, aj_n is the inverse
16 * Jacobian there, D_n is the difference across the face, and D_b (b != n) is the transverse
17 * difference chosen by PoissonOperator_TransverseDifference(). The operator assembles the
18 * divergence of this flux, and the projection subtracts it from `Ucont`, so the two are
19 * consistent by construction.
20 */
21#include "poisson.h"
22#include "logging.h"
23#include "setup.h"
24
25/** Cells whose `nvert` exceeds this value are solid. */
26#define POISSON_SOLID_THRESHOLD 0.1
27
28/** Offsets of the 19 stencil points, in the column order used to insert each row. */
29static const PetscInt POISSON_STENCIL_OFFSETS[19][3] = {
30 { 0, 0, 0}, /* centre */
31 { 1, 0, 0}, {-1, 0, 0}, { 0, 1, 0}, { 0, -1, 0}, /* faces */
32 { 0, 0, 1}, { 0, 0, -1},
33 { 1, 1, 0}, { 1, -1, 0}, {-1, 1, 0}, {-1, -1, 0}, /* edges */
34 { 0, 1, 1}, { 0, 1, -1}, { 0, -1, 1}, { 0, -1, -1},
35 { 1, 0, 1}, { 1, 0, -1}, {-1, 0, 1}, {-1, 0, -1},
36};
37
38/**
39 * @brief Transverse difference used at one face.
40 *
41 * The difference is `weight * (sum of Phi on row hi - sum of Phi on row lo)`, where a row
42 * is the pair of cells on either side of the face, displaced by the given offset along the
43 * transverse axis.
44 */
45typedef struct {
46 PetscInt lo; /**< Transverse offset of the subtracted row pair. */
47 PetscInt hi; /**< Transverse offset of the added row pair. */
48 PetscReal weight; /**< 0.25 central, 0.5 one-sided, 0 when no fluid side remains. */
50
51/** @brief Everything needed to evaluate the gradient flux through one face. */
52typedef struct {
53 PetscReal dot[3]; /**< F_b . F_n on the face, b = 0..2. */
54 PetscReal aj; /**< Inverse Jacobian on the face. */
55 PoissonTransverseDifference diff[3]; /**< Transverse differences; diff[n] is unused. */
57
58/** @brief Read-only face metric arrays: `metric[n][b]` is F_b on the n-faces. */
59typedef struct {
60 const Cmpnts ***metric[3][3];
61 const PetscReal ***aj[3];
63
64/** @brief Field IDs of the face metric vectors, indexed as PoissonFaceMetrics. */
71
72/**
73 * @brief Maps the 19 stencil offsets to their slots; corners of the 3x3x3 block are -1.
74 */
75static PetscInt PoissonOperator_StencilSlot(const PetscInt d[3])
76{
77 for (PetscInt s = 0; s < 19; s++) {
78 if (POISSON_STENCIL_OFFSETS[s][0] == d[0] && POISSON_STENCIL_OFFSETS[s][1] == d[1] &&
79 POISSON_STENCIL_OFFSETS[s][2] == d[2]) return s;
80 }
81 return -1;
82}
83
84/** @brief Value of a cell-centred array at cell @p c displaced by @p d. */
85static inline PetscReal PoissonOperator_At(const PetscReal ***field, const PetscInt c[3],
86 const PetscInt d[3])
87{
88 return field[c[2] + d[2]][c[1] + d[1]][c[0] + d[0]];
89}
90
91/** @brief Records which axes are periodic, from the negative face of each axis. */
92static void PoissonOperator_PeriodicAxes(const UserCtx *user, PetscBool periodic[3])
93{
94 periodic[0] = (PetscBool)(user->boundary_faces[BC_FACE_NEG_X].mathematical_type == PERIODIC);
95 periodic[1] = (PetscBool)(user->boundary_faces[BC_FACE_NEG_Y].mathematical_type == PERIODIC);
96 periodic[2] = (PetscBool)(user->boundary_faces[BC_FACE_NEG_Z].mathematical_type == PERIODIC);
97}
98
99/**
100 * @brief Chooses the transverse difference along axis @p t at the face between cell
101 * @p c and `c + e_n`.
102 *
103 * The central difference averages the two cells beside the face over rows -1 and +1.
104 * When the +1 row is a non-periodic boundary layer or touches a solid cell, the difference
105 * falls back to rows -1 and 0, and symmetrically to rows 0 and +1; with neither side
106 * available the transverse term vanishes.
107 */
109 const PetscReal ***nvert, const PetscInt c[3], PetscInt n, PetscInt t,
110 const PetscInt m[3], const PetscBool periodic[3])
111{
112 PoissonTransverseDifference diff = {0, 0, 0.0};
113 PetscInt plus[3] = {0, 0, 0}, plus_n[3] = {0, 0, 0};
114 PetscInt minus[3] = {0, 0, 0}, minus_n[3] = {0, 0, 0};
115 const PetscInt s = c[t];
116
117 plus[t] = 1; plus_n[t] = 1; plus_n[n] = 1;
118 minus[t] = -1; minus_n[t] = -1; minus_n[n] = 1;
119 const PetscReal solid_plus = PoissonOperator_At(nvert, c, plus) + PoissonOperator_At(nvert, c, plus_n);
120 const PetscReal solid_minus = PoissonOperator_At(nvert, c, minus) + PoissonOperator_At(nvert, c, minus_n);
121
122 if ((s == m[t] - 2 && !periodic[t]) || solid_plus > POISSON_SOLID_THRESHOLD) {
123 if (solid_minus < POISSON_SOLID_THRESHOLD && (s != 1 || periodic[t])) {
124 diff.lo = -1; diff.hi = 0; diff.weight = 0.5;
125 }
126 } else if ((s == 1 && !periodic[t]) || solid_minus > POISSON_SOLID_THRESHOLD) {
127 if (solid_plus < POISSON_SOLID_THRESHOLD) {
128 diff.lo = 0; diff.hi = 1; diff.weight = 0.5;
129 }
130 } else {
131 diff.lo = -1; diff.hi = 1; diff.weight = 0.25;
132 }
133 return diff;
134}
135
136/**
137 * @brief Gathers the metric coefficients and transverse differences of the gradient flux
138 * through the face between cell @p c and `c + e_n`.
139 */
141 const PoissonFaceMetrics *metrics, const PetscReal ***nvert, const PetscInt c[3],
142 PetscInt n, const PetscInt m[3], const PetscBool periodic[3])
143{
145 const Cmpnts normal = metrics->metric[n][n][c[2]][c[1]][c[0]];
146
147 face.aj = metrics->aj[n][c[2]][c[1]][c[0]];
148 for (PetscInt b = 0; b < 3; b++) {
149 const Cmpnts base = metrics->metric[n][b][c[2]][c[1]][c[0]];
150 face.dot[b] = base.x * normal.x + base.y * normal.y + base.z * normal.z;
151 if (b == n) {
152 face.diff[b].lo = 0; face.diff[b].hi = 0; face.diff[b].weight = 0.0;
153 } else {
154 face.diff[b] = PoissonOperator_TransverseDifference(nvert, c, n, b, m, periodic);
155 }
156 }
157 return face;
158}
159
160/** @brief Borrows read access to the face metric arrays of @p user. */
161static PetscErrorCode PoissonOperator_GetFaceMetrics(UserCtx *user, PoissonFaceMetrics *metrics)
162{
163 FieldView view;
164
165 PetscFunctionBeginUser;
166 for (PetscInt n = 0; n < 3; n++) {
167 for (PetscInt b = 0; b < 3; b++) {
168 PetscCall(FieldGetView(user, POISSON_FACE_METRIC_FIELDS[n][b], &view));
169 PetscCall(DMDAVecGetArrayRead(view.dm, view.local_vec, (void *)&metrics->metric[n][b]));
170 }
171 PetscCall(FieldGetView(user, POISSON_FACE_AJ_FIELDS[n], &view));
172 PetscCall(DMDAVecGetArrayRead(view.dm, view.local_vec, (void *)&metrics->aj[n]));
173 }
174 PetscFunctionReturn(0);
175}
176
177/** @brief Returns the arrays borrowed by PoissonOperator_GetFaceMetrics(). */
179{
180 FieldView view;
181
182 PetscFunctionBeginUser;
183 for (PetscInt n = 0; n < 3; n++) {
184 for (PetscInt b = 0; b < 3; b++) {
185 PetscCall(FieldGetView(user, POISSON_FACE_METRIC_FIELDS[n][b], &view));
186 PetscCall(DMDAVecRestoreArrayRead(view.dm, view.local_vec, (void *)&metrics->metric[n][b]));
187 }
188 PetscCall(FieldGetView(user, POISSON_FACE_AJ_FIELDS[n], &view));
189 PetscCall(DMDAVecRestoreArrayRead(view.dm, view.local_vec, (void *)&metrics->aj[n]));
190 }
191 PetscFunctionReturn(0);
192}
193
194/**
195 * @brief Index of the neighbour at offset @p d (-1, 0, +1) from @p v on an axis of @p m
196 * points, wrapping between the interior layers 1 and m-2 when periodic.
197 */
198static inline PetscInt PoissonOperator_NeighborIndex(PetscInt v, PetscInt d, PetscInt m, PetscBool periodic)
199{
200 if (periodic && d == 1 && v == m - 2) return 1;
201 if (periodic && d == -1 && v == 1) return m - 2;
202 return v + d;
203}
204
205/**
206 * @brief Adds the signed gradient flux through one face to a row's stencil coefficients.
207 *
208 * @param[in] face Face gradient from PoissonOperator_FaceGradientStencil().
209 * @param[in] own Offset of the face's lower cell from the row cell.
210 * @param[in] n Normal axis of the face.
211 * @param[in] sign +1 for the row's upper face on the axis, -1 for its lower face.
212 * @param[in,out] coefficients The row's 19 coefficients.
213 */
214static void PoissonOperator_AddFaceFlux(const PoissonFaceGradient *face, const PetscInt own[3],
215 PetscInt n, PetscReal sign, PetscScalar coefficients[19])
216{
217 for (PetscInt b = 0; b < 3; b++) {
218 const PetscReal g = face->dot[b] * face->aj;
219 PetscInt lower[3] = {own[0], own[1], own[2]};
220 PetscInt upper[3] = {own[0], own[1], own[2]};
221
222 upper[n] += 1;
223 if (b == n) {
224 coefficients[PoissonOperator_StencilSlot(lower)] += sign * (-g);
225 coefficients[PoissonOperator_StencilSlot(upper)] += sign * g;
226 continue;
227 }
228
229 const PoissonTransverseDifference diff = face->diff[b];
230 if (diff.weight == 0.0) continue;
231 const PetscReal term = g * diff.weight;
232 PetscInt hi_lower[3] = {lower[0], lower[1], lower[2]}, hi_upper[3] = {upper[0], upper[1], upper[2]};
233 PetscInt lo_lower[3] = {lower[0], lower[1], lower[2]}, lo_upper[3] = {upper[0], upper[1], upper[2]};
234 hi_lower[b] += diff.hi; hi_upper[b] += diff.hi;
235 lo_lower[b] += diff.lo; lo_upper[b] += diff.lo;
236 coefficients[PoissonOperator_StencilSlot(hi_lower)] += sign * term;
237 coefficients[PoissonOperator_StencilSlot(hi_upper)] += sign * term;
238 coefficients[PoissonOperator_StencilSlot(lo_lower)] += sign * (-term);
239 coefficients[PoissonOperator_StencilSlot(lo_upper)] += sign * (-term);
240 }
241}
242
243#undef __FUNCT__
244#define __FUNCT__ "AssemblePoissonOperator"
245/**
246 * @brief Implementation of \ref AssemblePoissonOperator().
247 * @details Full API contract (arguments, ownership, side effects) is documented with
248 * the header declaration in `include/poisson.h`.
249 * @see AssemblePoissonOperator()
250 */
251PetscErrorCode AssemblePoissonOperator(UserCtx *user)
252{
253 const DMDALocalInfo info = user->info;
254 const PetscInt m[3] = {info.mx, info.my, info.mz};
255 PetscBool periodic[3];
256 PoissonFaceMetrics metrics;
257 const PetscReal ***nvert, ***aj;
258 AO ao;
259
260 PetscFunctionBeginUser;
262
263 if (!user->A) {
264 PetscInt local_rows;
265 PetscCall(VecGetLocalSize(user->Phi, &local_rows));
266 PetscCall(MatCreateAIJ(PETSC_COMM_WORLD, local_rows, local_rows, m[0] * m[1] * m[2], m[0] * m[1] * m[2],
267 19, NULL, 19, NULL, &user->A));
268 }
269 PetscCall(MatZeroEntries(user->A));
270
271 PoissonOperator_PeriodicAxes(user, periodic);
272 PetscCall(DMDAGetAO(user->da, &ao));
273 PetscCall(PoissonOperator_GetFaceMetrics(user, &metrics));
274 PetscCall(DMDAVecGetArrayRead(user->da, user->lNvert, (void *)&nvert));
275 PetscCall(DMDAVecGetArrayRead(user->da, user->lAj, (void *)&aj));
276
277 for (PetscInt k = info.zs; k < info.zs + info.zm; k++) {
278 for (PetscInt j = info.ys; j < info.ys + info.ym; j++) {
279 for (PetscInt i = info.xs; i < info.xs + info.xm; i++) {
280 const PetscInt c[3] = {i, j, k};
281 PetscInt row = i + j * m[0] + k * m[0] * m[1];
282 PetscInt columns[19];
283 PetscScalar coefficients[19] = {0.0};
284
285 PetscCall(AOApplicationToPetsc(ao, 1, &row));
286 if (i == 0 || i == m[0] - 1 || j == 0 || j == m[1] - 1 || k == 0 || k == m[2] - 1) {
287 const PetscScalar one = 1.0;
288 PetscCall(MatSetValues(user->A, 1, &row, 1, &row, &one, INSERT_VALUES));
289 continue;
290 }
291
292 for (PetscInt s = 0; s < 19; s++) {
293 const PetscInt *d = POISSON_STENCIL_OFFSETS[s];
294 columns[s] = PoissonOperator_NeighborIndex(i, d[0], m[0], periodic[0]) +
295 PoissonOperator_NeighborIndex(j, d[1], m[1], periodic[1]) * m[0] +
296 PoissonOperator_NeighborIndex(k, d[2], m[2], periodic[2]) * m[0] * m[1];
297 }
298 PetscCall(AOApplicationToPetsc(ao, 19, columns));
299
300 if (nvert[k][j][i] > POISSON_SOLID_THRESHOLD) {
301 /* Solid rows keep the fluid row's structure with zero couplings, so a
302 solid field that changes during a run reassembles in place. */
303 coefficients[0] = 1.0;
304 PetscCall(MatSetValues(user->A, 1, &row, 19, columns, coefficients, INSERT_VALUES));
305 continue;
306 }
307
308 /* Faces in the order east, west, north, south, top, bottom. Non-periodic
309 boundary faces carry no flux (homogeneous Neumann). */
310 for (PetscInt n = 0; n < 3; n++) {
311 const PetscInt first = periodic[n] ? 0 : 1;
312 const PetscInt last = periodic[n] ? m[n] - 1 : m[n] - 2;
313 for (PetscInt side = 0; side < 2; side++) {
314 PetscInt across[3] = {0, 0, 0}, own[3] = {0, 0, 0}, face_cell[3] = {i, j, k};
315 const PetscBool upper = (PetscBool)(side == 0);
316
317 across[n] = upper ? 1 : -1;
318 if (PoissonOperator_At(nvert, c, across) >= POISSON_SOLID_THRESHOLD) continue;
319 if (c[n] == (upper ? last : first)) continue;
320 if (!upper) { own[n] = -1; face_cell[n] -= 1; }
321
322 const PoissonFaceGradient face =
323 PoissonOperator_FaceGradientStencil(&metrics, nvert, face_cell, n, m, periodic);
324 PoissonOperator_AddFaceFlux(&face, own, n, upper ? 1.0 : -1.0, coefficients);
325 }
326 }
327
328 for (PetscInt s = 0; s < 19; s++) coefficients[s] *= -aj[k][j][i];
329 PetscCall(MatSetValues(user->A, 1, &row, 19, columns, coefficients, INSERT_VALUES));
330 }
331 }
332 }
333
334 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lAj, (void *)&aj));
335 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lNvert, (void *)&nvert));
336 PetscCall(PoissonOperator_RestoreFaceMetrics(user, &metrics));
337 PetscCall(MatAssemblyBegin(user->A, MAT_FINAL_ASSEMBLY));
338 PetscCall(MatAssemblyEnd(user->A, MAT_FINAL_ASSEMBLY));
339
340 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Poisson operator assembled on level %d.\n", user->thislevel);
342 PetscFunctionReturn(0);
343}
344
345#undef __FUNCT__
346#define __FUNCT__ "ComputePoissonRHS"
347/**
348 * @brief Implementation of \ref ComputePoissonRHS().
349 * @details Full API contract (arguments, ownership, side effects) is documented with
350 * the header declaration in `include/poisson.h`.
351 * @see ComputePoissonRHS()
352 */
353PetscErrorCode ComputePoissonRHS(UserCtx *user, Vec B)
354{
355 SimCtx *simCtx = user->simCtx;
356 const DMDALocalInfo info = user->info;
357 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
358 const PetscReal dt = simCtx->dt;
359 const Cmpnts ***ucont;
360 const PetscReal ***nvert, ***aj;
361 PetscReal ***rhs;
362 PetscReal local_sum = 0.0, global_sum = 0.0;
363
364 PetscFunctionBeginUser;
366 PetscCall(DMDAVecGetArray(user->da, B, &rhs));
367 PetscCall(DMDAVecGetArrayRead(user->fda, user->lUcont, (void *)&ucont));
368 PetscCall(DMDAVecGetArrayRead(user->da, user->lNvert, (void *)&nvert));
369 PetscCall(DMDAVecGetArrayRead(user->da, user->lAj, (void *)&aj));
370
371 for (PetscInt k = info.zs; k < info.zs + info.zm; k++) {
372 for (PetscInt j = info.ys; j < info.ys + info.ym; j++) {
373 for (PetscInt i = info.xs; i < info.xs + info.xm; i++) {
374 if (i == 0 || i == mx - 1 || j == 0 || j == my - 1 || k == 0 || k == mz - 1 ||
375 nvert[k][j][i] > POISSON_SOLID_THRESHOLD) {
376 rhs[k][j][i] = 0.0;
377 } else {
378 rhs[k][j][i] = -(ucont[k][j][i].x - ucont[k][j][i-1].x +
379 ucont[k][j][i].y - ucont[k][j-1][i].y +
380 ucont[k][j][i].z - ucont[k-1][j][i].z) / dt * aj[k][j][i] * COEF_TIME_ACCURACY;
381 }
382 }
383 }
384 }
385
386 /* The integral of the right-hand side is the net volume flux into the domain carried
387 by the uncorrected velocity. With Neumann pressure boundaries it must vanish for
388 the equation to have a solution. */
389 for (PetscInt k = info.zs; k < info.zs + info.zm; k++) {
390 for (PetscInt j = info.ys; j < info.ys + info.ym; j++) {
391 for (PetscInt i = info.xs; i < info.xs + info.xm; i++) {
392 local_sum += rhs[k][j][i] / aj[k][j][i] * dt / COEF_TIME_ACCURACY;
393 }
394 }
395 }
396 PetscCallMPI(MPI_Allreduce(&local_sum, &global_sum, 1, MPIU_REAL, MPI_SUM, PetscObjectComm((PetscObject)B)));
397 simCtx->poissonSourceImbalance = global_sum;
398 LOG_ALLOW(GLOBAL, LOG_INFO, "Poisson source imbalance: %le\n", (double)global_sum);
399
400 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lAj, (void *)&aj));
401 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lNvert, (void *)&nvert));
402 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lUcont, (void *)&ucont));
403 PetscCall(DMDAVecRestoreArray(user->da, B, &rhs));
405 PetscFunctionReturn(0);
406}
407
408#undef __FUNCT__
409#define __FUNCT__ "UpdatePressure"
410/**
411 * @brief Implementation of \ref UpdatePressure().
412 * @details Full API contract (arguments, ownership, side effects) is documented with
413 * the header declaration in `include/poisson.h`.
414 * @see UpdatePressure()
415 */
416PetscErrorCode UpdatePressure(UserCtx *user)
417{
418 const FieldId periodic_fields[] = {FIELD_ID_P, FIELD_ID_PHI};
419
420 PetscFunctionBeginUser;
422 PetscCall(VecAXPY(user->P, 1.0, user->Phi));
423 PetscCall(SynchronizePeriodicCellFields(user, 2, periodic_fields));
424 PetscCall(UpdateLocalGhosts(user, FIELD_ID_P));
425 PetscCall(UpdateLocalGhosts(user, FIELD_ID_PHI));
427 PetscFunctionReturn(0);
428}
429
430#undef __FUNCT__
431#define __FUNCT__ "ProjectVelocity"
432/**
433 * @brief Implementation of \ref ProjectVelocity().
434 * @details Full API contract (arguments, ownership, side effects) is documented with
435 * the header declaration in `include/poisson.h`.
436 * @see ProjectVelocity()
437 */
438PetscErrorCode ProjectVelocity(UserCtx *user)
439{
440 SimCtx *simCtx = user->simCtx;
441 const DMDALocalInfo info = user->info;
442 const PetscInt m[3] = {info.mx, info.my, info.mz};
443 const PetscInt start[3] = {info.xs, info.ys, info.zs};
444 const PetscInt end[3] = {info.xs + info.xm, info.ys + info.ym, info.zs + info.zm};
445 const PetscReal scale = simCtx->dt / COEF_TIME_ACCURACY;
446 const FieldId staggered_fields[] = {FIELD_ID_UCONT};
447 PetscBool periodic[3];
448 PetscInt interior_start[3], interior_end[3];
449 PoissonFaceMetrics metrics;
450 const PetscReal ***nvert, ***phi;
451 Cmpnts ***ucont;
452
453 PetscFunctionBeginUser;
455 PoissonOperator_PeriodicAxes(user, periodic);
456 for (PetscInt a = 0; a < 3; a++) {
457 interior_start[a] = (start[a] == 0) ? 1 : start[a];
458 interior_end[a] = (end[a] == m[a]) ? m[a] - 1 : end[a];
459 }
460
461 PetscCall(PoissonOperator_GetFaceMetrics(user, &metrics));
462 PetscCall(DMDAVecGetArrayRead(user->da, user->lNvert, (void *)&nvert));
463 PetscCall(DMDAVecGetArrayRead(user->da, user->lPhi, (void *)&phi));
464 PetscCall(DMDAVecGetArray(user->fda, user->Ucont, &ucont));
465
466 /* One pass per face orientation. Faces between two interior cells are corrected on
467 every axis; a periodic axis also corrects its seam face at index 0. */
468 for (PetscInt n = 0; n < 3; n++) {
469 PetscInt lo[3], hi[3];
470 for (PetscInt a = 0; a < 3; a++) { lo[a] = interior_start[a]; hi[a] = interior_end[a]; }
471 if (periodic[n] && start[n] == 0) lo[n] = 0;
472 hi[n] = PetscMin(hi[n], periodic[n] ? m[n] - 1 : m[n] - 2);
473
474 for (PetscInt k = lo[2]; k < hi[2]; k++) {
475 for (PetscInt j = lo[1]; j < hi[1]; j++) {
476 for (PetscInt i = lo[0]; i < hi[0]; i++) {
477 const PetscInt c[3] = {i, j, k};
478 PetscInt across[3] = {0, 0, 0};
479 PetscReal difference[3];
480
481 across[n] = 1;
482 if (nvert[k][j][i] > POISSON_SOLID_THRESHOLD ||
483 PoissonOperator_At(nvert, c, across) > POISSON_SOLID_THRESHOLD) continue;
484
485 const PoissonFaceGradient face =
486 PoissonOperator_FaceGradientStencil(&metrics, nvert, c, n, m, periodic);
487 for (PetscInt b = 0; b < 3; b++) {
488 if (b == n) {
489 difference[b] = PoissonOperator_At(phi, c, across) - phi[k][j][i];
490 continue;
491 }
492 const PoissonTransverseDifference diff = face.diff[b];
493 PetscInt hi_lower[3] = {0, 0, 0}, hi_upper[3] = {0, 0, 0};
494 PetscInt lo_lower[3] = {0, 0, 0}, lo_upper[3] = {0, 0, 0};
495 hi_lower[b] = diff.hi; hi_upper[b] = diff.hi; hi_upper[n] = 1;
496 lo_lower[b] = diff.lo; lo_upper[b] = diff.lo; lo_upper[n] = 1;
497 difference[b] = (diff.weight == 0.0) ? 0.0 :
498 (PoissonOperator_At(phi, c, hi_lower) + PoissonOperator_At(phi, c, hi_upper) -
499 PoissonOperator_At(phi, c, lo_lower) - PoissonOperator_At(phi, c, lo_upper)) * diff.weight;
500 }
501
502 const PetscReal flux = difference[0] * face.dot[0] * face.aj +
503 difference[1] * face.dot[1] * face.aj +
504 difference[2] * face.dot[2] * face.aj;
505 const PetscReal correction = flux * scale;
506 if (n == 0) ucont[k][j][i].x -= correction;
507 else if (n == 1) ucont[k][j][i].y -= correction;
508 else ucont[k][j][i].z -= correction;
509 }
510 }
511 }
512 }
513
514 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucont, &ucont));
515 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lPhi, (void *)&phi));
516 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lNvert, (void *)&nvert));
517 PetscCall(PoissonOperator_RestoreFaceMetrics(user, &metrics));
518
519 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, staggered_fields));
520 PetscCall(Contra2Cart(user));
521 PetscCall(FinalizePostProjectionCellFields(user));
523 PetscFunctionReturn(0);
524}
525
526/**
527 * @brief Removes the null space of the Neumann pressure problem from a level vector.
528 *
529 * PETSc first removes the global constant. This callback then subtracts the mean over
530 * the interior fluid cells and zeroes the dummy layers and solid cells, which carry no
531 * unknown.
532 */
533static PetscErrorCode PoissonMultigrid_RemoveNullSpace(MatNullSpace nullsp, Vec X, void *ctx)
534{
535 UserCtx *user = (UserCtx *)ctx;
536 const DMDALocalInfo info = user->info;
537 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
538 const PetscInt xs = info.xs, xe = info.xs + info.xm;
539 const PetscInt ys = info.ys, ye = info.ys + info.ym;
540 const PetscInt zs = info.zs, ze = info.zs + info.zm;
541 const PetscInt lxs = (xs == 0) ? 1 : xs, lxe = (xe == mx) ? mx - 1 : xe;
542 const PetscInt lys = (ys == 0) ? 1 : ys, lye = (ye == my) ? my - 1 : ye;
543 const PetscInt lzs = (zs == 0) ? 1 : zs, lze = (ze == mz) ? mz - 1 : ze;
544 const PetscReal ***nvert;
545 PetscReal ***x;
546 PetscReal local[2] = {0.0, 0.0}, global[2];
547 MPI_Comm comm = PetscObjectComm((PetscObject)X);
548
549 PetscFunctionBeginUser;
550 (void)nullsp;
551 PetscCall(DMDAVecGetArray(user->da, X, &x));
552 PetscCall(DMDAVecGetArrayRead(user->da, user->lNvert, (void *)&nvert));
553
554 for (PetscInt k = lzs; k < lze; k++) {
555 for (PetscInt j = lys; j < lye; j++) {
556 for (PetscInt i = lxs; i < lxe; i++) {
557 if (nvert[k][j][i] < POISSON_SOLID_THRESHOLD) {
558 local[0] += x[k][j][i];
559 local[1] += 1.0;
560 }
561 }
562 }
563 }
564 PetscCallMPI(MPI_Allreduce(&local[0], &global[0], 1, MPIU_REAL, MPI_SUM, comm));
565 PetscCallMPI(MPI_Allreduce(&local[1], &global[1], 1, MPIU_REAL, MPI_SUM, comm));
566 const PetscReal shift = global[0] / (-1.0 * global[1]);
567 for (PetscInt k = lzs; k < lze; k++) {
568 for (PetscInt j = lys; j < lye; j++) {
569 for (PetscInt i = lxs; i < lxe; i++) {
570 if (nvert[k][j][i] < POISSON_SOLID_THRESHOLD) x[k][j][i] += shift;
571 }
572 }
573 }
574
575 for (PetscInt k = zs; k < ze; k++) {
576 for (PetscInt j = ys; j < ye; j++) {
577 for (PetscInt i = xs; i < xe; i++) {
578 if (i == 0 || i == mx - 1 || j == 0 || j == my - 1 || k == 0 || k == mz - 1 ||
579 nvert[k][j][i] > POISSON_SOLID_THRESHOLD) x[k][j][i] = 0.0;
580 }
581 }
582 }
583
584 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lNvert, (void *)&nvert));
585 PetscCall(DMDAVecRestoreArray(user->da, X, &x));
586 PetscFunctionReturn(0);
587}
588
589/**
590 * @brief Coarse cell and interpolation direction along one axis for fine index @p f.
591 *
592 * Each fine cell takes 3/4 of its parent coarse cell and 1/4 of the coarse neighbour on
593 * the side it lies towards. The first and last interior cells and semi-coarsened axes use
594 * the parent only, as does a neighbour that is solid on the coarse grid.
595 */
596static void PoissonMultigrid_InterpolationParent(PetscInt f, PetscInt m, PetscInt semi,
597 PetscInt *coarse, PetscInt *direction)
598{
599 if (semi) {
600 *coarse = f;
601 *direction = 0;
602 return;
603 }
604 *coarse = (f + 1) / 2;
605 *direction = (f - 2 * (*coarse)) == 0 ? 1 : -1;
606 if (f == 1 || f == m - 2) *direction = 0;
607}
608
609/**
610 * @brief Prolongs a coarse-level correction to the next finer level (MatShell multiply).
611 * @details The shell context is the fine-level UserCtx.
612 */
613static PetscErrorCode PoissonMultigrid_Interpolate(Mat P, Vec X, Vec F)
614{
615 UserCtx *user, *coarse;
616 DMDALocalInfo info;
617 Vec lX;
618 const PetscReal ***x, ***nvert, ***nvert_c;
619 PetscReal ***f;
620
621 PetscFunctionBeginUser;
622 PetscCall(MatShellGetContext(P, &user));
623 coarse = user->user_c;
624 info = user->info;
625 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
626 const PetscInt xs = info.xs, xe = info.xs + info.xm;
627 const PetscInt ys = info.ys, ye = info.ys + info.ym;
628 const PetscInt zs = info.zs, ze = info.zs + info.zm;
629 const PetscInt lxs = (xs == 0) ? 1 : xs, lxe = (xe == mx) ? mx - 1 : xe;
630 const PetscInt lys = (ys == 0) ? 1 : ys, lye = (ye == my) ? my - 1 : ye;
631 const PetscInt lzs = (zs == 0) ? 1 : zs, lze = (ze == mz) ? mz - 1 : ze;
632
633 PetscCall(DMGetLocalVector(coarse->da, &lX));
634 PetscCall(DMGlobalToLocalBegin(coarse->da, X, INSERT_VALUES, lX));
635 PetscCall(DMGlobalToLocalEnd(coarse->da, X, INSERT_VALUES, lX));
636 PetscCall(DMDAVecGetArrayRead(coarse->da, lX, (void *)&x));
637 PetscCall(DMDAVecGetArrayRead(coarse->da, coarse->lNvert, (void *)&nvert_c));
638 PetscCall(DMDAVecGetArrayRead(user->da, user->lNvert, (void *)&nvert));
639 PetscCall(DMDAVecGetArray(user->da, F, &f));
640
641 for (PetscInt k = lzs; k < lze; k++) {
642 for (PetscInt j = lys; j < lye; j++) {
643 for (PetscInt i = lxs; i < lxe; i++) {
644 PetscInt ic, jc, kc, ia, ja, ka;
645
646 PoissonMultigrid_InterpolationParent(i, mx, user->isc, &ic, &ia);
647 PoissonMultigrid_InterpolationParent(j, my, user->jsc, &jc, &ja);
648 PoissonMultigrid_InterpolationParent(k, mz, user->ksc, &kc, &ka);
649 if (ka == -1 && nvert_c[kc-1][jc][ic] > POISSON_SOLID_THRESHOLD) ka = 0;
650 else if (ka == 1 && nvert_c[kc+1][jc][ic] > POISSON_SOLID_THRESHOLD) ka = 0;
651 if (ja == -1 && nvert_c[kc][jc-1][ic] > POISSON_SOLID_THRESHOLD) ja = 0;
652 else if (ja == 1 && nvert_c[kc][jc+1][ic] > POISSON_SOLID_THRESHOLD) ja = 0;
653 if (ia == -1 && nvert_c[kc][jc][ic-1] > POISSON_SOLID_THRESHOLD) ia = 0;
654 else if (ia == 1 && nvert_c[kc][jc][ic+1] > POISSON_SOLID_THRESHOLD) ia = 0;
655
656 f[k][j][i] = (x[kc ][jc ][ic ] * 9 +
657 x[kc ][jc+ja][ic ] * 3 +
658 x[kc ][jc ][ic+ia] * 3 +
659 x[kc ][jc+ja][ic+ia]) * 3./64. +
660 (x[kc+ka][jc ][ic ] * 9 +
661 x[kc+ka][jc+ja][ic ] * 3 +
662 x[kc+ka][jc ][ic+ia] * 3 +
663 x[kc+ka][jc+ja][ic+ia]) / 64.;
664 }
665 }
666 }
667 for (PetscInt k = zs; k < ze; k++) {
668 for (PetscInt j = ys; j < ye; j++) {
669 for (PetscInt i = xs; i < xe; i++) {
670 if (i == 0 || i == mx - 1 || j == 0 || j == my - 1 || k == 0 || k == mz - 1 ||
671 nvert[k][j][i] > POISSON_SOLID_THRESHOLD) f[k][j][i] = 0.0;
672 }
673 }
674 }
675
676 PetscCall(DMDAVecRestoreArray(user->da, F, &f));
677 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lNvert, (void *)&nvert));
678 PetscCall(DMDAVecRestoreArrayRead(coarse->da, coarse->lNvert, (void *)&nvert_c));
679 PetscCall(DMDAVecRestoreArrayRead(coarse->da, lX, (void *)&x));
680 PetscCall(DMRestoreLocalVector(coarse->da, &lX));
681 PetscFunctionReturn(0);
682}
683
684/**
685 * @brief Restricts a fine-level residual to the next coarser level (MatShell multiply).
686 *
687 * Each coarse cell averages the eight fine cells it covers, weighting each by its fluid
688 * fraction; coarse dummy and solid cells receive zero. The shell context is the
689 * coarse-level UserCtx.
690 */
691static PetscErrorCode PoissonMultigrid_Restrict(Mat R, Vec X, Vec F)
692{
693 UserCtx *user, *fine;
694 DMDALocalInfo info;
695 Vec lX;
696 const PetscReal ***x, ***nvert, ***nvert_f;
697 PetscReal ***f;
698
699 PetscFunctionBeginUser;
700 PetscCall(MatShellGetContext(R, &user));
701 fine = user->user_f;
702 info = user->info;
703 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
704 const PetscInt ia = user->isc ? 0 : 1, ja = user->jsc ? 0 : 1, ka = user->ksc ? 0 : 1;
705
706 PetscCall(DMGetLocalVector(fine->da, &lX));
707 PetscCall(DMGlobalToLocalBegin(fine->da, X, INSERT_VALUES, lX));
708 PetscCall(DMGlobalToLocalEnd(fine->da, X, INSERT_VALUES, lX));
709 PetscCall(DMDAVecGetArrayRead(fine->da, lX, (void *)&x));
710 PetscCall(DMDAVecGetArrayRead(fine->da, fine->lNvert, (void *)&nvert_f));
711 PetscCall(DMDAVecGetArrayRead(user->da, user->lNvert, (void *)&nvert));
712 PetscCall(DMDAVecGetArray(user->da, F, &f));
713
714 for (PetscInt k = info.zs; k < info.zs + info.zm; k++) {
715 for (PetscInt j = info.ys; j < info.ys + info.ym; j++) {
716 for (PetscInt i = info.xs; i < info.xs + info.xm; i++) {
717 if (i == 0 || i == mx - 1 || j == 0 || j == my - 1 || k == 0 || k == mz - 1 ||
718 nvert[k][j][i] > POISSON_SOLID_THRESHOLD) {
719 f[k][j][i] = 0.0;
720 continue;
721 }
722 const PetscInt ih = user->isc ? i : 2 * i;
723 const PetscInt jh = user->jsc ? j : 2 * j;
724 const PetscInt kh = user->ksc ? k : 2 * k;
725 f[k][j][i] = 0.125 *
726 (x[kh ][jh ][ih ] * PetscMax(0., 1 - nvert_f[kh ][jh ][ih ]) +
727 x[kh ][jh ][ih-ia] * PetscMax(0., 1 - nvert_f[kh ][jh ][ih-ia]) +
728 x[kh ][jh-ja][ih ] * PetscMax(0., 1 - nvert_f[kh ][jh-ja][ih ]) +
729 x[kh-ka][jh ][ih ] * PetscMax(0., 1 - nvert_f[kh-ka][jh ][ih ]) +
730 x[kh ][jh-ja][ih-ia] * PetscMax(0., 1 - nvert_f[kh ][jh-ja][ih-ia]) +
731 x[kh-ka][jh-ja][ih ] * PetscMax(0., 1 - nvert_f[kh-ka][jh-ja][ih ]) +
732 x[kh-ka][jh ][ih-ia] * PetscMax(0., 1 - nvert_f[kh-ka][jh ][ih-ia]) +
733 x[kh-ka][jh-ja][ih-ia] * PetscMax(0., 1 - nvert_f[kh-ka][jh-ja][ih-ia]));
734 }
735 }
736 }
737
738 PetscCall(DMDAVecRestoreArray(user->da, F, &f));
739 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lNvert, (void *)&nvert));
740 PetscCall(DMDAVecRestoreArrayRead(fine->da, fine->lNvert, (void *)&nvert_f));
741 PetscCall(DMDAVecRestoreArrayRead(fine->da, lX, (void *)&x));
742 PetscCall(DMRestoreLocalVector(fine->da, &lX));
743 PetscFunctionReturn(0);
744}
745
746/**
747 * @brief Gives each block factor of a block-Jacobi level solver a small diagonal shift, so
748 * the factorization of a nearly singular Neumann block does not fail on a zero
749 * pivot. Does nothing for any other preconditioner.
750 */
751static PetscErrorCode PoissonMultigrid_ShiftBlockFactors(KSP level_ksp)
752{
753 PC level_pc;
754 PCType level_pc_type;
755 PetscBool is_bjacobi = PETSC_FALSE;
756 KSP *block_ksp;
757 PetscInt nblocks;
758
759 PetscFunctionBeginUser;
760 PetscCall(KSPGetPC(level_ksp, &level_pc));
761 PetscCall(PCGetType(level_pc, &level_pc_type));
762 if (level_pc_type) PetscCall(PetscStrcmp(level_pc_type, PCBJACOBI, &is_bjacobi));
763 if (!is_bjacobi) PetscFunctionReturn(0);
764
765 PetscCall(KSPSetUp(level_ksp));
766 PetscCall(PCBJacobiGetSubKSP(level_pc, &nblocks, NULL, &block_ksp));
767 for (PetscInt b = 0; b < nblocks; b++) {
768 PC block_pc;
769 PetscCall(KSPGetPC(block_ksp[b], &block_pc));
770 PetscCall(PCFactorSetShiftAmount(block_pc, 1.e-10));
771 }
772 PetscFunctionReturn(0);
773}
774
775#undef __FUNCT__
776#define __FUNCT__ "PoissonMultigrid_Build"
777/**
778 * @brief Builds the multigrid solver for block @p bi and stores it in the finest level.
779 *
780 * Assembles the operator on every level, then configures the outer `ps_` Krylov solver
781 * with a multiplicative V-cycle `PCMG`: shell restriction and interpolation between
782 * levels, block-Jacobi smoothers by default, a coarse solve limited to 40 iterations at
783 * relative tolerance 1e-8, and the Neumann null space on every level. PETSc options
784 * override these defaults.
785 *
786 * Each smoother runs `pre_sweeps` iterations before the coarse correction and
787 * `post_sweeps` after it. When the two differ, the post-smoother is a separate solver
788 * that starts as a copy of the configured pre-smoother and reads further options under
789 * `ps_mg_levels_N_up_`. Everything built here depends only on the grid metrics, the
790 * solid field, and the boundary types; calling it again after one of those changes
791 * rebuilds the solver.
792 */
793static PetscErrorCode PoissonMultigrid_Build(UserMG *usermg, PetscInt bi)
794{
795 MGCtx *mgctx = usermg->mgctx;
796 const PetscInt levels = usermg->mglevels;
797 UserCtx *finest = &mgctx[levels - 1].user[bi];
798 SimCtx *simCtx = finest->simCtx;
799 DualMonitorCtx *monitor;
800 KSP ksp;
801 PC pc;
802
803 PetscFunctionBeginUser;
805 LOG_ALLOW(GLOBAL, LOG_INFO, "Block %d: building the multigrid Poisson solver on %d levels.\n", bi, levels);
806
807 for (PetscInt l = levels - 1; l >= 0; l--) PetscCall(AssemblePoissonOperator(&mgctx[l].user[bi]));
808
809 PetscCall(KSPCreate(PETSC_COMM_WORLD, &ksp));
810 PetscCall(KSPAppendOptionsPrefix(ksp, "ps_"));
811
812 /* The convergence log is opened and closed around every solve; see
813 PoissonMultigrid_OpenConvergenceLog(). The monitor owns its context. */
814 PetscCall(PetscNew(&monitor));
815 monitor->block_id = bi;
816 monitor->file_handle = NULL;
817 PetscCall(KSPMonitorSet(ksp, DualKSPMonitor, monitor, DualMonitorDestroy));
818
819 PetscCall(KSPGetPC(ksp, &pc));
820 PetscCall(PCSetType(pc, PCMG));
821 PetscCall(PCMGSetLevels(pc, levels, NULL));
822 PetscCall(PCMGSetCycleType(pc, PC_MG_CYCLE_V));
823 PetscCall(PCMGSetType(pc, PC_MG_MULTIPLICATIVE));
824 PetscCall(PCMGSetNumberSmooth(pc, simCtx->mg_preItr));
825
826 for (PetscInt l = levels - 1; l > 0; l--) {
827 UserCtx *fine = &mgctx[l].user[bi];
828 UserCtx *coarse = &mgctx[l - 1].user[bi];
829 const PetscInt m_c = coarse->info.xm * coarse->info.ym * coarse->info.zm;
830 const PetscInt m_f = fine->info.xm * fine->info.ym * fine->info.zm;
831 const PetscInt M_c = coarse->info.mx * coarse->info.my * coarse->info.mz;
832 const PetscInt M_f = fine->info.mx * fine->info.my * fine->info.mz;
833
834 PetscCall(MatCreateShell(PETSC_COMM_WORLD, m_c, m_f, M_c, M_f, coarse, &fine->MR));
835 PetscCall(MatCreateShell(PETSC_COMM_WORLD, m_f, m_c, M_f, M_c, fine, &fine->MP));
836 PetscCall(MatShellSetOperation(fine->MR, MATOP_MULT, (void (*)(void))PoissonMultigrid_Restrict));
837 PetscCall(MatShellSetOperation(fine->MP, MATOP_MULT, (void (*)(void))PoissonMultigrid_Interpolate));
838 PetscCall(PCMGSetRestriction(pc, l, fine->MR));
839 PetscCall(PCMGSetInterpolation(pc, l, fine->MP));
840 }
841
842 for (PetscInt l = levels - 1; l >= 0; l--) {
843 UserCtx *level = &mgctx[l].user[bi];
844 KSP level_ksp;
845 PC level_pc;
846
847 if (l > 0) {
848 PetscCall(PCMGGetSmoother(pc, l, &level_ksp));
849 } else {
850 PetscCall(PCMGGetCoarseSolve(pc, &level_ksp));
851 PetscCall(KSPSetTolerances(level_ksp, 1.e-8, PETSC_DEFAULT, PETSC_DEFAULT, 40));
852 }
853 PetscCall(KSPSetOperators(level_ksp, level->A, level->A));
854 PetscCall(KSPGetPC(level_ksp, &level_pc));
855 PetscCall(PCSetType(level_pc, PCBJACOBI));
856 PetscCall(KSPSetFromOptions(level_ksp));
857 PetscCall(PoissonMultigrid_ShiftBlockFactors(level_ksp));
858
859 PetscCall(MatNullSpaceCreate(PETSC_COMM_WORLD, PETSC_TRUE, 0, NULL, &level->nullsp));
860 PetscCall(MatNullSpaceSetFunction(level->nullsp, PoissonMultigrid_RemoveNullSpace, level));
861 PetscCall(MatSetNullSpace(level->A, level->nullsp));
862 PetscCall(PCMGSetResidual(pc, l, PCMGResidualDefault, level->A));
863 PetscCall(KSPSetUp(level_ksp));
864
865 if (l > 0 && simCtx->mg_preItr != simCtx->mg_poItr) {
866 KSP post_smoother;
867
868 /* PETSc creates the post-smoother as a copy of the pre-smoother's type,
869 preconditioner and tolerances as configured so far. */
870 PetscCall(PCMGGetSmootherUp(pc, l, &post_smoother));
871 PetscCall(KSPAppendOptionsPrefix(post_smoother, "up_"));
872 PetscCall(KSPSetOperators(post_smoother, level->A, level->A));
873 PetscCall(KSPSetTolerances(post_smoother, PETSC_DEFAULT, PETSC_DEFAULT, PETSC_DEFAULT, simCtx->mg_poItr));
874 PetscCall(KSPSetFromOptions(post_smoother));
875 PetscCall(PoissonMultigrid_ShiftBlockFactors(post_smoother));
876 PetscCall(KSPSetUp(post_smoother));
877 }
878
879 if (l < levels - 1) {
880 PetscCall(MatCreateVecs(level->A, &level->R, NULL));
881 PetscCall(PCMGSetRhs(pc, l, level->R));
882 }
883 }
884
885 PetscCall(KSPSetOperators(ksp, finest->A, finest->A));
886 PetscCall(MatSetNullSpace(finest->A, finest->nullsp));
887 PetscCall(KSPSetFromOptions(ksp));
888 PetscCall(KSPSetUp(ksp));
889 PetscCall(VecDuplicate(finest->P, &finest->B));
890 finest->ksp = ksp;
891
893 PetscFunctionReturn(0);
894}
895
896/**
897 * @brief Prepares the convergence monitor for one solve and opens its log file on rank 0.
898 *
899 * The first step of a fresh run truncates the log; every other step appends, and the
900 * first step of a continued run records where it resumed.
901 */
902static PetscErrorCode PoissonMultigrid_OpenConvergenceLog(KSP ksp, SimCtx *simCtx, PetscInt bi)
903{
904 DualMonitorCtx *monitor = NULL;
905 const PetscBool first_step = (PetscBool)(simCtx->step == simCtx->StartStep + 1);
906
907 PetscFunctionBeginUser;
908 PetscCall(KSPGetMonitorContext(ksp, &monitor));
909 monitor->step = simCtx->step;
911 monitor->file_handle = NULL;
912 if (simCtx->rank == 0) {
913 char filename[PETSC_MAX_PATH_LEN + 128];
914
915 PetscCall(PetscSNPrintf(filename, sizeof(filename),
916 "%s/Poisson_Solver_Convergence_History_Block_%d.log", simCtx->log_dir, bi));
917 monitor->file_handle = fopen(filename, (first_step && !simCtx->continueMode) ? "w" : "a");
918 PetscCheck(monitor->file_handle, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
919 "Could not open KSP monitor log file: %s", filename);
920 if (simCtx->continueMode && first_step) {
921 PetscCall(PetscFPrintf(PETSC_COMM_SELF, monitor->file_handle,
922 "# Continuation from step %" PetscInt_FMT "\n", simCtx->StartStep));
923 }
924 PetscCall(PetscFPrintf(PETSC_COMM_SELF, monitor->file_handle,
925 "--- Convergence for Timestep %d, Block %d ---\n", (int)simCtx->step, bi));
926 }
927 PetscFunctionReturn(0);
928}
929
930/** @brief Closes the log file opened by PoissonMultigrid_OpenConvergenceLog(). */
931static PetscErrorCode PoissonMultigrid_CloseConvergenceLog(KSP ksp)
932{
933 DualMonitorCtx *monitor = NULL;
934
935 PetscFunctionBeginUser;
936 PetscCall(KSPGetMonitorContext(ksp, &monitor));
937 if (monitor->file_handle) {
938 fclose(monitor->file_handle);
939 monitor->file_handle = NULL;
940 }
941 PetscFunctionReturn(0);
942}
943
944#undef __FUNCT__
945#define __FUNCT__ "PoissonSolver_Multigrid"
946/**
947 * @brief Implementation of \ref PoissonSolver_Multigrid().
948 * @details Full API contract (arguments, ownership, side effects) is documented with
949 * the header declaration in `include/poisson.h`.
950 * @see PoissonSolver_Multigrid()
951 */
952PetscErrorCode PoissonSolver_Multigrid(UserMG *usermg)
953{
954 SimCtx *simCtx = usermg->mgctx[0].user[0].simCtx;
955 const FieldId staggered_fields[] = {FIELD_ID_UCONT};
956
957 PetscFunctionBeginUser;
959 LOG_ALLOW(GLOBAL, LOG_INFO, "Starting Multigrid Poisson Solve...\n");
960
961 for (PetscInt bi = 0; bi < simCtx->block_number; bi++) {
962 UserCtx *user = &usermg->mgctx[usermg->mglevels - 1].user[bi];
963 KSPConvergedReason reason;
964
965 if (!user->ksp) PetscCall(PoissonMultigrid_Build(usermg, bi));
966
967 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, staggered_fields));
968 PetscCall(ComputePoissonRHS(user, user->B));
969
970 PetscCall(PoissonMultigrid_OpenConvergenceLog(user->ksp, simCtx, bi));
971 PetscCall(KSPSolve(user->ksp, user->B, user->Phi));
973
974 /* A non-finite residual or a preconditioner that could not be built leaves Phi
975 meaningless, and the projection would carry it into the next momentum step,
976 where it surfaces as a failure of the wrong solver. Stopping at max_it is this
977 solve's normal mode and stays silent; any other divergence is reported, because
978 the projection proceeds on that Phi. */
979 PetscCall(KSPGetConvergedReason(user->ksp, &reason));
980 PetscCheck(reason != KSP_DIVERGED_NANORINF && reason != KSP_DIVERGED_PC_FAILED,
981 PETSC_COMM_WORLD, PETSC_ERR_NOT_CONVERGED,
982 "Pressure Poisson solve on block %" PetscInt_FMT " failed at step %" PetscInt_FMT
983 " (KSP reason %s). Known causes: a multigrid hierarchy coarsened too far "
984 "(reduce poisson_solver.multigrid.levels or refine the grid), or a momentum "
985 "field that has already diverged, such as an explicit time step beyond its "
986 "stability limit.",
987 bi, simCtx->step, KSPConvergedReasons[reason]);
988 if (reason < 0 && reason != KSP_DIVERGED_ITS) {
989 LOG(GLOBAL, LOG_WARNING, "Pressure Poisson solve on block %" PetscInt_FMT
990 " diverged at step %" PetscInt_FMT " (KSP reason %s); the projection uses the last iterate.\n",
991 bi, simCtx->step, KSPConvergedReasons[reason]);
992 }
993 }
994
995 LOG_ALLOW(GLOBAL, LOG_INFO, "Multigrid Poisson Solve complete.\n");
997 PetscFunctionReturn(0);
998}
PetscErrorCode SynchronizePeriodicStaggeredFields(UserCtx *user, PetscInt num_fields, const FieldId field_ids[])
Synchronizes persistent component-staggered vector fields.
PetscErrorCode FinalizePostProjectionCellFields(UserCtx *user)
Finalizes cell-centered fields after the projection step.
PetscErrorCode SynchronizePeriodicCellFields(UserCtx *user, PetscInt num_fields, const FieldId field_ids[])
Synchronizes periodic endpoint cells for a list of cell-centered fields.
PetscErrorCode FieldGetView(UserCtx *user, FieldId field_id, FieldView *view)
Resolve the existing DM and global/local vectors for one field.
FieldId
Compile-time identity for a catalogued Eulerian field.
@ FIELD_ID_JETA
@ FIELD_ID_IAJ
@ FIELD_ID_KETA
@ FIELD_ID_JAJ
@ FIELD_ID_KAJ
@ FIELD_ID_KZET
@ FIELD_ID_IETA
@ FIELD_ID_UCONT
@ FIELD_ID_ICSI
@ FIELD_ID_PHI
@ FIELD_ID_JCSI
@ FIELD_ID_JZET
@ FIELD_ID_P
@ FIELD_ID_IZET
@ FIELD_ID_KCSI
Non-owning runtime objects resolved for one field and UserCtx.
Logging utilities and macros for PETSc-based applications.
PetscErrorCode DualMonitorDestroy(void **ctx)
Destroys the DualMonitorCtx.
Definition logging.c:926
PetscBool log_to_console
Definition logging.h:58
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:46
#define LOG_ALLOW(scope, level, fmt,...)
Logging macro that checks both the log level and whether the calling function is in the allowed-funct...
Definition logging.h:200
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:894
PetscInt step
Definition logging.h:60
#define LOG(scope, level, fmt,...)
Logging macro for PETSc-based applications with scope control.
Definition logging.h:84
PetscErrorCode DualKSPMonitor(KSP ksp, PetscInt it, PetscReal rnorm, void *ctx)
A custom KSP monitor that logs to a file and optionally to the console.
Definition logging.c:965
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:30
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:885
FILE * file_handle
Definition logging.h:57
PetscInt block_id
Definition logging.h:61
Context for a dual-purpose KSP monitor.
Definition logging.h:56
static PetscErrorCode PoissonMultigrid_Restrict(Mat R, Vec X, Vec F)
Restricts a fine-level residual to the next coarser level (MatShell multiply).
Definition poisson.c:691
const PetscReal *** aj[3]
Definition poisson.c:61
static PoissonTransverseDifference PoissonOperator_TransverseDifference(const PetscReal ***nvert, const PetscInt c[3], PetscInt n, PetscInt t, const PetscInt m[3], const PetscBool periodic[3])
Chooses the transverse difference along axis t at the face between cell c and c + e_n.
Definition poisson.c:108
static PetscInt PoissonOperator_NeighborIndex(PetscInt v, PetscInt d, PetscInt m, PetscBool periodic)
Index of the neighbour at offset d (-1, 0, +1) from v on an axis of m points, wrapping between the in...
Definition poisson.c:198
PetscReal aj
Inverse Jacobian on the face.
Definition poisson.c:54
PetscReal weight
0.25 central, 0.5 one-sided, 0 when no fluid side remains.
Definition poisson.c:48
const Cmpnts *** metric[3][3]
Definition poisson.c:60
PoissonTransverseDifference diff[3]
Transverse differences; diff[n] is unused.
Definition poisson.c:55
static PetscErrorCode PoissonMultigrid_CloseConvergenceLog(KSP ksp)
Closes the log file opened by PoissonMultigrid_OpenConvergenceLog().
Definition poisson.c:931
PetscErrorCode AssemblePoissonOperator(UserCtx *user)
Implementation of AssemblePoissonOperator().
Definition poisson.c:251
static PetscErrorCode PoissonOperator_RestoreFaceMetrics(UserCtx *user, PoissonFaceMetrics *metrics)
Returns the arrays borrowed by PoissonOperator_GetFaceMetrics().
Definition poisson.c:178
static const PetscInt POISSON_STENCIL_OFFSETS[19][3]
Offsets of the 19 stencil points, in the column order used to insert each row.
Definition poisson.c:29
PetscInt hi
Transverse offset of the added row pair.
Definition poisson.c:47
static void PoissonOperator_AddFaceFlux(const PoissonFaceGradient *face, const PetscInt own[3], PetscInt n, PetscReal sign, PetscScalar coefficients[19])
Adds the signed gradient flux through one face to a row's stencil coefficients.
Definition poisson.c:214
#define POISSON_SOLID_THRESHOLD
Cells whose nvert exceeds this value are solid.
Definition poisson.c:26
static const FieldId POISSON_FACE_AJ_FIELDS[3]
Definition poisson.c:70
static void PoissonMultigrid_InterpolationParent(PetscInt f, PetscInt m, PetscInt semi, PetscInt *coarse, PetscInt *direction)
Coarse cell and interpolation direction along one axis for fine index f.
Definition poisson.c:596
static PetscReal PoissonOperator_At(const PetscReal ***field, const PetscInt c[3], const PetscInt d[3])
Value of a cell-centred array at cell c displaced by d.
Definition poisson.c:85
static PetscErrorCode PoissonMultigrid_ShiftBlockFactors(KSP level_ksp)
Gives each block factor of a block-Jacobi level solver a small diagonal shift, so the factorization o...
Definition poisson.c:751
static PetscErrorCode PoissonOperator_GetFaceMetrics(UserCtx *user, PoissonFaceMetrics *metrics)
Borrows read access to the face metric arrays of user.
Definition poisson.c:161
PetscErrorCode UpdatePressure(UserCtx *user)
Implementation of UpdatePressure().
Definition poisson.c:416
static PetscErrorCode PoissonMultigrid_Interpolate(Mat P, Vec X, Vec F)
Prolongs a coarse-level correction to the next finer level (MatShell multiply).
Definition poisson.c:613
PetscErrorCode ProjectVelocity(UserCtx *user)
Implementation of ProjectVelocity().
Definition poisson.c:438
static PetscInt PoissonOperator_StencilSlot(const PetscInt d[3])
Maps the 19 stencil offsets to their slots; corners of the 3x3x3 block are -1.
Definition poisson.c:75
static PetscErrorCode PoissonMultigrid_RemoveNullSpace(MatNullSpace nullsp, Vec X, void *ctx)
Removes the null space of the Neumann pressure problem from a level vector.
Definition poisson.c:533
PetscReal dot[3]
F_b .
Definition poisson.c:53
static void PoissonOperator_PeriodicAxes(const UserCtx *user, PetscBool periodic[3])
Records which axes are periodic, from the negative face of each axis.
Definition poisson.c:92
PetscErrorCode PoissonSolver_Multigrid(UserMG *usermg)
Implementation of PoissonSolver_Multigrid().
Definition poisson.c:952
static const FieldId POISSON_FACE_METRIC_FIELDS[3][3]
Field IDs of the face metric vectors, indexed as PoissonFaceMetrics.
Definition poisson.c:65
PetscInt lo
Transverse offset of the subtracted row pair.
Definition poisson.c:46
static PetscErrorCode PoissonMultigrid_OpenConvergenceLog(KSP ksp, SimCtx *simCtx, PetscInt bi)
Prepares the convergence monitor for one solve and opens its log file on rank 0.
Definition poisson.c:902
PetscErrorCode ComputePoissonRHS(UserCtx *user, Vec B)
Implementation of ComputePoissonRHS().
Definition poisson.c:353
static PoissonFaceGradient PoissonOperator_FaceGradientStencil(const PoissonFaceMetrics *metrics, const PetscReal ***nvert, const PetscInt c[3], PetscInt n, const PetscInt m[3], const PetscBool periodic[3])
Gathers the metric coefficients and transverse differences of the gradient flux through the face betw...
Definition poisson.c:140
static PetscErrorCode PoissonMultigrid_Build(UserMG *usermg, PetscInt bi)
Builds the multigrid solver for block bi and stores it in the finest level.
Definition poisson.c:793
Everything needed to evaluate the gradient flux through one face.
Definition poisson.c:52
Read-only face metric arrays: metric[n][b] is F_b on the n-faces.
Definition poisson.c:59
Transverse difference used at one face.
Definition poisson.c:45
Pressure-Poisson projection: the multigrid pressure-correction solve, the pressure update,...
PetscErrorCode Contra2Cart(UserCtx *user)
Reconstructs Cartesian velocity (Ucat) at cell centers from contravariant velocity (Ucont) defined on...
Definition setup.c:3300
PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:2489
PetscInt isc
Definition variables.h:1092
@ PERIODIC
Definition variables.h:318
PetscBool continueMode
Definition variables.h:876
UserCtx * user
Definition variables.h:729
PetscMPIInt rank
Definition variables.h:862
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:1099
MatNullSpace nullsp
Definition variables.h:1142
PetscInt block_number
Definition variables.h:952
UserCtx * user_f
Definition variables.h:1166
Vec lNvert
Definition variables.h:1113
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1077
PetscInt ksc
Definition variables.h:1092
PetscReal poissonSourceImbalance
Definition variables.h:1026
PetscReal dt
Definition variables.h:874
PetscInt jsc
Definition variables.h:1092
Vec Ucont
Definition variables.h:1113
PetscInt StartStep
Definition variables.h:869
PetscScalar x
Definition variables.h:122
PetscInt mg_poItr
Definition variables.h:902
UserCtx * user_c
Definition variables.h:1166
char log_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:882
PetscInt thislevel
Definition variables.h:1165
PetscScalar z
Definition variables.h:122
PetscInt mglevels
Definition variables.h:736
Vec lUcont
Definition variables.h:1113
PetscInt step
Definition variables.h:867
DMDALocalInfo info
Definition variables.h:1086
PetscScalar y
Definition variables.h:122
PetscBool ps_ksp_pic_monitor_true_residual
Definition variables.h:915
MGCtx * mgctx
Definition variables.h:739
PetscInt mg_preItr
Definition variables.h:902
BCType mathematical_type
Definition variables.h:392
#define COEF_TIME_ACCURACY
Coefficient controlling the temporal accuracy scheme (e.g., 1.5 for 2nd Order Backward Difference).
Definition variables.h:75
@ BC_FACE_NEG_X
Definition variables.h:288
@ BC_FACE_NEG_Z
Definition variables.h:290
@ BC_FACE_NEG_Y
Definition variables.h:289
A 3D point or vector with PetscScalar components.
Definition variables.h:121
Context for Multigrid operations.
Definition variables.h:728
The master context for the entire simulation.
Definition variables.h:859
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1074
User-level context for managing the entire multigrid hierarchy.
Definition variables.h:735