PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
momentum_newton_krylov.c
Go to the documentation of this file.
1#include "momentumsolvers.h"
2
6
10
16
21
26
33
34typedef struct {
35 PetscErrorCode (*Describe)(UserCtx *, MomentumPreconditionerDescription *);
36 PetscErrorCode (*AssembleInterior)(UserCtx *, Vec, Mat);
38
47
48typedef struct {
50 SNES snes;
54 PetscReal initial_norm;
55 /* These objects are owned by the solve context. They are deliberately
56 * kept here (rather than in UserCtx) because an SNES is per physical solve. */
60
61static PetscErrorCode MomentumNewtonKrylov_Validate(UserCtx *user);
62static PetscErrorCode MomentumNewtonKrylov_FormResidual(SNES snes, Vec X, Vec F, void *ctx);
63static PetscErrorCode MomentumNewtonKrylov_Monitor(SNES snes, PetscInt iteration,
64 PetscReal norm, void *ctx);
65static PetscErrorCode MomentumNewtonKrylov_LinearMonitor(KSP ksp, PetscInt iteration,
66 PetscReal norm, void *ctx);
69 SNESConvergedReason reason,
70 PetscInt nonlinear_its,
71 PetscInt function_evals,
72 PetscInt linear_its,
73 PetscReal final_norm,
74 PetscBool committed);
76 Vec X, Vec F);
79static PetscErrorCode MomentumNewtonKrylov_FormJacobian(SNES snes, Vec current_solution,
80 Mat jacobian_operator, Mat preconditioning_matrix, void *vctx);
82 UserCtx *user, Vec current_solution, Mat preconditioning_matrix);
84 UserCtx *user, Mat *preconditioning_matrix);
85
86/**
87 * @brief Captures SNES iteration norms and optionally writes PICurv history rows.
88 * @details SNES supplies the already-computed norm, so this monitor never causes
89 * an additional nonlinear residual evaluation. PETSc monitors selected through
90 * `-mom_nk_snes_monitor` remain independent and may run alongside this callback.
91 */
92static PetscErrorCode MomentumNewtonKrylov_Monitor(SNES snes, PetscInt iteration,
93 PetscReal norm, void *vctx)
94{
96 SimCtx *simCtx = ctx->user->simCtx;
97
98 (void)snes;
99 PetscFunctionBeginUser;
100 if (iteration == 0 && !ctx->have_initial_norm) {
101 ctx->initial_norm = norm;
102 ctx->have_initial_norm = PETSC_TRUE;
103 }
104 if (ctx->history_file) {
105 (void)fprintf(ctx->history_file,
106 "step: %d | block: %d | newton: %d | nonlinear_norm: %.16e\n",
107 (int)simCtx->step, (int)ctx->user->_this, (int)iteration,
108 (double)norm);
109 (void)fflush(ctx->history_file);
110 }
111 PetscFunctionReturn(PETSC_SUCCESS);
112}
113
114/**
115 * @brief Writes the effective KSP tolerance and PETSc-reported norm for each inner iteration.
116 * @details The tolerance is queried after SNES has applied any inexact-Newton forcing
117 * update, so the log distinguishes changing Eisenstat--Walker requests from changing
118 * linear convergence behavior.
119 */
120static PetscErrorCode MomentumNewtonKrylov_LinearMonitor(KSP ksp, PetscInt iteration,
121 PetscReal norm, void *vctx)
122{
124 PetscInt newton_iteration = -1;
125 PetscReal relative_tolerance = 0.0;
126
127 PetscFunctionBeginUser;
128 if (!ctx->linear_history_file) PetscFunctionReturn(PETSC_SUCCESS);
129 PetscCall(SNESGetIterationNumber(ctx->snes, &newton_iteration));
130 PetscCall(KSPGetTolerances(ksp, &relative_tolerance, NULL, NULL, NULL));
131 (void)fprintf(ctx->linear_history_file,
132 "step: %d | block: %d | newton: %d | krylov: %d | "
133 "requested_rtol: %.16e | reported_residual_norm: %.16e\n",
134 (int)ctx->user->simCtx->step, (int)ctx->user->_this,
135 (int)newton_iteration, (int)iteration,
136 (double)relative_tolerance, (double)norm);
137 (void)fflush(ctx->linear_history_file);
138 PetscFunctionReturn(PETSC_SUCCESS);
139}
140
141/** @brief Opens the optional rank-zero Newton iteration-history file. */
143{
144 SimCtx *simCtx = ctx->user->simCtx;
145 char path[PETSC_MAX_PATH_LEN + 128];
146 const char *mode;
147
148 if (!simCtx->mom_nk_monitor_history || simCtx->rank != 0) return;
149 if (PetscSNPrintf(path, sizeof(path),
150 "%s/Momentum_Solver_Newton_Krylov_History_Block_%d.log",
151 simCtx->log_dir, (int)ctx->user->_this)) return;
152 mode = (simCtx->step == simCtx->StartStep + 1 && !simCtx->continueMode) ? "w" : "a";
153 ctx->history_file = fopen(path, mode);
154 if (!ctx->history_file) {
155 LOG(GLOBAL, LOG_WARNING, "Could not open Newton iteration-history log '%s'.\n", path);
156 return;
157 }
158 if (mode[0] == 'w') {
159 (void)fprintf(ctx->history_file,
160 "# step | block | Newton iteration | nonlinear residual norm\n");
161 } else if (simCtx->continueMode && simCtx->step == simCtx->StartStep + 1) {
162 (void)fprintf(ctx->history_file, "# Continuation from step %d\n", (int)simCtx->StartStep);
163 }
164 if (PetscSNPrintf(path, sizeof(path),
165 "%s/Momentum_Solver_Newton_Krylov_Linear_History_Block_%d.log",
166 simCtx->log_dir, (int)ctx->user->_this)) return;
167 ctx->linear_history_file = fopen(path, mode);
168 if (!ctx->linear_history_file) {
169 LOG(GLOBAL, LOG_WARNING, "Could not open Newton Krylov linear-history log '%s'.\n", path);
170 return;
171 }
172 if (mode[0] == 'w') {
173 (void)fprintf(ctx->linear_history_file,
174 "# step | block | Newton iteration | Krylov iteration | "
175 "requested relative tolerance | PETSc-reported residual norm\n");
176 } else if (simCtx->continueMode && simCtx->step == simCtx->StartStep + 1) {
177 (void)fprintf(ctx->linear_history_file, "# Continuation from step %d\n",
178 (int)simCtx->StartStep);
179 }
180}
181
182/**
183 * @brief Appends one rank-zero structured Newton result for a physical step.
184 * @details File failures are deliberately diagnostic-only: rollback and PETSc
185 * cleanup must retain their original error behavior.
186 */
188 SNESConvergedReason reason,
189 PetscInt nonlinear_its,
190 PetscInt function_evals,
191 PetscInt linear_its,
192 PetscReal final_norm,
193 PetscBool committed)
194{
195 SimCtx *simCtx = ctx->user->simCtx;
196 char path[PETSC_MAX_PATH_LEN + 128];
197 const char *mode;
198 const char *reason_name;
199 FILE *file;
200
201 if (simCtx->rank != 0) return;
202 if (PetscSNPrintf(path, sizeof(path),
203 "%s/Momentum_Solver_Newton_Krylov_Summary_Block_%d.log",
204 simCtx->log_dir, (int)ctx->user->_this)) return;
205 mode = (simCtx->step == simCtx->StartStep + 1 && !simCtx->continueMode) ? "w" : "a";
206 file = fopen(path, mode);
207 if (!file) {
208 LOG(GLOBAL, LOG_WARNING, "Could not open Newton summary log '%s'.\n", path);
209 return;
210 }
211 if (mode[0] == 'w') {
212 (void)fprintf(file,
213 "# step | block | solver | Jacobian | preconditioner | SNES reason | "
214 "reason code | Newton iterations | "
215 "residual evaluations | Krylov iterations | initial nonlinear norm | "
216 "final nonlinear norm | state\n");
217 } else if (simCtx->continueMode && simCtx->step == simCtx->StartStep + 1) {
218 (void)fprintf(file, "# Continuation from step %d\n", (int)simCtx->StartStep);
219 }
220 reason_name = reason == SNES_CONVERGED_ITERATING
221 ? "SNES_CONVERGED_ITERATING" : SNESConvergedReasons[reason];
222 (void)fprintf(file,
223 "step: %d | block: %d | solver: Newton Krylov | "
224 "Jacobian: finite_difference / matrix_free | Preconditioner: %s | "
225 "reason: %s | reason_code: %d | "
226 "newton: %d | evals: %d | krylov: %d | initial: ",
227 (int)simCtx->step, (int)ctx->user->_this,
229 "none" : "frozen_momentum_jacobian / point_block",
230 reason_name, (int)reason,
231 (int)nonlinear_its, (int)function_evals, (int)linear_its);
232 if (ctx->have_initial_norm) (void)fprintf(file, "%.16e", (double)ctx->initial_norm);
233 else (void)fprintf(file, "unavailable");
234 (void)fprintf(file, " | final: %.16e | state: %s\n", (double)final_norm,
235 committed ? "committed" : "rolled_back");
236 (void)fclose(file);
237}
238
239#undef __FUNCT__
240#define __FUNCT__ "MomentumNewtonKrylov_Validate"
241/**
242 * @brief Rejects configurations outside the audited version-one feature set.
243 * @param user Single-block momentum context to validate.
244 * @return PetscErrorCode 0 when the configuration is supported.
245 */
246static PetscErrorCode MomentumNewtonKrylov_Validate(UserCtx *user)
247{
248 SimCtx *simCtx;
249 PetscReal mask_max = 0.0;
250 PetscInt velocity_dof = 0;
251
252 PetscFunctionBeginUser;
253 PetscCheck(user != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
254 "Newton Krylov requires a non-NULL UserCtx.");
255 simCtx = user->simCtx;
256 PetscCheck(simCtx != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
257 "Newton Krylov requires UserCtx::simCtx.");
258 PetscCall(DMDAGetInfo(user->fda, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
259 &velocity_dof, NULL, NULL, NULL, NULL, NULL));
260 PetscCheck(velocity_dof == 3, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
261 "Newton Krylov requires a three-component velocity DMDA (got dof=%d).",
262 velocity_dof);
263 PetscCheck(simCtx->block_number == 1, PETSC_COMM_WORLD, PETSC_ERR_SUP,
264 "Newton Krylov version one supports exactly one block (got %d).",
265 simCtx->block_number);
266 PetscCheck(!simCtx->immersed, PETSC_COMM_WORLD, PETSC_ERR_SUP,
267 "Newton Krylov version one does not support immersed boundaries.");
268 PetscCheck(!simCtx->movefsi && !simCtx->rotatefsi, PETSC_COMM_WORLD, PETSC_ERR_SUP,
269 "Newton Krylov version one does not support moving or rotating bodies/FSI.");
270 PetscCheck(!simCtx->moveframe && !simCtx->rotateframe, PETSC_COMM_WORLD, PETSC_ERR_SUP,
271 "Newton Krylov version one does not support moving or rotating reference frames.");
272 PetscCheck(!simCtx->TwoD, PETSC_COMM_WORLD, PETSC_ERR_SUP,
273 "Newton Krylov version one does not support TwoD component masking.");
274 for (PetscInt face = 0; face < 6; ++face) {
275 const BoundaryFaceConfig *cfg = &user->boundary_faces[face];
276 PetscBool supported = PETSC_FALSE;
277
278 switch (cfg->handler_type) {
280 supported = (PetscBool)(cfg->mathematical_type == WALL);
281 break;
285 supported = (PetscBool)(cfg->mathematical_type == INLET);
286 break;
288 supported = (PetscBool)(cfg->mathematical_type == OUTLET);
289 break;
291 /* Both driven handlers freeze their correction once per timestep in
292 * PreStep, which the Newton solve never re-enters, so the momentum
293 * source stays constant across every residual evaluation. The paired
294 * face checks below still apply. */
297 supported = (PetscBool)(cfg->mathematical_type == PERIODIC);
298 break;
299 default:
300 supported = PETSC_FALSE;
301 break;
302 }
303 PetscCheck(supported, PETSC_COMM_WORLD, PETSC_ERR_SUP,
304 "Newton Krylov version one does not support boundary face %d with mathematical type %d and handler %d.",
305 face, (PetscInt)cfg->mathematical_type, (PetscInt)cfg->handler_type);
306 }
307
310 PETSC_COMM_WORLD, PETSC_ERR_SUP, "Newton Krylov requires paired x-periodic faces.");
313 PETSC_COMM_WORLD, PETSC_ERR_SUP, "Newton Krylov requires paired y-periodic faces.");
316 PETSC_COMM_WORLD, PETSC_ERR_SUP, "Newton Krylov requires paired z-periodic faces.");
317
318 PetscCheck(user->Nvert != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
319 "Newton Krylov requires the cell-mask vector Nvert.");
320 /* Solid cells no longer disqualify the solver: MomentumRowIsSolidMasked() gives
321 their rows the same constrained treatment as any other row carrying no unknown.
322 The immersed-boundary method itself remains unsupported above, because its
323 velocity reconstruction does not run inside this solver's residual. */
324 PetscCall(VecMax(user->Nvert, NULL, &mask_max));
325 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Newton Krylov solid-cell mask maximum Nvert=%g.\n",
326 (double)mask_max);
327 PetscFunctionReturn(PETSC_SUCCESS);
328}
329
330#undef __FUNCT__
331#define __FUNCT__ "MomentumNewtonKrylov_ReadLinearizationConfig"
332/**
333 * @brief Reads application-owned Jacobian and preconditioner mathematics.
334 */
337{
338 char type[48] = "finite_difference";
339 char finite_difference_mode[32] = "matrix_free";
340 char model[48] = "none";
341 char structure[32] = "none";
342 PetscBool set = PETSC_FALSE, match = PETSC_FALSE;
343
344 PetscFunctionBeginUser;
345 PetscCheck(jacobian != NULL && description != NULL, PETSC_COMM_SELF,
346 PETSC_ERR_ARG_NULL, "Newton Krylov linearization configuration is NULL.");
347 PetscCall(PetscOptionsGetString(NULL, NULL, "-mom_nk_jacobian_type",
348 type, sizeof(type), &set));
349 PetscCall(PetscStrcasecmp(type, "finite_difference", &match));
350 PetscCheck(match, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
351 "-mom_nk_jacobian_type must be 'finite_difference' (got '%s').", type);
353 PetscCall(PetscOptionsGetString(NULL, NULL, "-mom_nk_jacobian_fd_mode",
354 finite_difference_mode,
355 sizeof(finite_difference_mode), &set));
356 PetscCall(PetscStrcasecmp(finite_difference_mode, "matrix_free", &match));
357 PetscCheck(match, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
358 "-mom_nk_jacobian_fd_mode must be 'matrix_free' (got '%s').",
359 finite_difference_mode);
361
362 PetscCall(PetscOptionsGetString(NULL, NULL, "-mom_nk_preconditioner_model",
363 model, sizeof(model), &set));
364 PetscCall(PetscStrcasecmp(model, "none", &match));
365 if (match) description->model = MOM_NK_PC_MODEL_NONE;
366 if (!match) {
367 PetscCall(PetscStrcasecmp(model, "frozen_momentum_jacobian", &match));
368 if (match) description->model = MOM_NK_PC_MODEL_FROZEN_MOMENTUM_JACOBIAN;
369 }
370 PetscCheck(match, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
371 "-mom_nk_preconditioner_model must be 'none' or "
372 "'frozen_momentum_jacobian' (got '%s').", model);
373 PetscCall(PetscOptionsGetString(NULL, NULL, "-mom_nk_preconditioner_structure",
374 structure, sizeof(structure), &set));
375 PetscCall(PetscStrcasecmp(structure, "none", &match));
376 if (match) description->structure = MOM_NK_PC_STRUCTURE_NONE;
377 if (!match) {
378 PetscCall(PetscStrcasecmp(structure, "point_block", &match));
379 if (match) description->structure = MOM_NK_PC_STRUCTURE_POINT_BLOCK;
380 }
381 PetscCheck(match, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
382 "-mom_nk_preconditioner_structure must be 'none' or 'point_block' "
383 "(got '%s').", structure);
384 PetscCheck((description->model == MOM_NK_PC_MODEL_NONE &&
385 description->structure == MOM_NK_PC_STRUCTURE_NONE) ||
388 PETSC_COMM_WORLD, PETSC_ERR_SUP,
389 "Unsupported Newton Krylov preconditioner model/structure combination: "
390 "model='%s', structure='%s'.", model, structure);
391 PetscFunctionReturn(PETSC_SUCCESS);
392}
393
394/**
395 * @brief Row classification with solid-cell masking folded in.
396 *
397 * A masked row's residual is identically zero for every X, so it has a zero Jacobian
398 * row and a zero column and its unknown is undetermined. It therefore needs the same
399 * identity treatment as any other row carrying no unknown. Checked after the boundary
400 * classification so a periodic duplicate keeps its representative column.
401 */
403 UserCtx *user, const PetscReal ***nvert, PetscInt i, PetscInt j, PetscInt k,
404 PetscInt component, PetscInt *ri, PetscInt *rj, PetscInt *rk)
405{
406 const MomentumRowType type = ClassifyMomentumRow(user, i, j, k, component, ri, rj, rk);
407
408 if (type == MOM_ROW_PHYSICAL && MomentumRowIsSolidMasked(nvert, i, j, k, component))
410 return type;
411}
412
413/** @brief Returns the squared Euclidean norm of one metric vector. */
415{
416 return metric.x * metric.x + metric.y * metric.y + metric.z * metric.z;
417}
418
419/**
420 * @brief Returns the face-averaged eddy viscosity the residual uses on one face.
421 *
422 * The preconditioner has to reproduce the operator it preconditions, so the interior
423 * face average here tracks `Viscous()`.
424 *
425 * The WALL branch below does not, and is retained only because it cannot currently
426 * change an assembled entry. It is unreachable by construction, not by configuration:
427 * `FrozenMomentumJacobian_PointBlock()` puts the eddy viscosity only on the three
428 * diagonal entries, and the row-at-a-time insertion in
429 * `FrozenMomentumJacobian_AssemblePointBlocks()` means row c carries only axis == c.
430 * The two coordinates that trigger the branch are exactly those
431 * `ClassifyMomentumRow()` reports as non-MOM_ROW_PHYSICAL, and only physical rows
432 * assemble a block; the WALL-on-a-periodic-axis loophole is closed by the pairing
433 * check in `BoundarySystem_Validate()`. Both rules state the same boundary
434 * fact: the wall-normal flux at a no-slip wall is not an unknown.
435 *
436 * `Viscous()` substitutes the wall-model eddy viscosity `lnu_wall` on a wall face and
437 * falls back to zero only when `simCtx->wallfunction` is unset, so this branch is
438 * already wrong for a wall-modelled run -- it simply has no way to express that yet.
439 * Widening the stencil makes it reachable and therefore wrong in effect: an interior
440 * row would need the eddy viscosity on the wall face for its off-diagonal. Fix this
441 * to read `lNu_Wall` before adding any preconditioner with stencil_width >= 1, or any
442 * row classification that makes wall-normal DOFs solvable. Tracked as issue #8.
443 */
445 const UserCtx *user, const PetscReal ***nu_t, PetscInt axis,
446 PetscInt i, PetscInt j, PetscInt k)
447{
448 const BCFace neg_face[3] = {BC_FACE_NEG_X, BC_FACE_NEG_Y, BC_FACE_NEG_Z};
449 const BCFace pos_face[3] = {BC_FACE_POS_X, BC_FACE_POS_Y, BC_FACE_POS_Z};
450 const PetscInt coord[3] = {i, j, k};
451 const PetscInt size[3] = {user->info.mx, user->info.my, user->info.mz};
452 PetscReal neighbour;
453
454 if (nu_t == NULL) return 0.0;
455 if ((user->boundary_faces[neg_face[axis]].mathematical_type == WALL && coord[axis] == 0) ||
456 (user->boundary_faces[pos_face[axis]].mathematical_type == WALL && coord[axis] == size[axis] - 2))
457 return 0.0;
458
459 neighbour = (axis == 0) ? nu_t[k][j][i + 1]
460 : (axis == 1) ? nu_t[k][j + 1][i]
461 : nu_t[k + 1][j][i];
462 return 0.5 * (nu_t[k][j][i] + neighbour);
463}
464
465/** @brief Returns the frozen-momentum point block for the current residual convention. */
467 const Cmpnts ***ucont, const Cmpnts ***csi, const Cmpnts ***eta,
468 const Cmpnts ***zet, const PetscReal ***aj, const PetscReal ***nu_t,
469 PetscInt i, PetscInt j, PetscInt k, PetscScalar block[9])
470{
471 const SimCtx *simCtx = user->simCtx;
472 const PetscReal dtc = MomentumBDFCoefficient((SimCtx *)simCtx) / simCtx->dt;
473 const PetscReal molecular = simCtx->ren > 0.0 ? 1.0 / simCtx->ren : 0.0;
474 /* The residual diffuses with nu + nu_t, so the block must too; nu_t alone is the
475 eddy contribution. Omitting it left the preconditioner modelling a viscous
476 diagonal smaller than the operator's by the eddy-to-molecular ratio, which on a
477 developed LES is order one or more. */
478 const PetscReal nu_eff_i = molecular + FrozenMomentumJacobian_FaceEddyViscosity(user, nu_t, 0, i, j, k);
479 const PetscReal nu_eff_j = molecular + FrozenMomentumJacobian_FaceEddyViscosity(user, nu_t, 1, i, j, k);
480 const PetscReal nu_eff_k = molecular + FrozenMomentumJacobian_FaceEddyViscosity(user, nu_t, 2, i, j, k);
481 const PetscReal AJip = 0.5 * (aj[k][j][i] + aj[k][j][i + 1]);
482 const PetscReal AJjp = 0.5 * (aj[k][j][i] + aj[k][j + 1][i]);
483 const PetscReal AJkp = 0.5 * (aj[k][j][i] + aj[k + 1][j][i]);
484 const PetscReal g11ip = csi[k][j][i].x * csi[k][j][i].x +
485 csi[k][j][i].y * csi[k][j][i].y +
486 csi[k][j][i].z * csi[k][j][i].z;
487 const PetscReal g22ip = 0.25 * (
491 FrozenMomentumJacobian_MetricNormSquared(eta[k][j - 1][i + 1]));
492 const PetscReal g33ip = 0.25 * (
496 FrozenMomentumJacobian_MetricNormSquared(zet[k - 1][j][i + 1]));
497 const PetscReal g11jp = 0.25 * (
501 FrozenMomentumJacobian_MetricNormSquared(csi[k][j + 1][i - 1]));
502 const PetscReal g22jp = eta[k][j][i].x * eta[k][j][i].x +
503 eta[k][j][i].y * eta[k][j][i].y +
504 eta[k][j][i].z * eta[k][j][i].z;
505 const PetscReal g33jp = 0.25 * (
509 FrozenMomentumJacobian_MetricNormSquared(zet[k - 1][j + 1][i]));
510 const PetscReal g11kp = 0.25 * (
514 FrozenMomentumJacobian_MetricNormSquared(csi[k + 1][j][i - 1]));
515 const PetscReal g22kp = 0.25 * (
519 FrozenMomentumJacobian_MetricNormSquared(eta[k + 1][j - 1][i]));
520 const PetscReal g33kp = zet[k][j][i].x * zet[k][j][i].x +
521 zet[k][j][i].y * zet[k][j][i].y +
522 zet[k][j][i].z * zet[k][j][i].z;
523 const PetscReal U0jp = 0.25 * (ucont[k][j][i].x + ucont[k][j][i - 1].x +
524 ucont[k][j + 1][i].x + ucont[k][j + 1][i - 1].x);
525 const PetscReal U0kp = 0.25 * (ucont[k][j][i].x + ucont[k][j][i - 1].x +
526 ucont[k + 1][j][i].x + ucont[k + 1][j][i - 1].x);
527 const PetscReal U1ip = 0.25 * (ucont[k][j][i].y + ucont[k][j - 1][i].y +
528 ucont[k][j][i + 1].y + ucont[k][j - 1][i + 1].y);
529 const PetscReal U1kp = 0.25 * (ucont[k][j][i].y + ucont[k][j - 1][i].y +
530 ucont[k + 1][j][i].y + ucont[k + 1][j - 1][i].y);
531 const PetscReal U2ip = 0.25 * (ucont[k][j][i].z + ucont[k - 1][j][i].z +
532 ucont[k][j][i + 1].z + ucont[k - 1][j][i + 1].z);
533 const PetscReal U2jp = 0.25 * (ucont[k][j][i].z + ucont[k - 1][j][i].z +
534 ucont[k][j + 1][i].z + ucont[k - 1][j + 1][i].z);
535 PetscReal A[6][4] = {{0.0}};
536 PetscReal Su, Sv, Sw, nui, nuj, nuk;
537
538 A[0][0] = 0.125 * aj[k][j][i] * ucont[k][j][i].y;
539 A[0][1] = -0.125 * aj[k][j - 1][i] * ucont[k][j - 1][i].y;
540 A[0][2] = 0.125 * aj[k][j][i + 1] * ucont[k][j][i + 1].y;
541 A[0][3] = -0.125 * aj[k][j - 1][i + 1] * ucont[k][j - 1][i + 1].y;
542 A[1][0] = 0.125 * aj[k][j][i] * ucont[k][j][i].z;
543 A[1][1] = -0.125 * aj[k - 1][j][i] * ucont[k - 1][j][i].z;
544 A[1][2] = 0.125 * aj[k][j][i + 1] * ucont[k][j][i + 1].z;
545 A[1][3] = -0.125 * aj[k - 1][j][i + 1] * ucont[k - 1][j][i + 1].z;
546 A[2][0] = -0.125 * aj[k][j + 1][i - 1] * ucont[k][j + 1][i - 1].x;
547 A[2][1] = -0.125 * aj[k][j][i - 1] * ucont[k][j][i - 1].x;
548 A[2][2] = 0.125 * aj[k][j + 1][i] * ucont[k][j + 1][i].x;
549 A[2][3] = 0.125 * aj[k][j][i] * ucont[k][j][i].x;
550 A[3][0] = 0.125 * aj[k][j][i] * ucont[k][j][i].z;
551 A[3][1] = -0.125 * aj[k - 1][j][i] * ucont[k - 1][j][i].z;
552 A[3][2] = 0.125 * aj[k][j + 1][i] * ucont[k][j + 1][i].z;
553 A[3][3] = -0.125 * aj[k - 1][j + 1][i] * ucont[k - 1][j + 1][i].z;
554 A[4][0] = -0.125 * aj[k + 1][j][i - 1] * ucont[k + 1][j][i - 1].x;
555 A[4][1] = -0.125 * aj[k][j][i - 1] * ucont[k][j][i - 1].x;
556 A[4][2] = 0.125 * aj[k + 1][j][i] * ucont[k + 1][j][i].x;
557 A[4][3] = 0.125 * aj[k][j][i] * ucont[k][j][i].x;
558 A[5][0] = -0.125 * aj[k + 1][j - 1][i] * ucont[k + 1][j - 1][i].y;
559 A[5][1] = -0.125 * aj[k][j - 1][i] * ucont[k][j - 1][i].y;
560 A[5][2] = 0.125 * aj[k + 1][j][i] * ucont[k + 1][j][i].y;
561 A[5][3] = 0.125 * aj[k][j][i] * ucont[k][j][i].y;
562 Su = A[0][0] + A[0][1] + A[0][2] + A[0][3] +
563 A[1][0] + A[1][1] + A[1][2] + A[1][3];
564 Sv = A[2][0] + A[2][1] + A[2][2] + A[2][3] +
565 A[3][0] + A[3][1] + A[3][2] + A[3][3];
566 Sw = A[4][0] + A[4][1] + A[4][2] + A[4][3] +
567 A[5][0] + A[5][1] + A[5][2] + A[5][3];
568 nui = AJip * AJip * (g11ip + g22ip + g33ip) * nu_eff_i;
569 nuj = AJjp * AJjp * (g11jp + g22jp + g33jp) * nu_eff_j;
570 nuk = AJkp * AJkp * (g11kp + g22kp + g33kp) * nu_eff_k;
571
572 /* MomentumNewtonKrylov_FormResidual forms F = -R, fixing this block's sign. */
573 /* `block` is row-major: each row is one residual component and each column
574 is one same-cell Ucont component. */
575 block[0] = dtc + nui + Su; block[1] = 0.5 * AJjp * U0jp; block[2] = 0.5 * AJkp * U0kp;
576 block[3] = 0.5 * AJip * U1ip; block[4] = dtc + nuj + Sv; block[5] = 0.5 * AJkp * U1kp;
577 block[6] = 0.5 * AJip * U2ip; block[7] = 0.5 * AJjp * U2jp; block[8] = dtc + nuk + Sw;
578}
579
580#undef __FUNCT__
581#define __FUNCT__ "FrozenMomentumJacobian_DescribePointBlock"
582/** @brief Describes the audited frozen-coefficient point-block model. */
584 UserCtx *user, MomentumPreconditionerDescription *description)
585{
586 PetscFunctionBeginUser;
587 (void)user;
588 PetscCheck(description != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
589 "Preconditioner description is NULL.");
592 description->block_size = 3;
593 description->stencil_width = 0;
594 PetscFunctionReturn(PETSC_SUCCESS);
595}
596
597#undef __FUNCT__
598#define __FUNCT__ "FrozenMomentumJacobian_AssemblePointBlocks"
599/** @brief Inserts only the audited interior frozen-momentum point blocks. */
601 UserCtx *user, Vec current_solution, Mat preconditioning_matrix)
602{
603 DMDALocalInfo info = user->info;
604 Cmpnts ***ucont = NULL, ***csi = NULL, ***eta = NULL, ***zet = NULL;
605 PetscReal ***aj = NULL, ***nvert = NULL, ***nu_t = NULL;
606 SimCtx *simCtx = user->simCtx;
607 /* The eddy viscosity enters the preconditioner only when a turbulence model is
608 actually producing one; without it the field may not even be allocated. */
609 const PetscBool has_eddy_viscosity =
610 (PetscBool)(simCtx->les && user->lNu_t != NULL);
611 PetscErrorCode ierr = PETSC_SUCCESS, cleanup_ierr;
612
613 PetscFunctionBeginUser;
614 /*
615 * current_solution is the current SNES trial Ucont Vec: it is layout-compatible
616 * with user->fda/user->Ucont, but is not necessarily the canonical user->Ucont
617 * selected by UpdateLocalGhosts(FIELD_ID_UCONT). Scatter that trial Vec directly with
618 * user->fda so PETSc applies its MPI ownership, periodic topology, component
619 * ordering, and ghost mapping without canonical-field synchronization or mutation
620 * of current_solution. UpdateLocalGhosts additionally repairs the component-normal
621 * staggered buffers Uxi(i=-1,mx), Ueta(j=-1,my), and Uzeta(k=-1,mz); the audited
622 * point-block velocity stencil reads none of those planes, so the repair cannot
623 * change a coefficient. Revisit this choice and extend the periodic-localization
624 * tests if the model stencil is expanded to read any repaired normal buffer.
625 */
626 ierr = DMGlobalToLocalBegin(user->fda, current_solution, INSERT_VALUES, user->lUcont);
627 if (ierr) goto cleanup;
628 ierr = DMGlobalToLocalEnd(user->fda, current_solution, INSERT_VALUES, user->lUcont);
629 if (ierr) goto cleanup;
630 ierr = DMDAVecGetArrayRead(user->fda, user->lUcont, &ucont); if (ierr) goto cleanup;
631 ierr = DMDAVecGetArrayRead(user->fda, user->lCsi, &csi); if (ierr) goto cleanup;
632 ierr = DMDAVecGetArrayRead(user->fda, user->lEta, &eta); if (ierr) goto cleanup;
633 ierr = DMDAVecGetArrayRead(user->fda, user->lZet, &zet); if (ierr) goto cleanup;
634 ierr = DMDAVecGetArrayRead(user->da, user->lAj, &aj); if (ierr) goto cleanup;
635 ierr = DMDAVecGetArrayRead(user->da, user->lNvert, &nvert); if (ierr) goto cleanup;
636 if (has_eddy_viscosity) {
637 ierr = DMDAVecGetArrayRead(user->da, user->lNu_t, &nu_t); if (ierr) goto cleanup;
638 }
639 for (PetscInt k = info.zs; k < info.zs + info.zm; ++k) {
640 for (PetscInt j = info.ys; j < info.ys + info.ym; ++j) {
641 for (PetscInt i = info.xs; i < info.xs + info.xm; ++i) {
642 for (PetscInt component = 0; component < 3; ++component) {
643 MatStencil row = {.i = i, .j = j, .k = k, .c = component};
644 PetscInt ri, rj, rk;
646 user, (const PetscReal ***)nvert, i, j, k, component, &ri, &rj, &rk);
647 if (type == MOM_ROW_PHYSICAL) {
648 PetscScalar block[9];
649 MatStencil cols[3] = {
650 {.i = i, .j = j, .k = k, .c = 0},
651 {.i = i, .j = j, .k = k, .c = 1},
652 {.i = i, .j = j, .k = k, .c = 2}
653 };
654 FrozenMomentumJacobian_PointBlock(user, (const Cmpnts ***)ucont,
655 (const Cmpnts ***)csi, (const Cmpnts ***)eta, (const Cmpnts ***)zet,
656 (const PetscReal ***)aj, (const PetscReal ***)nu_t, i, j, k, block);
657 ierr = MatSetValuesStencil(preconditioning_matrix, 1, &row, 3, cols,
658 &block[3 * component], INSERT_VALUES);
659 if (ierr) goto cleanup;
660 }
661 }
662 }
663 }
664 }
665cleanup:
666 if (nu_t) { cleanup_ierr = DMDAVecRestoreArrayRead(user->da, user->lNu_t, &nu_t); if (!ierr) ierr = cleanup_ierr; }
667 if (nvert) { cleanup_ierr = DMDAVecRestoreArrayRead(user->da, user->lNvert, &nvert); if (!ierr) ierr = cleanup_ierr; }
668 if (aj) { cleanup_ierr = DMDAVecRestoreArrayRead(user->da, user->lAj, &aj); if (!ierr) ierr = cleanup_ierr; }
669 if (zet) { cleanup_ierr = DMDAVecRestoreArrayRead(user->fda, user->lZet, &zet); if (!ierr) ierr = cleanup_ierr; }
670 if (eta) { cleanup_ierr = DMDAVecRestoreArrayRead(user->fda, user->lEta, &eta); if (!ierr) ierr = cleanup_ierr; }
671 if (csi) { cleanup_ierr = DMDAVecRestoreArrayRead(user->fda, user->lCsi, &csi); if (!ierr) ierr = cleanup_ierr; }
672 if (ucont) { cleanup_ierr = DMDAVecRestoreArrayRead(user->fda, user->lUcont, &ucont); if (!ierr) ierr = cleanup_ierr; }
673 PetscFunctionReturn(ierr);
674}
675
676#undef __FUNCT__
677#define __FUNCT__ "MomentumPreconditionerEngine_ApplyConstraintRows"
678/** @brief Inserts all common fixed, homogeneous, and periodic-duplicate rows. */
680 UserCtx *user, Mat preconditioning_matrix)
681{
682 DMDALocalInfo info = user->info;
683 const PetscReal ***nvert = NULL;
684
685 PetscFunctionBeginUser;
686 PetscCall(DMDAVecGetArrayRead(user->da, user->lNvert, &nvert));
687 for (PetscInt k = info.zs; k < info.zs + info.zm; ++k) {
688 for (PetscInt j = info.ys; j < info.ys + info.ym; ++j) {
689 for (PetscInt i = info.xs; i < info.xs + info.xm; ++i) {
690 for (PetscInt component = 0; component < 3; ++component) {
691 PetscInt ri, rj, rk;
693 user, nvert, i, j, k, component, &ri, &rj, &rk);
694 if (type != MOM_ROW_PHYSICAL) {
695 MatStencil row = {.i = i, .j = j, .k = k, .c = component};
696 MatStencil columns[2] = {row, row};
697 PetscScalar values[2] = {1.0, -1.0};
698 PetscInt column_count = 1;
699 if (type == MOM_ROW_PERIODIC_DUPLICATE) {
700 columns[column_count++] = (MatStencil){
701 .i = ri, .j = rj, .k = rk, .c = component
702 };
703 }
704 PetscCall(MatSetValuesStencil(preconditioning_matrix, 1, &row,
705 column_count, columns, values,
706 INSERT_VALUES));
707 }
708 }
709 }
710 }
711 }
712 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lNvert, &nvert));
713 PetscFunctionReturn(PETSC_SUCCESS);
714}
715
720
721#undef __FUNCT__
722#define __FUNCT__ "MomentumNewtonJacobian_Create"
723/** @brief Creates the selected Jacobian operator; currently PETSc MFFD only. */
724static PetscErrorCode MomentumNewtonJacobian_Create(SNES snes,
725 MomentumNewtonJacobian *jacobian)
726{
727 PetscFunctionBeginUser;
728 PetscCheck(jacobian->type == MOM_NK_JACOBIAN_FINITE_DIFFERENCE &&
730 PETSC_COMM_WORLD, PETSC_ERR_SUP,
731 "Unsupported Newton Krylov Jacobian type/finite-difference-mode combination.");
732 PetscCall(MatCreateSNESMF(snes, &jacobian->jacobian_operator));
733 PetscCall(MatSetOptionsPrefix(jacobian->jacobian_operator, "mom_nk_"));
734 PetscCall(PetscObjectSetName((PetscObject)jacobian->jacobian_operator,
735 "momentum_jacobian_finite_difference_matrix_free"));
736 PetscFunctionReturn(PETSC_SUCCESS);
737}
738
739/** @brief Updates the matrix-free finite-difference operator base. */
740static PetscErrorCode MomentumNewtonJacobian_Update(SNES snes, Vec current_solution,
741 MomentumNewtonJacobian *jacobian)
742{
743 PetscFunctionBeginUser;
744 PetscCall(MatMFFDComputeJacobian(snes, current_solution, jacobian->jacobian_operator,
745 jacobian->jacobian_operator, NULL));
746 PetscFunctionReturn(PETSC_SUCCESS);
747}
748
749/** @brief Registers the application orchestration callback and both SNES matrices. */
750static PetscErrorCode MomentumNewtonJacobian_Register(SNES snes,
753{
754 PetscFunctionBeginUser;
755 PetscCall(SNESSetJacobian(snes, jacobian->jacobian_operator,
758 PetscFunctionReturn(PETSC_SUCCESS);
759}
760
761/** @brief Destroys a partially or fully created Jacobian operator. */
763{
764 PetscFunctionBeginUser;
765 PetscCall(MatDestroy(&jacobian->jacobian_operator));
766 PetscFunctionReturn(PETSC_SUCCESS);
767}
768
769#undef __FUNCT__
770#define __FUNCT__ "MomentumPreconditionerEngine_Create"
771/** @brief Validates a model/structure and creates or aliases its matrix. */
773 Mat jacobian_operator, const MomentumPreconditionerDescription *requested,
775{
776 PetscFunctionBeginUser;
777 engine->description = *requested;
778 if (requested->model == MOM_NK_PC_MODEL_NONE &&
779 requested->structure == MOM_NK_PC_STRUCTURE_NONE) {
780 engine->description.block_size = 0;
781 engine->description.stencil_width = 0;
782 engine->preconditioning_matrix = jacobian_operator;
783 engine->aliases_jacobian_operator = PETSC_TRUE;
784 engine->owns_preconditioning_matrix = PETSC_FALSE;
785 engine->petsc_pc_type = PCNONE;
786 } else if (requested->model == MOM_NK_PC_MODEL_FROZEN_MOMENTUM_JACOBIAN &&
789 PetscCall(engine->model_ops->Describe(user, &engine->description));
791 user, &engine->preconditioning_matrix));
792 engine->owns_preconditioning_matrix = PETSC_TRUE;
793 PetscCall(PetscObjectSetName((PetscObject)engine->preconditioning_matrix,
794 "momentum_preconditioner_frozen_point_block"));
795 engine->aliases_jacobian_operator = PETSC_FALSE;
796 engine->petsc_pc_type = PCPBJACOBI;
797 } else {
798 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_SUP,
799 "Unsupported Newton Krylov preconditioner model/structure combination.");
800 }
801 PetscFunctionReturn(PETSC_SUCCESS);
802}
803
804#undef __FUNCT__
805#define __FUNCT__ "MomentumPreconditionerEngine_Assemble"
806/** @brief Runs model insertion, common row handling, and final assembly. */
808 MomentumPreconditionerEngine *engine, UserCtx *user, Vec current_solution)
809{
810 PetscFunctionBeginUser;
811 if (engine->aliases_jacobian_operator) PetscFunctionReturn(PETSC_SUCCESS);
812 PetscCall(MatZeroEntries(engine->preconditioning_matrix));
813 PetscCall(engine->model_ops->AssembleInterior(user, current_solution,
814 engine->preconditioning_matrix));
816 user, engine->preconditioning_matrix));
817 PetscCall(MatAssemblyBegin(engine->preconditioning_matrix, MAT_FINAL_ASSEMBLY));
818 PetscCall(MatAssemblyEnd(engine->preconditioning_matrix, MAT_FINAL_ASSEMBLY));
819 PetscFunctionReturn(PETSC_SUCCESS);
820}
821
822/** @brief Applies the validated model/structure-to-PETSc-PC mapping. */
824 MomentumPreconditionerEngine *engine, PC pc)
825{
826 PetscFunctionBeginUser;
827 PetscCall(PCSetType(pc, engine->petsc_pc_type));
828 PetscFunctionReturn(PETSC_SUCCESS);
829}
830
831/** @brief Rejects raw options that select an unvalidated PETSc PC backend. */
833 MomentumPreconditionerEngine *engine, PC pc)
834{
835 const char *actual_type = NULL;
836 PetscBool matches = PETSC_FALSE;
837
838 PetscFunctionBeginUser;
839 PetscCall(PCGetType(pc, &actual_type));
840 PetscCall(PetscStrcmp(actual_type, engine->petsc_pc_type, &matches));
841 PetscCheck(matches, PETSC_COMM_WORLD, PETSC_ERR_SUP,
842 "Newton Krylov preconditioner model/structure requires internal PETSc PC "
843 "type '%s', but raw option processing selected '%s'.",
844 engine->petsc_pc_type, actual_type ? actual_type : "(unset)");
845 PetscFunctionReturn(PETSC_SUCCESS);
846}
847
848/** @brief Destroys only a separately owned preconditioning matrix. */
851{
852 PetscFunctionBeginUser;
853 if (!engine->owns_preconditioning_matrix) engine->preconditioning_matrix = NULL;
854 PetscCall(MatDestroy(&engine->preconditioning_matrix));
855 engine->aliases_jacobian_operator = PETSC_FALSE;
856 engine->owns_preconditioning_matrix = PETSC_FALSE;
857 PetscFunctionReturn(PETSC_SUCCESS);
858}
859
860#undef __FUNCT__
861#define __FUNCT__ "MomentumNewtonKrylov_FormJacobian"
862/** @brief Updates the Jacobian and then assembles any separate preconditioning matrix. */
863static PetscErrorCode MomentumNewtonKrylov_FormJacobian(SNES snes, Vec current_solution,
864 Mat jacobian_operator, Mat preconditioning_matrix, void *vctx)
865{
867 PetscFunctionBeginUser;
868 (void)jacobian_operator;
869 (void)preconditioning_matrix;
870 PetscCall(MomentumNewtonJacobian_Update(snes, current_solution, &ctx->jacobian));
872 &ctx->preconditioning_engine, ctx->user, current_solution));
873 PetscFunctionReturn(PETSC_SUCCESS);
874}
875
876#undef __FUNCT__
877#define __FUNCT__ "MomentumPreconditionerEngine_CreateExactPointBlockMatrix"
878/**
879 * @brief Creates the frozen point-block P matrix with its exact scalar pattern.
880 * @details Row and column ownership follows the velocity DMDA global vector.
881 * Physical rows reserve the three same-point components, fixed rows reserve
882 * only their diagonal, and periodic duplicate rows additionally reserve their
883 * wrapped representative. DMDA AO, local mapping, and stencil metadata are
884 * retained so the existing insertion paths keep their exact ordering.
885 */
887 UserCtx *user, Mat *preconditioning_matrix)
888{
889 DMDALocalInfo info;
890 ISLocalToGlobalMapping local_to_global = NULL;
891 Mat matrix = NULL;
892 MPI_Comm comm;
893 PetscInt local_size, global_size, ownership_start, ownership_end;
894 PetscInt *diagonal_nnz = NULL, *offdiagonal_nnz = NULL;
895 PetscInt ghost_starts[4] = {0, 0, 0, 0}, ghost_sizes[3] = {0, 0, 0};
896 const PetscReal ***nvert = NULL;
897 PetscErrorCode ierr = PETSC_SUCCESS, cleanup_ierr;
898
899 PetscFunctionBeginUser;
900 PetscCheck(preconditioning_matrix != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
901 "Point-block matrix output is NULL.");
902 *preconditioning_matrix = NULL;
903 comm = PetscObjectComm((PetscObject)user->fda);
904 PetscCall(DMDAGetLocalInfo(user->fda, &info));
905 PetscCall(VecGetLocalSize(user->Ucont, &local_size));
906 PetscCall(VecGetSize(user->Ucont, &global_size));
907 PetscCall(VecGetOwnershipRange(user->Ucont, &ownership_start, &ownership_end));
908 PetscCheck(local_size == ownership_end - ownership_start, comm, PETSC_ERR_PLIB,
909 "Velocity ownership range does not match its local size.");
910 ierr = PetscCalloc2(local_size, &diagonal_nnz,
911 local_size, &offdiagonal_nnz); if (ierr) goto cleanup;
912 ierr = DMGetLocalToGlobalMapping(user->fda, &local_to_global); if (ierr) goto cleanup;
913 ierr = DMDAGetGhostCorners(user->fda,
914 &ghost_starts[0], &ghost_starts[1], &ghost_starts[2],
915 &ghost_sizes[0], &ghost_sizes[1], &ghost_sizes[2]);
916 if (ierr) goto cleanup;
917 ierr = DMDAVecGetArrayRead(user->da, user->lNvert, &nvert); if (ierr) goto cleanup;
918
919 for (PetscInt k = info.zs; k < info.zs + info.zm; ++k) {
920 for (PetscInt j = info.ys; j < info.ys + info.ym; ++j) {
921 for (PetscInt i = info.xs; i < info.xs + info.xm; ++i) {
922 for (PetscInt component = 0; component < 3; ++component) {
923 PetscInt ri, rj, rk, column_count;
924 MatStencil row_stencil = {.i = i, .j = j, .k = k, .c = component};
925 MatStencil column_stencils[3];
926 PetscInt row_local, row, column_locals[3], columns[3];
928 user, nvert, i, j, k, component, &ri, &rj, &rk);
929
930 if (type == MOM_ROW_PHYSICAL) {
931 column_count = 3;
932 for (PetscInt column_component = 0; column_component < 3;
933 ++column_component) {
934 column_stencils[column_component] = (MatStencil){
935 .i = i, .j = j, .k = k, .c = column_component
936 };
937 }
938 } else {
939 column_count = 1;
940 column_stencils[0] = row_stencil;
941 if (type == MOM_ROW_PERIODIC_DUPLICATE) {
942 column_stencils[column_count++] = (MatStencil){
943 .i = ri, .j = rj, .k = rk, .c = component
944 };
945 }
946 }
947 row_local = component + 3 * (
948 (i - ghost_starts[0]) + ghost_sizes[0] * (
949 (j - ghost_starts[1]) + ghost_sizes[1] *
950 (k - ghost_starts[2])));
951 for (PetscInt column_index = 0; column_index < column_count;
952 ++column_index) {
953 const MatStencil column = column_stencils[column_index];
954 if (!(column.i >= ghost_starts[0] &&
955 column.i < ghost_starts[0] + ghost_sizes[0] &&
956 column.j >= ghost_starts[1] &&
957 column.j < ghost_starts[1] + ghost_sizes[1] &&
958 column.k >= ghost_starts[2] &&
959 column.k < ghost_starts[2] + ghost_sizes[2])) {
960 ierr = PetscError(comm, __LINE__, PETSC_FUNCTION_NAME, __FILE__,
961 PETSC_ERR_ARG_OUTOFRANGE,
962 PETSC_ERROR_INITIAL,
963 "Point-block column lies outside the DMDA ghost stencil.");
964 goto cleanup;
965 }
966 column_locals[column_index] = column.c + 3 * (
967 (column.i - ghost_starts[0]) + ghost_sizes[0] * (
968 (column.j - ghost_starts[1]) + ghost_sizes[1] *
969 (column.k - ghost_starts[2])));
970 }
971 ierr = ISLocalToGlobalMappingApply(local_to_global, 1,
972 &row_local, &row);
973 if (ierr) goto cleanup;
974 ierr = ISLocalToGlobalMappingApply(local_to_global, column_count,
975 column_locals, columns);
976 if (ierr) goto cleanup;
977 if (!(row >= ownership_start && row < ownership_end)) {
978 ierr = PetscError(comm, __LINE__, PETSC_FUNCTION_NAME, __FILE__,
979 PETSC_ERR_PLIB, PETSC_ERROR_INITIAL,
980 "DMDA-mapped point-block row is not locally owned.");
981 goto cleanup;
982 }
983 for (PetscInt column_index = 0; column_index < column_count;
984 ++column_index) {
985 PetscBool duplicate = PETSC_FALSE;
986 for (PetscInt previous = 0; previous < column_index; ++previous)
987 if (columns[previous] == columns[column_index]) duplicate = PETSC_TRUE;
988 if (columns[column_index] < 0) {
989 ierr = PetscError(comm, __LINE__, PETSC_FUNCTION_NAME, __FILE__,
990 PETSC_ERR_PLIB, PETSC_ERROR_INITIAL,
991 "DMDA-mapped point-block column is invalid.");
992 goto cleanup;
993 }
994 if (duplicate) continue;
995 if (columns[column_index] >= ownership_start &&
996 columns[column_index] < ownership_end)
997 ++diagonal_nnz[row - ownership_start];
998 else
999 ++offdiagonal_nnz[row - ownership_start];
1000 }
1001 }
1002 }
1003 }
1004 }
1005
1006 ierr = MatCreateAIJ(comm, local_size, local_size, global_size, global_size,
1007 0, diagonal_nnz, 0, offdiagonal_nnz, &matrix);
1008 if (ierr) goto cleanup;
1009 ierr = MatSetBlockSize(matrix, 3); if (ierr) goto cleanup;
1010 ierr = MatSetLocalToGlobalMapping(matrix, local_to_global, local_to_global);
1011 if (ierr) goto cleanup;
1012 ierr = MatSetStencil(matrix, 3, ghost_sizes, ghost_starts, 3);
1013 if (ierr) goto cleanup;
1014 ierr = MatSetDM(matrix, user->fda); if (ierr) goto cleanup;
1015 ierr = MatSetOption(matrix, MAT_NEW_NONZERO_ALLOCATION_ERR, PETSC_TRUE);
1016 if (ierr) goto cleanup;
1017 ierr = PetscFree2(diagonal_nnz, offdiagonal_nnz); if (ierr) goto cleanup;
1018
1019 *preconditioning_matrix = matrix;
1020 matrix = NULL;
1021
1022cleanup:
1023 if (nvert) {
1024 cleanup_ierr = DMDAVecRestoreArrayRead(user->da, user->lNvert, &nvert);
1025 if (!ierr) ierr = cleanup_ierr;
1026 }
1027 cleanup_ierr = MatDestroy(&matrix); if (!ierr) ierr = cleanup_ierr;
1028 cleanup_ierr = PetscFree2(diagonal_nnz, offdiagonal_nnz);
1029 if (!ierr) ierr = cleanup_ierr;
1030 PetscFunctionReturn(ierr);
1031}
1032
1033#undef __FUNCT__
1034#define __FUNCT__ "MomentumNewtonKrylov_ApplyConstraints"
1035/**
1036 * @brief Replaces every non-independent residual row with an explicit equation.
1037 *
1038 * Which rows are non-independent is decided by ClassifyMomentumRow(), shared with
1039 * the residual path; only the action taken differs. Conditioned face-normal rows
1040 * use F=X-Uconditioned, unconditioned legacy dummy/tangential rows use F=X, and
1041 * periodic duplicates use Fdup=Xdup-Xrep. These equations prevent the zero Jacobian
1042 * rows that EnforceRHSBoundaryConditions()'s zeroing would produce in a matrix-free
1043 * Newton operator. Immersed, masked, TwoD, and interface rows are rejected before
1044 * this callback is installed.
1045 * @param ctx Active solve context.
1046 * @param X Unconditioned PETSc trial state.
1047 * @param F Residual vector to update in place.
1048 * @return PetscErrorCode 0 on success.
1049 */
1051 Vec X, Vec F)
1052{
1053 UserCtx *user = ctx->user;
1054 DMDALocalInfo info = user->info;
1055 Vec local_x = NULL;
1056 Cmpnts ***x = NULL, ***conditioned = NULL, ***f = NULL, ***lx = NULL;
1057 const PetscInt xs = info.xs, xe = info.xs + info.xm;
1058 const PetscInt ys = info.ys, ye = info.ys + info.ym;
1059 const PetscInt zs = info.zs, ze = info.zs + info.zm;
1060
1061 PetscFunctionBeginUser;
1062 PetscCall(DMGetLocalVector(user->fda, &local_x));
1063 PetscCall(DMGlobalToLocalBegin(user->fda, X, INSERT_VALUES, local_x));
1064 PetscCall(DMGlobalToLocalEnd(user->fda, X, INSERT_VALUES, local_x));
1065 PetscCall(DMDAVecGetArrayRead(user->fda, X, &x));
1066 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &conditioned));
1067 PetscCall(DMDAVecGetArray(user->fda, F, &f));
1068 PetscCall(DMDAVecGetArrayRead(user->fda, local_x, &lx));
1069
1070 for (PetscInt k = zs; k < ze; ++k) {
1071 for (PetscInt j = ys; j < ye; ++j) {
1072 for (PetscInt i = xs; i < xe; ++i) {
1073 PetscScalar *fv = &f[k][j][i].x;
1074 const PetscScalar *xv = &x[k][j][i].x;
1075 const PetscScalar *cv = &conditioned[k][j][i].x;
1076
1077 for (PetscInt component = 0; component < 3; ++component) {
1078 PetscInt ri, rj, rk;
1080 user, i, j, k, component, &ri, &rj, &rk);
1081 const PetscScalar *rv = &lx[rk][rj][ri].x;
1082
1083 if (row == MOM_ROW_FIXED_CONDITIONED) fv[component] = xv[component] - cv[component];
1084 else if (row == MOM_ROW_FIXED_HOMOGENEOUS) fv[component] = xv[component];
1085 else if (row == MOM_ROW_PERIODIC_DUPLICATE) fv[component] = xv[component] - rv[component];
1086 }
1087 }
1088 }
1089 }
1090
1091 PetscCall(DMDAVecRestoreArrayRead(user->fda, local_x, &lx));
1092 PetscCall(DMDAVecRestoreArray(user->fda, F, &f));
1093 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &conditioned));
1094 PetscCall(DMDAVecRestoreArrayRead(user->fda, X, &x));
1095 PetscCall(DMRestoreLocalVector(user->fda, &local_x));
1096 PetscFunctionReturn(PETSC_SUCCESS);
1097}
1098
1099#undef __FUNCT__
1100#define __FUNCT__ "MomentumNewtonKrylov_FormResidual"
1101/**
1102 * @brief Adapts a PETSc trial vector to the existing momentum residual path.
1103 *
1104 * A matrix-free SNES residual must be a deterministic function of the trial
1105 * vector X alone: F(X) may not depend on any state left by a previous residual
1106 * or MFFD evaluation, or finite-difference Jacobian actions become inconsistent.
1107 * To honor that contract this callback fully derives the Cartesian velocity
1108 * state (Ucat/lUcat) from X before the first boundary sweep -- see the inline
1109 * comment below for why ApplyBoundaryConditions()'s own internal reconstruction
1110 * is not sufficient for the first outlet pass.
1111 *
1112 * State invariants:
1113 * - On entry, X is the only input that determines the result; user->Ucont,
1114 * user->Ucat and their local ghosts are treated as scratch and are fully
1115 * overwritten from X.
1116 * - Supported handlers may overwrite flux totals and other diagnostics on
1117 * every call, but those values must not affect a later call at the same X;
1118 * the deterministic seed guarantees this.
1119 * - No histories, pressure, viscosity, or controller state advance here.
1120 *
1121 * Side effects: overwrites user->Ucont/lUcont, user->Ucat/lUcat, user->Rhs, the
1122 * boundary Ubcs targets, and boundary flux/area diagnostics; writes F.
1123 *
1124 * @param snes Calling nonlinear solver.
1125 * @param X Trial solution (read-only).
1126 * @param F Residual output.
1127 * @param vctx Pointer to MomentumNewtonKrylovContext.
1128 * @return PetscErrorCode 0 on success.
1129 */
1130static PetscErrorCode MomentumNewtonKrylov_FormResidual(SNES snes, Vec X, Vec F, void *vctx)
1131{
1133 UserCtx *user = ctx->user;
1134 const FieldId staggered_fields[] = {FIELD_ID_UCONT};
1135 const FieldId cell_fields[] = {FIELD_ID_UCAT};
1136
1137 PetscFunctionBeginUser;
1138 (void)snes;
1139 PetscCall(VecCopy(X, user->Ucont));
1140 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, staggered_fields));
1141
1142 /* Deterministic pre-boundary Cartesian seed. Establish the full Ucat/lUcat
1143 * state from the current X before any boundary handler runs:
1144 *
1145 * X -> Ucont/lUcont -> Ucat -> periodic Ucat -> lUcat -> boundaries
1146 *
1147 * Why this is required, and why it is NOT redundant with the reconstruction
1148 * already performed inside ApplyBoundaryConditions():
1149 *
1150 * 1. A matrix-free SNES residual must be a deterministic function of X. If
1151 * the Cartesian state is left over from a previous residual/MFFD call,
1152 * F(X) becomes history dependent and the finite-difference Jacobian
1153 * action Jv = (F(X+hv)-F(X))/h is invalidated.
1154 * 2. The conservation-outlet handler reads lUcat during the FIRST boundary
1155 * sweep (it measures the uncorrected outflow and builds the outlet
1156 * profile from the Cartesian field). Without this seed it would read the
1157 * stale lUcat from the preceding evaluation.
1158 * 3. ApplyBoundaryConditions() does reconstruct Ucat/lUcat, but only AFTER
1159 * each handler sweep (Contra2Cart runs after BoundarySystem_ExecuteStep
1160 * within every pass). Those internal updates therefore prepare passes 2
1161 * and 3 -- they cannot prepare the very first outlet read of pass 1.
1162 * 4. Contra2Cart() rebuilds the global Ucat interior from the current
1163 * lUcont, but it does not by itself refresh lUcat (nor lUcont; that was
1164 * done by the SynchronizePeriodicStaggeredFields call above).
1165 * 5. SynchronizePeriodicCellFields(FIELD_ID_UCAT) must run before the ghost
1166 * scatter so periodic duplicate planes are finalized consistently (it is
1167 * a no-op when no direction is periodic, as on the straight duct).
1168 * 6. UpdateLocalGhosts(FIELD_ID_UCAT) is required because the outlet handler reads
1169 * lUcat -- the local ghosted vector -- not merely the global Ucat.
1170 *
1171 * Do NOT "simplify" this to a bare Contra2Cart(user), and do NOT delete it
1172 * as apparently redundant with ApplyBoundaryConditions(): the three internal
1173 * boundary passes remain necessary (they refresh the Cartesian state after
1174 * each boundary correction), but only this sequence makes pass 1's input a
1175 * deterministic function of X. */
1176 PetscCall(Contra2Cart(user));
1177 PetscCall(SynchronizePeriodicCellFields(user, 1, cell_fields));
1178 PetscCall(UpdateLocalGhosts(user, FIELD_ID_UCAT));
1179
1180 PetscCall(ApplyBoundaryConditions(user));
1181 PetscCall(ComputeTotalResidual(user));
1182 PetscCall(VecCopy(user->Rhs, F));
1183 PetscCall(VecScale(F, -1.0));
1184 PetscCall(MomentumNewtonKrylov_ApplyConstraints(ctx, X, F));
1185 PetscFunctionReturn(PETSC_SUCCESS);
1186}
1187
1188#undef __FUNCT__
1189#define __FUNCT__ "MomentumSolver_NewtonKrylov"
1190/*
1191 * Runs one per-call matrix-free Newton--Krylov momentum solve. The public
1192 * header owns the rendered API contract; this definition retains the detailed
1193 * lifecycle and rollback behavior below.
1194 */
1195PetscErrorCode MomentumSolver_NewtonKrylov(UserCtx *user, IBMNodes *ibm, FSInfo *fsi)
1196{
1197 PetscErrorCode ierr = PETSC_SUCCESS, cleanup_ierr;
1198 SimCtx *simCtx;
1199 SNES snes = NULL;
1200 Vec solution = NULL, entry_backup = NULL;
1201 KSP ksp = NULL;
1202 PC pc = NULL;
1203 MomentumPreconditionerDescription preconditioner_description = {0};
1204 PetscBool restore_entry = PETSC_FALSE;
1205 PetscBool rhs_created = PETSC_FALSE;
1206 PetscBool solve_started = PETSC_FALSE;
1207 PetscBool committed = PETSC_FALSE;
1208 SNESConvergedReason reason = SNES_CONVERGED_ITERATING;
1209 PetscInt nonlinear_its = 0, function_evals = 0, linear_its = 0;
1210 PetscReal final_norm = PETSC_MAX_REAL;
1212 const FieldId staggered_fields[] = {FIELD_ID_UCONT};
1213
1214 PetscFunctionBeginUser;
1215 PetscCall(MomentumNewtonKrylov_Validate(user));
1216 PetscCheck(ibm == NULL && fsi == NULL, PETSC_COMM_WORLD, PETSC_ERR_SUP,
1217 "Newton Krylov version one does not accept IBM or FSI objects.");
1218 PetscCheck(user->Rhs == NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
1219 "Newton Krylov requires UserCtx::Rhs to be unallocated on entry.");
1220 simCtx = user->simCtx;
1221
1222 ierr = VecDuplicate(user->Ucont, &solution); if (ierr) goto cleanup;
1223 ierr = VecDuplicate(user->Ucont, &entry_backup); if (ierr) goto cleanup;
1224 ierr = VecDuplicate(user->Ucont, &user->Rhs); if (ierr) goto cleanup;
1225 rhs_created = PETSC_TRUE;
1226 ierr = SNESCreate(PetscObjectComm((PetscObject)user->Ucont), &snes); if (ierr) goto cleanup;
1227
1228 ierr = SynchronizePeriodicStaggeredFields(user, 1, staggered_fields); if (ierr) goto cleanup;
1229 ierr = ApplyBoundaryConditions(user); if (ierr) goto cleanup;
1230 ierr = VecCopy(user->Ucont, entry_backup); if (ierr) goto cleanup;
1231 restore_entry = PETSC_TRUE;
1232 ierr = VecCopy(user->Ucont, solution); if (ierr) goto cleanup;
1233
1234 ctx.user = user;
1235 ctx.snes = snes;
1237 &ctx.jacobian, &preconditioner_description); if (ierr) goto cleanup;
1238 ierr = SNESSetOptionsPrefix(snes, "mom_nk_"); if (ierr) goto cleanup;
1239 ierr = SNESSetType(snes, SNESNEWTONLS); if (ierr) goto cleanup;
1240 ierr = SNESSetDM(snes, user->fda); if (ierr) goto cleanup;
1241 ierr = SNESSetFunction(snes, NULL, MomentumNewtonKrylov_FormResidual, &ctx); if (ierr) goto cleanup;
1242 ierr = MomentumNewtonJacobian_Create(snes, &ctx.jacobian); if (ierr) goto cleanup;
1244 &preconditioner_description,
1245 &ctx.preconditioning_engine); if (ierr) goto cleanup;
1246 ierr = MomentumNewtonJacobian_Register(snes, &ctx.jacobian,
1247 &ctx.preconditioning_engine, &ctx); if (ierr) goto cleanup;
1248 ierr = SNESGetKSP(snes, &ksp); if (ierr) goto cleanup;
1249 ierr = KSPSetType(ksp, KSPGMRES); if (ierr) goto cleanup;
1250 ierr = KSPGetPC(ksp, &pc); if (ierr) goto cleanup;
1251 ierr = MomentumPreconditionerEngine_ConfigurePetscPC(&ctx.preconditioning_engine, pc); if (ierr) goto cleanup;
1252 ierr = SNESSetFromOptions(snes); if (ierr) goto cleanup;
1253 ierr = SNESMonitorSet(snes, MomentumNewtonKrylov_Monitor, &ctx, NULL); if (ierr) goto cleanup;
1254 ierr = KSPMonitorSet(ksp, MomentumNewtonKrylov_LinearMonitor, &ctx, NULL); if (ierr) goto cleanup;
1255 ierr = MomentumPreconditionerEngine_ValidatePetscPC(&ctx.preconditioning_engine, pc); if (ierr) goto cleanup;
1256
1258 "Newton Krylov Jacobian: finite_difference / matrix_free; "
1259 "Preconditioner: %s; PETSc Jacobian matrix type: MATMFFD; "
1260 "PETSc PC type: %s.\n",
1262 "none" : "frozen_momentum_jacobian / point_block",
1264
1266 solve_started = PETSC_TRUE;
1267 ierr = SNESSolve(snes, NULL, solution);
1268 if (ierr) goto cleanup;
1269 ierr = SNESGetConvergedReason(snes, &reason); if (ierr) goto cleanup;
1270 ierr = SNESGetIterationNumber(snes, &nonlinear_its); if (ierr) goto cleanup;
1271 ierr = SNESGetNumberFunctionEvals(snes, &function_evals); if (ierr) goto cleanup;
1272 ierr = SNESGetLinearSolveIterations(snes, &linear_its); if (ierr) goto cleanup;
1273 ierr = SNESGetFunctionNorm(snes, &final_norm); if (ierr) goto cleanup;
1274
1275 if (reason > 0) {
1276 ierr = VecCopy(solution, user->Ucont); if (ierr) goto cleanup;
1277 simCtx->mom_last_converged = PETSC_TRUE;
1278 } else {
1279 ierr = VecCopy(entry_backup, user->Ucont); if (ierr) goto cleanup;
1280 simCtx->mom_last_converged = PETSC_FALSE;
1281 }
1282 ierr = SynchronizePeriodicStaggeredFields(user, 1, staggered_fields); if (ierr) goto cleanup;
1283 ierr = ApplyBoundaryConditions(user); if (ierr) goto cleanup;
1284 restore_entry = PETSC_FALSE;
1285 committed = (PetscBool)(reason > 0);
1286
1288 "Newton Krylov momentum solve: reason=%s (%d), Newton iterations=%d, residual evaluations=%d, Krylov iterations=%d, final norm=%.6e, state=%s.\n",
1289 SNESConvergedReasons[reason], (PetscInt)reason, nonlinear_its, function_evals,
1290 linear_its, (double)final_norm, reason > 0 ? "committed" : "rolled back");
1291 if (reason <= 0) ierr = PETSC_ERR_CONV_FAILED;
1292
1293cleanup:
1294 /* A PETSc solve error can bypass the normal statistics path. Query whatever
1295 SNES retained without replacing the primary error so a failed attempt is
1296 still represented in the structured log. */
1297 if (solve_started && snes) {
1298 (void)SNESGetConvergedReason(snes, &reason);
1299 (void)SNESGetIterationNumber(snes, &nonlinear_its);
1300 (void)SNESGetNumberFunctionEvals(snes, &function_evals);
1301 (void)SNESGetLinearSolveIterations(snes, &linear_its);
1302 (void)SNESGetFunctionNorm(snes, &final_norm);
1303 }
1304 if (restore_entry && entry_backup) {
1305 cleanup_ierr = VecCopy(entry_backup, user->Ucont);
1306 if (!ierr) ierr = cleanup_ierr;
1307 cleanup_ierr = SynchronizePeriodicStaggeredFields(user, 1, staggered_fields);
1308 if (!ierr) ierr = cleanup_ierr;
1309 cleanup_ierr = ApplyBoundaryConditions(user);
1310 if (!ierr) ierr = cleanup_ierr;
1311 simCtx->mom_last_converged = PETSC_FALSE;
1312 committed = PETSC_FALSE;
1313 }
1314 if (ctx.history_file) {
1315 (void)fclose(ctx.history_file);
1316 ctx.history_file = NULL;
1317 }
1318 if (ctx.linear_history_file) {
1319 (void)fclose(ctx.linear_history_file);
1320 ctx.linear_history_file = NULL;
1321 }
1322 if (solve_started) {
1323 MomentumNewtonKrylov_WriteSummary(&ctx, reason, nonlinear_its, function_evals,
1324 linear_its, final_norm, committed);
1325 }
1326 if (rhs_created) {
1327 cleanup_ierr = VecDestroy(&user->Rhs);
1328 if (!ierr) ierr = cleanup_ierr;
1329 }
1330 cleanup_ierr = VecDestroy(&entry_backup); if (!ierr) ierr = cleanup_ierr;
1331 cleanup_ierr = VecDestroy(&solution); if (!ierr) ierr = cleanup_ierr;
1332 cleanup_ierr = MomentumPreconditionerEngine_Destroy(&ctx.preconditioning_engine); if (!ierr) ierr = cleanup_ierr;
1333 cleanup_ierr = MomentumNewtonJacobian_Destroy(&ctx.jacobian); if (!ierr) ierr = cleanup_ierr;
1334 cleanup_ierr = SNESDestroy(&snes); if (!ierr) ierr = cleanup_ierr;
1335 PetscFunctionReturn(ierr);
1336}
MomentumRowType
Classification of one staggered momentum row (location + component).
Definition Boundaries.h:252
@ MOM_ROW_FIXED_HOMOGENEOUS
Dummy/tangential row carrying no unknown at all.
Definition Boundaries.h:255
@ MOM_ROW_PHYSICAL
Independent unknown governed by the momentum equation.
Definition Boundaries.h:253
@ MOM_ROW_PERIODIC_DUPLICATE
Duplicate of a wrapped representative row (see ri, rj, rk).
Definition Boundaries.h:256
@ MOM_ROW_FIXED_CONDITIONED
Strong Dirichlet row; the value comes from ApplyBoundaryConditions().
Definition Boundaries.h:254
MomentumRowType ClassifyMomentumRow(UserCtx *user, PetscInt i, PetscInt j, PetscInt k, PetscInt component, PetscInt *ri, PetscInt *rj, PetscInt *rk)
Single source of truth for "which staggered momentum rows are unknowns".
Definition Boundaries.c:597
PetscBool MomentumRowIsSolidMasked(const PetscReal ***nvert, PetscInt i, PetscInt j, PetscInt k, PetscInt component)
Reports whether a momentum row is masked out by the solid-cell field.
Definition Boundaries.c:655
PetscErrorCode SynchronizePeriodicStaggeredFields(UserCtx *user, PetscInt num_fields, const FieldId field_ids[])
Synchronizes persistent component-staggered vector fields.
PetscErrorCode ApplyBoundaryConditions(UserCtx *user)
Main boundary-condition orchestrator executed during solver timestepping.
PetscErrorCode SynchronizePeriodicCellFields(UserCtx *user, PetscInt num_fields, const FieldId field_ids[])
Synchronizes periodic endpoint cells for a list of cell-centered fields.
FieldId
Compile-time identity for a catalogued Eulerian field.
@ FIELD_ID_UCAT
@ FIELD_ID_UCONT
#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 LOG(scope, level, fmt,...)
Logging macro for PETSc-based applications with scope control.
Definition logging.h:84
@ 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
static PetscReal FrozenMomentumJacobian_FaceEddyViscosity(const UserCtx *user, const PetscReal ***nu_t, PetscInt axis, PetscInt i, PetscInt j, PetscInt k)
Returns the face-averaged eddy viscosity the residual uses on one face.
static PetscErrorCode MomentumNewtonJacobian_Register(SNES snes, MomentumNewtonJacobian *jacobian, MomentumPreconditionerEngine *engine, MomentumNewtonKrylovContext *ctx)
Registers the application orchestration callback and both SNES matrices.
static const MomentumPreconditionerModelOps frozen_momentum_point_block_ops
const MomentumPreconditionerModelOps * model_ops
static PetscErrorCode MomentumPreconditionerEngine_ConfigurePetscPC(MomentumPreconditionerEngine *engine, PC pc)
Applies the validated model/structure-to-PETSc-PC mapping.
static PetscErrorCode FrozenMomentumJacobian_DescribePointBlock(UserCtx *user, MomentumPreconditionerDescription *description)
Describes the audited frozen-coefficient point-block model.
static PetscErrorCode MomentumNewtonKrylov_ApplyConstraints(MomentumNewtonKrylovContext *ctx, Vec X, Vec F)
Replaces every non-independent residual row with an explicit equation.
static PetscErrorCode MomentumPreconditionerEngine_CreateExactPointBlockMatrix(UserCtx *user, Mat *preconditioning_matrix)
Creates the frozen point-block P matrix with its exact scalar pattern.
static PetscErrorCode MomentumPreconditionerEngine_ValidatePetscPC(MomentumPreconditionerEngine *engine, PC pc)
Rejects raw options that select an unvalidated PETSc PC backend.
static PetscReal FrozenMomentumJacobian_MetricNormSquared(Cmpnts metric)
Returns the squared Euclidean norm of one metric vector.
MomentumPreconditionerStructure
@ MOM_NK_PC_STRUCTURE_POINT_BLOCK
@ MOM_NK_PC_STRUCTURE_NONE
static PetscErrorCode MomentumNewtonKrylov_FormJacobian(SNES snes, Vec current_solution, Mat jacobian_operator, Mat preconditioning_matrix, void *vctx)
Updates the Jacobian and then assembles any separate preconditioning matrix.
MomentumPreconditionerDescription description
static PetscErrorCode MomentumPreconditionerEngine_Create(UserCtx *user, Mat jacobian_operator, const MomentumPreconditionerDescription *requested, MomentumPreconditionerEngine *engine)
Validates a model/structure and creates or aliases its matrix.
static void MomentumNewtonKrylov_WriteSummary(const MomentumNewtonKrylovContext *ctx, SNESConvergedReason reason, PetscInt nonlinear_its, PetscInt function_evals, PetscInt linear_its, PetscReal final_norm, PetscBool committed)
Appends one rank-zero structured Newton result for a physical step.
MomentumNewtonFiniteDifferenceMode finite_difference_mode
MomentumNewtonJacobianType type
static PetscErrorCode MomentumNewtonKrylov_ReadLinearizationConfig(MomentumNewtonJacobian *jacobian, MomentumPreconditionerDescription *description)
Reads application-owned Jacobian and preconditioner mathematics.
static PetscErrorCode MomentumPreconditionerEngine_Assemble(MomentumPreconditionerEngine *engine, UserCtx *user, Vec current_solution)
Runs model insertion, common row handling, and final assembly.
static PetscErrorCode MomentumNewtonJacobian_Destroy(MomentumNewtonJacobian *jacobian)
Destroys a partially or fully created Jacobian operator.
static void FrozenMomentumJacobian_PointBlock(const UserCtx *user, const Cmpnts ***ucont, const Cmpnts ***csi, const Cmpnts ***eta, const Cmpnts ***zet, const PetscReal ***aj, const PetscReal ***nu_t, PetscInt i, PetscInt j, PetscInt k, PetscScalar block[9])
Returns the frozen-momentum point block for the current residual convention.
static void MomentumNewtonKrylov_OpenHistory(MomentumNewtonKrylovContext *ctx)
Opens the optional rank-zero Newton iteration-history file.
static PetscErrorCode MomentumNewtonKrylov_LinearMonitor(KSP ksp, PetscInt iteration, PetscReal norm, void *ctx)
Writes the effective KSP tolerance and PETSc-reported norm for each inner iteration.
static PetscErrorCode MomentumNewtonJacobian_Create(SNES snes, MomentumNewtonJacobian *jacobian)
Creates the selected Jacobian operator; currently PETSc MFFD only.
MomentumPreconditionerStructure structure
static PetscErrorCode FrozenMomentumJacobian_AssemblePointBlocks(UserCtx *user, Vec current_solution, Mat preconditioning_matrix)
Inserts only the audited interior frozen-momentum point blocks.
MomentumNewtonJacobianType
@ MOM_NK_JACOBIAN_FINITE_DIFFERENCE
static PetscErrorCode MomentumNewtonJacobian_Update(SNES snes, Vec current_solution, MomentumNewtonJacobian *jacobian)
Updates the matrix-free finite-difference operator base.
static PetscErrorCode MomentumNewtonKrylov_Validate(UserCtx *user)
Rejects configurations outside the audited version-one feature set.
static PetscErrorCode MomentumPreconditionerEngine_Destroy(MomentumPreconditionerEngine *engine)
Destroys only a separately owned preconditioning matrix.
MomentumPreconditionerEngine preconditioning_engine
static PetscErrorCode MomentumNewtonKrylov_FormResidual(SNES snes, Vec X, Vec F, void *ctx)
Adapts a PETSc trial vector to the existing momentum residual path.
static MomentumRowType MomentumNewtonKrylov_ClassifyRow(UserCtx *user, const PetscReal ***nvert, PetscInt i, PetscInt j, PetscInt k, PetscInt component, PetscInt *ri, PetscInt *rj, PetscInt *rk)
Row classification with solid-cell masking folded in.
MomentumNewtonFiniteDifferenceMode
@ MOM_NK_FD_MODE_MATRIX_FREE
static PetscErrorCode MomentumNewtonKrylov_Monitor(SNES snes, PetscInt iteration, PetscReal norm, void *ctx)
Captures SNES iteration norms and optionally writes PICurv history rows.
static PetscErrorCode MomentumPreconditionerEngine_ApplyConstraintRows(UserCtx *user, Mat preconditioning_matrix)
Inserts all common fixed, homogeneous, and periodic-duplicate rows.
MomentumPreconditionerModel
@ MOM_NK_PC_MODEL_FROZEN_MOMENTUM_JACOBIAN
@ MOM_NK_PC_MODEL_NONE
PetscReal MomentumBDFCoefficient(SimCtx *simCtx)
Returns the BDF physical-time coefficient a0 for the current step.
PetscErrorCode ComputeTotalResidual(UserCtx *user)
Computes the shared spatial-plus-BDF momentum residual in user->Rhs.
PetscErrorCode Contra2Cart(UserCtx *user)
Reconstructs Cartesian velocity (Ucat) at cell centers from contravariant velocity (Ucont) defined on...
Definition setup.c:3316
PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:2505
PetscErrorCode(* Describe)(UserCtx *, MomentumPreconditionerDescription *)
PetscErrorCode(* AssembleInterior)(UserCtx *, Vec, Mat)
PetscBool mom_nk_monitor_history
Definition variables.h:919
PetscInt movefsi
Definition variables.h:896
@ INLET
Definition variables.h:321
@ OUTLET
Definition variables.h:320
@ PERIODIC
Definition variables.h:323
@ WALL
Definition variables.h:317
PetscBool continueMode
Definition variables.h:881
PetscInt moveframe
Definition variables.h:897
PetscInt TwoD
Definition variables.h:897
PetscMPIInt rank
Definition variables.h:867
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:1096
PetscInt block_number
Definition variables.h:958
Vec lNvert
Definition variables.h:1111
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1074
PetscBool mom_last_converged
Definition variables.h:917
@ BC_HANDLER_PERIODIC_GEOMETRIC
Definition variables.h:347
@ BC_HANDLER_INLET_PARABOLIC
Definition variables.h:340
@ BC_HANDLER_INLET_CONSTANT_VELOCITY
Definition variables.h:339
@ BC_HANDLER_PERIODIC_DRIVEN_INITIAL_FLUX
Definition variables.h:350
@ BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX
Definition variables.h:349
@ BC_HANDLER_INLET_PROFILE_FROM_FILE
Definition variables.h:341
@ BC_HANDLER_WALL_NOSLIP
Definition variables.h:336
@ BC_HANDLER_OUTLET_CONSERVATION
Definition variables.h:345
PetscReal ren
Definition variables.h:911
BCHandlerType handler_type
Definition variables.h:400
PetscInt _this
Definition variables.h:1089
PetscReal dt
Definition variables.h:879
Vec Ucont
Definition variables.h:1111
PetscInt StartStep
Definition variables.h:874
PetscInt rotatefsi
Refused at setup: immersed boundaries and moving bodies are not implemented.
Definition variables.h:896
PetscScalar x
Definition variables.h:122
char log_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:887
Vec lNu_t
Definition variables.h:1154
PetscScalar z
Definition variables.h:122
Vec lUcont
Definition variables.h:1111
PetscInt step
Definition variables.h:872
DMDALocalInfo info
Definition variables.h:1083
PetscScalar y
Definition variables.h:122
PetscInt les
Active LES closure; an LESModelType value.
Definition variables.h:986
Vec Nvert
Definition variables.h:1111
BCType mathematical_type
Definition variables.h:399
PetscInt rotateframe
moveframe/rotateframe are refused at setup.
Definition variables.h:897
PetscInt immersed
Definition variables.h:896
BCFace
Identifies the six logical faces of a structured computational block.
Definition variables.h:292
@ BC_FACE_NEG_X
Definition variables.h:293
@ BC_FACE_POS_Z
Definition variables.h:295
@ BC_FACE_POS_Y
Definition variables.h:294
@ BC_FACE_NEG_Z
Definition variables.h:295
@ BC_FACE_POS_X
Definition variables.h:293
@ BC_FACE_NEG_Y
Definition variables.h:294
Holds the complete configuration for one of the six boundary faces.
Definition variables.h:397
A 3D point or vector with PetscScalar components.
Definition variables.h:121
Holds all data related to the state and motion of a body in FSI.
Definition variables.h:508
Represents a collection of nodes forming a surface for the IBM.
Definition variables.h:435
The master context for the entire simulation.
Definition variables.h:864
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1071
double nu_t(double yplus)
Computes turbulent eddy viscosity ratio (ν_t / ν)