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
50 SNES snes;
54 PetscReal initial_norm;
55 /* These objects are owned by the solve context, which MomentumSolver_NewtonKrylov() builds
56 * on its first call and keeps for the run (UserCtx::momentumNewtonKrylov): nothing they are
57 * built from changes between steps. The point-block pattern follows Nvert, which is fixed
58 * for the run; an immersed boundary that moves would need it rebuilt. */
63
64static PetscErrorCode MomentumNewtonKrylov_Validate(UserCtx *user);
65static PetscErrorCode MomentumNewtonKrylov_FormResidual(SNES snes, Vec X, Vec F, void *ctx);
66static PetscErrorCode MomentumNewtonKrylov_Monitor(SNES snes, PetscInt iteration,
67 PetscReal norm, void *ctx);
68static PetscErrorCode MomentumNewtonKrylov_LinearMonitor(KSP ksp, PetscInt iteration,
69 PetscReal norm, void *ctx);
72 SNESConvergedReason reason,
73 PetscInt nonlinear_its,
74 PetscInt function_evals,
75 PetscInt linear_its,
76 PetscReal final_norm,
77 PetscBool committed);
79 Vec X, Vec F);
82static PetscErrorCode MomentumNewtonKrylov_FormJacobian(SNES snes, Vec current_solution,
83 Mat jacobian_operator, Mat preconditioning_matrix, void *vctx);
85 UserCtx *user, Vec current_solution, Mat preconditioning_matrix);
87 UserCtx *user, Mat *preconditioning_matrix);
88
89/**
90 * @brief Captures SNES iteration norms and optionally writes PICurv history rows.
91 * @details SNES supplies the already-computed norm, so this monitor never causes
92 * an additional nonlinear residual evaluation. PETSc monitors selected through
93 * `-mom_nk_snes_monitor` remain independent and may run alongside this callback.
94 */
95static PetscErrorCode MomentumNewtonKrylov_Monitor(SNES snes, PetscInt iteration,
96 PetscReal norm, void *vctx)
97{
99 SimCtx *simCtx = ctx->user->simCtx;
100
101 (void)snes;
102 PetscFunctionBeginUser;
104 if (iteration == 0 && !ctx->have_initial_norm) {
105 ctx->initial_norm = norm;
106 ctx->have_initial_norm = PETSC_TRUE;
107 }
108 if (ctx->history_file) {
109 (void)fprintf(ctx->history_file, "%d,%d,%d,%.16e\n",
110 (int)simCtx->step, (int)ctx->user->_this, (int)iteration,
111 (double)norm);
112 (void)fflush(ctx->history_file);
113 }
115 PetscFunctionReturn(PETSC_SUCCESS);
116}
117
118/**
119 * @brief Writes the effective KSP tolerance and PETSc-reported norm for each inner iteration.
120 * @details The tolerance is queried after SNES has applied any inexact-Newton forcing
121 * update, so the log distinguishes changing Eisenstat--Walker requests from changing
122 * linear convergence behavior.
123 */
124static PetscErrorCode MomentumNewtonKrylov_LinearMonitor(KSP ksp, PetscInt iteration,
125 PetscReal norm, void *vctx)
126{
128 PetscInt newton_iteration = -1;
129 PetscReal relative_tolerance = 0.0;
130
131 PetscFunctionBeginUser;
133 if (!ctx->linear_history_file) { PROFILE_HELPER_END; PetscFunctionReturn(PETSC_SUCCESS); }
134 PetscCall(SNESGetIterationNumber(ctx->snes, &newton_iteration));
135 PetscCall(KSPGetTolerances(ksp, &relative_tolerance, NULL, NULL, NULL));
136 (void)fprintf(ctx->linear_history_file, "%d,%d,%d,%d,%.16e,%.16e\n",
137 (int)ctx->user->simCtx->step, (int)ctx->user->_this,
138 (int)newton_iteration, (int)iteration,
139 (double)relative_tolerance, (double)norm);
140 (void)fflush(ctx->linear_history_file);
142 PetscFunctionReturn(PETSC_SUCCESS);
143}
144
145/**
146 * @brief Opens one of the block's Newton CSV records on rank 0, or returns NULL.
147 * @details PicurvOpenRunCsv() owns the lifecycle: a fresh run's first open truncates the file,
148 * the header is written to an empty file, and a continued run's first open marks the
149 * seam. A failure only warns: these files are diagnostics, and must not change the
150 * solve's error behaviour.
151 */
152static FILE *MomentumNewtonKrylov_OpenCsv(const MomentumNewtonKrylovContext *ctx, const char *kind, const char *header)
153{
155 SimCtx *simCtx = ctx->user->simCtx;
156 char name[128];
157 FILE *file = NULL;
158
159 if (PetscSNPrintf(name, sizeof(name), "Momentum_Solver_Newton_Krylov_%s_Block_%d.csv", kind, (int)ctx->user->_this) ||
160 PicurvOpenRunCsv(simCtx, simCtx->log_dir, name, header, PICURV_CSV_TRUNCATE_ON_FRESH, PETSC_FALSE, &file)) {
161 file = NULL;
162 }
164 return file;
165}
166
167/** @brief Opens the optional rank-zero Newton iteration-history files (per Newton and per Krylov iteration). */
169{
171 SimCtx *simCtx = ctx->user->simCtx;
172
173 if (!simCtx->mom_nk_monitor_history || simCtx->rank != 0) { PROFILE_FUNCTION_END; return; }
174 ctx->history_file = MomentumNewtonKrylov_OpenCsv(ctx, "History", "step,block,newton,nonlinear_norm");
175 if (!ctx->history_file) { PROFILE_FUNCTION_END; return; }
176 ctx->linear_history_file = MomentumNewtonKrylov_OpenCsv(ctx, "Linear_History",
177 "step,block,newton,krylov,requested_rtol,reported_residual_norm");
179}
180
181/**
182 * @brief Appends one rank-zero structured Newton result for a physical step.
183 * @details File failures are deliberately diagnostic-only: rollback and PETSc
184 * cleanup must retain their original error behavior.
185 */
187 SNESConvergedReason reason,
188 PetscInt nonlinear_its,
189 PetscInt function_evals,
190 PetscInt linear_its,
191 PetscReal final_norm,
192 PetscBool committed)
193{
195 SimCtx *simCtx = ctx->user->simCtx;
196 const char *reason_name;
197 FILE *file;
198
199 if (simCtx->rank != 0) { PROFILE_FUNCTION_END; return; }
200 file = MomentumNewtonKrylov_OpenCsv(ctx, "Summary",
201 "step,block,solver,jacobian,preconditioner,reason,reason_code,newton,evals,krylov,"
202 "initial_norm,final_norm,state");
203 if (!file) { PROFILE_FUNCTION_END; return; }
204 reason_name = reason == SNES_CONVERGED_ITERATING
205 ? "SNES_CONVERGED_ITERATING" : SNESConvergedReasons[reason];
206 (void)fprintf(file, "%d,%d,Newton Krylov,finite_difference / matrix_free,%s,%s,%d,%d,%d,%d,",
207 (int)simCtx->step, (int)ctx->user->_this,
209 "none" : "frozen_momentum_jacobian / point_block",
210 reason_name, (int)reason,
211 (int)nonlinear_its, (int)function_evals, (int)linear_its);
212 if (ctx->have_initial_norm) (void)fprintf(file, "%.16e", (double)ctx->initial_norm);
213 (void)fprintf(file, ",%.16e,%s\n", (double)final_norm,
214 !committed ? "rolled_back" :
215 simCtx->mom_last_converged ? "committed" : "committed_unconverged");
216 (void)fclose(file);
218}
219
220/**
221 * @brief Rejects configurations outside the audited version-one feature set.
222 * @param user Single-block momentum context to validate.
223 * @return PetscErrorCode 0 when the configuration is supported.
224 */
225static PetscErrorCode MomentumNewtonKrylov_Validate(UserCtx *user)
226{
227 SimCtx *simCtx;
228 PetscReal mask_max = 0.0;
229 PetscInt velocity_dof = 0;
230
231 PetscFunctionBeginUser;
233 PetscCheck(user != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
234 "Newton Krylov requires a non-NULL UserCtx.");
235 simCtx = user->simCtx;
236 PetscCheck(simCtx != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
237 "Newton Krylov requires UserCtx::simCtx.");
238 PetscCall(DMDAGetInfo(user->fda, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
239 &velocity_dof, NULL, NULL, NULL, NULL, NULL));
240 PetscCheck(velocity_dof == 3, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
241 "Newton Krylov requires a three-component velocity DMDA (got dof=%" PetscInt_FMT ").",
242 velocity_dof);
243 PetscCheck(simCtx->block_number == 1, PETSC_COMM_WORLD, PETSC_ERR_SUP,
244 "Newton Krylov version one supports exactly one block (got %" PetscInt_FMT ").",
245 simCtx->block_number);
246 PetscCheck(!simCtx->immersed, PETSC_COMM_WORLD, PETSC_ERR_SUP,
247 "Newton Krylov version one does not support immersed boundaries.");
248 PetscCheck(!simCtx->movefsi && !simCtx->rotatefsi, PETSC_COMM_WORLD, PETSC_ERR_SUP,
249 "Newton Krylov version one does not support moving or rotating bodies/FSI.");
250 PetscCheck(!simCtx->moveframe && !simCtx->rotateframe, PETSC_COMM_WORLD, PETSC_ERR_SUP,
251 "Newton Krylov version one does not support moving or rotating reference frames.");
252 for (PetscInt face = 0; face < 6; ++face) {
253 const BoundaryFaceConfig *cfg = &user->boundary_faces[face];
254 PetscBool supported = PETSC_FALSE;
255
256 switch (cfg->handler_type) {
259 /* As on an inlet, its velocity is refreshed once per step in PreStep. */
261 supported = (PetscBool)(cfg->mathematical_type == WALL);
262 break;
263 /* The inlet's velocity is refreshed once per step in PreStep, so every
264 * residual evaluation within the step sees the same boundary value. */
266 supported = (PetscBool)(cfg->mathematical_type == INLET);
267 break;
269 supported = (PetscBool)(cfg->mathematical_type == OUTLET);
270 break;
272 /* Both driven handlers freeze their correction once per timestep in
273 * PreStep, which the Newton solve never re-enters, so the momentum
274 * source stays constant across every residual evaluation. The paired
275 * face checks below still apply. */
278 supported = (PetscBool)(cfg->mathematical_type == PERIODIC);
279 break;
280 default:
281 supported = PETSC_FALSE;
282 break;
283 }
284 PetscCheck(supported, PETSC_COMM_WORLD, PETSC_ERR_SUP,
285 "Newton Krylov version one does not support boundary face %" PetscInt_FMT " with mathematical type %" PetscInt_FMT " and handler %" PetscInt_FMT ".",
286 face, (PetscInt)cfg->mathematical_type, (PetscInt)cfg->handler_type);
287 }
288
291 PETSC_COMM_WORLD, PETSC_ERR_SUP, "Newton Krylov requires paired x-periodic faces.");
294 PETSC_COMM_WORLD, PETSC_ERR_SUP, "Newton Krylov requires paired y-periodic faces.");
297 PETSC_COMM_WORLD, PETSC_ERR_SUP, "Newton Krylov requires paired z-periodic faces.");
298
299 PetscCheck(user->Nvert != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
300 "Newton Krylov requires the cell-mask vector Nvert.");
301 /* Solid cells no longer disqualify the solver: MomentumRowIsSolidMasked() gives
302 their rows the same constrained treatment as any other row carrying no unknown.
303 The immersed-boundary method itself remains unsupported above, because its
304 velocity reconstruction does not run inside this solver's residual. */
305 PetscCall(VecMax(user->Nvert, NULL, &mask_max));
306 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Newton Krylov solid-cell mask maximum Nvert=%g.\n",
307 (double)mask_max);
309 PetscFunctionReturn(PETSC_SUCCESS);
310}
311
312/**
313 * @brief Reads application-owned Jacobian and preconditioner mathematics.
314 */
317{
318 char type[48] = "finite_difference";
319 char finite_difference_mode[32] = "matrix_free";
320 char model[48] = "none";
321 char structure[32] = "none";
322 PetscBool set = PETSC_FALSE, match = PETSC_FALSE;
323
324 PetscFunctionBeginUser;
326 PetscCheck(jacobian != NULL && description != NULL, PETSC_COMM_SELF,
327 PETSC_ERR_ARG_NULL, "Newton Krylov linearization configuration is NULL.");
328 PetscCall(PetscOptionsGetString(NULL, NULL, "-mom_nk_jacobian_type",
329 type, sizeof(type), &set));
330 PetscCall(PetscStrcasecmp(type, "finite_difference", &match));
331 PetscCheck(match, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
332 "-mom_nk_jacobian_type must be 'finite_difference' (got '%s').", type);
334 PetscCall(PetscOptionsGetString(NULL, NULL, "-mom_nk_jacobian_fd_mode",
335 finite_difference_mode,
336 sizeof(finite_difference_mode), &set));
337 PetscCall(PetscStrcasecmp(finite_difference_mode, "matrix_free", &match));
338 PetscCheck(match, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
339 "-mom_nk_jacobian_fd_mode must be 'matrix_free' (got '%s').",
340 finite_difference_mode);
342
343 PetscCall(PetscOptionsGetString(NULL, NULL, "-mom_nk_preconditioner_model",
344 model, sizeof(model), &set));
345 PetscCall(PetscStrcasecmp(model, "none", &match));
346 if (match) description->model = MOM_NK_PC_MODEL_NONE;
347 if (!match) {
348 PetscCall(PetscStrcasecmp(model, "frozen_momentum_jacobian", &match));
349 if (match) description->model = MOM_NK_PC_MODEL_FROZEN_MOMENTUM_JACOBIAN;
350 }
351 PetscCheck(match, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
352 "-mom_nk_preconditioner_model must be 'none' or "
353 "'frozen_momentum_jacobian' (got '%s').", model);
354 PetscCall(PetscOptionsGetString(NULL, NULL, "-mom_nk_preconditioner_structure",
355 structure, sizeof(structure), &set));
356 PetscCall(PetscStrcasecmp(structure, "none", &match));
357 if (match) description->structure = MOM_NK_PC_STRUCTURE_NONE;
358 if (!match) {
359 PetscCall(PetscStrcasecmp(structure, "point_block", &match));
360 if (match) description->structure = MOM_NK_PC_STRUCTURE_POINT_BLOCK;
361 }
362 PetscCheck(match, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
363 "-mom_nk_preconditioner_structure must be 'none' or 'point_block' "
364 "(got '%s').", structure);
365 PetscCheck((description->model == MOM_NK_PC_MODEL_NONE &&
366 description->structure == MOM_NK_PC_STRUCTURE_NONE) ||
369 PETSC_COMM_WORLD, PETSC_ERR_SUP,
370 "Unsupported Newton Krylov preconditioner model/structure combination: "
371 "model='%s', structure='%s'.", model, structure);
373 PetscFunctionReturn(PETSC_SUCCESS);
374}
375
376/**
377 * @brief Row classification with solid-cell masking folded in.
378 *
379 * A masked row's residual is identically zero for every X, so it has a zero Jacobian
380 * row and a zero column and its unknown is undetermined. It therefore needs the same
381 * identity treatment as any other row carrying no unknown. Checked after the boundary
382 * classification so a periodic duplicate keeps its representative column.
383 */
385 UserCtx *user, const PetscReal ***nvert, PetscInt i, PetscInt j, PetscInt k,
386 PetscInt component, PetscInt *ri, PetscInt *rj, PetscInt *rk)
387{
389 const MomentumRowType type = ClassifyMomentumRow(user, i, j, k, component, ri, rj, rk);
390
391 if (type == MOM_ROW_PHYSICAL && MomentumRowIsSolidMasked(nvert, i, j, k, component))
394 return type;
395}
396
397/** @brief Returns the squared Euclidean norm of one metric vector. */
399{
400 return metric.x * metric.x + metric.y * metric.y + metric.z * metric.z;
401}
402
403/**
404 * @brief Returns the face-averaged eddy viscosity the residual uses on one face.
405 *
406 * The preconditioner has to reproduce the operator it preconditions, so the interior
407 * face average here tracks `ComputeViscous()`.
408 *
409 * The WALL branch below does not, and is retained only because it cannot currently
410 * change an assembled entry. It is unreachable by construction, not by configuration:
411 * `FrozenMomentumJacobian_PointBlock()` puts the eddy viscosity only on the three
412 * diagonal entries, and the row-at-a-time insertion in
413 * `FrozenMomentumJacobian_AssemblePointBlocks()` means row c carries only axis == c.
414 * The two coordinates that trigger the branch are exactly those
415 * `ClassifyMomentumRow()` reports as non-MOM_ROW_PHYSICAL, and only physical rows
416 * assemble a block; the WALL-on-a-periodic-axis loophole is closed by the pairing
417 * check in `BoundarySystem_Validate()`. Both rules state the same boundary
418 * fact: the wall-normal flux at a no-slip wall is not an unknown.
419 *
420 * On a wall-resolved face `ComputeViscous()` uses the molecular gradient flux with no eddy
421 * viscosity, which this branch matches. On a wall-modelled face it applies the wall
422 * stress `ApplyWallFunction()` stored, with no gradient flux at all: that flux depends on
423 * the reference cell, the second from the wall, through the law, and not on the cells
424 * either side of the face. Widening the stencil would make the branch reachable, and the
425 * block would then need the law's derivative with respect to the reference velocity in
426 * place of any viscosity here. Tracked as issue #8.
427 */
429 const UserCtx *user, const PetscReal ***nu_t, PetscInt axis,
430 PetscInt i, PetscInt j, PetscInt k)
431{
433 const BCFace neg_face[3] = {BC_FACE_NEG_X, BC_FACE_NEG_Y, BC_FACE_NEG_Z};
434 const BCFace pos_face[3] = {BC_FACE_POS_X, BC_FACE_POS_Y, BC_FACE_POS_Z};
435 const PetscInt coord[3] = {i, j, k};
436 const PetscInt size[3] = {user->info.mx, user->info.my, user->info.mz};
437 PetscReal neighbour;
438
439 if (nu_t == NULL) { PROFILE_HELPER_END; return 0.0; }
440 if ((BoundaryFaceHasWallShear(&user->boundary_faces[neg_face[axis]]) && coord[axis] == 0) ||
441 (BoundaryFaceHasWallShear(&user->boundary_faces[pos_face[axis]]) && coord[axis] == size[axis] - 2))
442 { PROFILE_HELPER_END; return 0.0; }
443
444 neighbour = (axis == 0) ? nu_t[k][j][i + 1]
445 : (axis == 1) ? nu_t[k][j + 1][i]
446 : nu_t[k + 1][j][i];
448 return 0.5 * (nu_t[k][j][i] + neighbour);
449}
450
451/** @brief Returns the frozen-momentum point block for the current residual convention. */
453 const Cmpnts ***ucont, const Cmpnts ***csi, const Cmpnts ***eta,
454 const Cmpnts ***zet, const PetscReal ***aj, const PetscReal ***nu_t,
455 PetscInt i, PetscInt j, PetscInt k, PetscScalar block[9])
456{
458 const SimCtx *simCtx = user->simCtx;
459 const PetscReal dtc = MomentumBDFCoefficient((SimCtx *)simCtx) / simCtx->dt;
460 const PetscReal molecular = simCtx->ren > 0.0 ? 1.0 / simCtx->ren : 0.0;
461 /* The residual diffuses with nu + nu_t, so the block must too; nu_t alone is the
462 eddy contribution. Omitting it left the preconditioner modelling a viscous
463 diagonal smaller than the operator's by the eddy-to-molecular ratio, which on a
464 developed LES is order one or more. */
465 const PetscReal nu_eff_i = molecular + FrozenMomentumJacobian_FaceEddyViscosity(user, nu_t, 0, i, j, k);
466 const PetscReal nu_eff_j = molecular + FrozenMomentumJacobian_FaceEddyViscosity(user, nu_t, 1, i, j, k);
467 const PetscReal nu_eff_k = molecular + FrozenMomentumJacobian_FaceEddyViscosity(user, nu_t, 2, i, j, k);
468 const PetscReal AJip = 0.5 * (aj[k][j][i] + aj[k][j][i + 1]);
469 const PetscReal AJjp = 0.5 * (aj[k][j][i] + aj[k][j + 1][i]);
470 const PetscReal AJkp = 0.5 * (aj[k][j][i] + aj[k + 1][j][i]);
471 const PetscReal g11ip = csi[k][j][i].x * csi[k][j][i].x +
472 csi[k][j][i].y * csi[k][j][i].y +
473 csi[k][j][i].z * csi[k][j][i].z;
474 const PetscReal g22ip = 0.25 * (
478 FrozenMomentumJacobian_MetricNormSquared(eta[k][j - 1][i + 1]));
479 const PetscReal g33ip = 0.25 * (
483 FrozenMomentumJacobian_MetricNormSquared(zet[k - 1][j][i + 1]));
484 const PetscReal g11jp = 0.25 * (
488 FrozenMomentumJacobian_MetricNormSquared(csi[k][j + 1][i - 1]));
489 const PetscReal g22jp = eta[k][j][i].x * eta[k][j][i].x +
490 eta[k][j][i].y * eta[k][j][i].y +
491 eta[k][j][i].z * eta[k][j][i].z;
492 const PetscReal g33jp = 0.25 * (
496 FrozenMomentumJacobian_MetricNormSquared(zet[k - 1][j + 1][i]));
497 const PetscReal g11kp = 0.25 * (
501 FrozenMomentumJacobian_MetricNormSquared(csi[k + 1][j][i - 1]));
502 const PetscReal g22kp = 0.25 * (
506 FrozenMomentumJacobian_MetricNormSquared(eta[k + 1][j - 1][i]));
507 const PetscReal g33kp = zet[k][j][i].x * zet[k][j][i].x +
508 zet[k][j][i].y * zet[k][j][i].y +
509 zet[k][j][i].z * zet[k][j][i].z;
510 const PetscReal U0jp = 0.25 * (ucont[k][j][i].x + ucont[k][j][i - 1].x +
511 ucont[k][j + 1][i].x + ucont[k][j + 1][i - 1].x);
512 const PetscReal U0kp = 0.25 * (ucont[k][j][i].x + ucont[k][j][i - 1].x +
513 ucont[k + 1][j][i].x + ucont[k + 1][j][i - 1].x);
514 const PetscReal U1ip = 0.25 * (ucont[k][j][i].y + ucont[k][j - 1][i].y +
515 ucont[k][j][i + 1].y + ucont[k][j - 1][i + 1].y);
516 const PetscReal U1kp = 0.25 * (ucont[k][j][i].y + ucont[k][j - 1][i].y +
517 ucont[k + 1][j][i].y + ucont[k + 1][j - 1][i].y);
518 const PetscReal U2ip = 0.25 * (ucont[k][j][i].z + ucont[k - 1][j][i].z +
519 ucont[k][j][i + 1].z + ucont[k - 1][j][i + 1].z);
520 const PetscReal U2jp = 0.25 * (ucont[k][j][i].z + ucont[k - 1][j][i].z +
521 ucont[k][j + 1][i].z + ucont[k - 1][j + 1][i].z);
522 PetscReal A[6][4] = {{0.0}};
523 PetscReal Su, Sv, Sw, nui, nuj, nuk;
524
525 A[0][0] = 0.125 * aj[k][j][i] * ucont[k][j][i].y;
526 A[0][1] = -0.125 * aj[k][j - 1][i] * ucont[k][j - 1][i].y;
527 A[0][2] = 0.125 * aj[k][j][i + 1] * ucont[k][j][i + 1].y;
528 A[0][3] = -0.125 * aj[k][j - 1][i + 1] * ucont[k][j - 1][i + 1].y;
529 A[1][0] = 0.125 * aj[k][j][i] * ucont[k][j][i].z;
530 A[1][1] = -0.125 * aj[k - 1][j][i] * ucont[k - 1][j][i].z;
531 A[1][2] = 0.125 * aj[k][j][i + 1] * ucont[k][j][i + 1].z;
532 A[1][3] = -0.125 * aj[k - 1][j][i + 1] * ucont[k - 1][j][i + 1].z;
533 A[2][0] = -0.125 * aj[k][j + 1][i - 1] * ucont[k][j + 1][i - 1].x;
534 A[2][1] = -0.125 * aj[k][j][i - 1] * ucont[k][j][i - 1].x;
535 A[2][2] = 0.125 * aj[k][j + 1][i] * ucont[k][j + 1][i].x;
536 A[2][3] = 0.125 * aj[k][j][i] * ucont[k][j][i].x;
537 A[3][0] = 0.125 * aj[k][j][i] * ucont[k][j][i].z;
538 A[3][1] = -0.125 * aj[k - 1][j][i] * ucont[k - 1][j][i].z;
539 A[3][2] = 0.125 * aj[k][j + 1][i] * ucont[k][j + 1][i].z;
540 A[3][3] = -0.125 * aj[k - 1][j + 1][i] * ucont[k - 1][j + 1][i].z;
541 A[4][0] = -0.125 * aj[k + 1][j][i - 1] * ucont[k + 1][j][i - 1].x;
542 A[4][1] = -0.125 * aj[k][j][i - 1] * ucont[k][j][i - 1].x;
543 A[4][2] = 0.125 * aj[k + 1][j][i] * ucont[k + 1][j][i].x;
544 A[4][3] = 0.125 * aj[k][j][i] * ucont[k][j][i].x;
545 A[5][0] = -0.125 * aj[k + 1][j - 1][i] * ucont[k + 1][j - 1][i].y;
546 A[5][1] = -0.125 * aj[k][j - 1][i] * ucont[k][j - 1][i].y;
547 A[5][2] = 0.125 * aj[k + 1][j][i] * ucont[k + 1][j][i].y;
548 A[5][3] = 0.125 * aj[k][j][i] * ucont[k][j][i].y;
549 Su = A[0][0] + A[0][1] + A[0][2] + A[0][3] +
550 A[1][0] + A[1][1] + A[1][2] + A[1][3];
551 Sv = A[2][0] + A[2][1] + A[2][2] + A[2][3] +
552 A[3][0] + A[3][1] + A[3][2] + A[3][3];
553 Sw = A[4][0] + A[4][1] + A[4][2] + A[4][3] +
554 A[5][0] + A[5][1] + A[5][2] + A[5][3];
555 nui = AJip * AJip * (g11ip + g22ip + g33ip) * nu_eff_i;
556 nuj = AJjp * AJjp * (g11jp + g22jp + g33jp) * nu_eff_j;
557 nuk = AJkp * AJkp * (g11kp + g22kp + g33kp) * nu_eff_k;
558
559 /* MomentumNewtonKrylov_FormResidual forms F = -R, fixing this block's sign. */
560 /* `block` is row-major: each row is one residual component and each column
561 is one same-cell Ucont component. */
562 block[0] = dtc + nui + Su; block[1] = 0.5 * AJjp * U0jp; block[2] = 0.5 * AJkp * U0kp;
563 block[3] = 0.5 * AJip * U1ip; block[4] = dtc + nuj + Sv; block[5] = 0.5 * AJkp * U1kp;
564 block[6] = 0.5 * AJip * U2ip; block[7] = 0.5 * AJjp * U2jp; block[8] = dtc + nuk + Sw;
566}
567
568/** @brief Describes the audited frozen-coefficient point-block model. */
570 UserCtx *user, MomentumPreconditionerDescription *description)
571{
572 PetscFunctionBeginUser;
574 (void)user;
575 PetscCheck(description != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
576 "Preconditioner description is NULL.");
579 description->block_size = 3;
580 description->stencil_width = 0;
582 PetscFunctionReturn(PETSC_SUCCESS);
583}
584
585/** @brief Inserts only the audited interior frozen-momentum point blocks. */
587 UserCtx *user, Vec current_solution, Mat preconditioning_matrix)
588{
589 DMDALocalInfo info = user->info;
590 Cmpnts ***ucont = NULL, ***csi = NULL, ***eta = NULL, ***zet = NULL;
591 PetscReal ***aj = NULL, ***nvert = NULL, ***nu_t = NULL;
592 SimCtx *simCtx = user->simCtx;
593 /* The eddy viscosity enters the preconditioner only when a turbulence model is
594 actually producing one; without it the field may not even be allocated. */
595 const PetscBool has_eddy_viscosity =
596 (PetscBool)(simCtx->les && user->lNu_t != NULL);
597 PetscErrorCode ierr = PETSC_SUCCESS, cleanup_ierr;
598
599 PetscFunctionBeginUser;
601 /*
602 * current_solution is the current SNES trial Ucont Vec: it is layout-compatible
603 * with user->fda/user->Ucont, but is not necessarily the canonical user->Ucont
604 * selected by UpdateLocalGhosts(FIELD_ID_UCONT). Scatter that trial Vec directly with
605 * user->fda so PETSc applies its MPI ownership, periodic topology, component
606 * ordering, and ghost mapping without canonical-field synchronization or mutation
607 * of current_solution. UpdateLocalGhosts additionally repairs the component-normal
608 * staggered buffers Uxi(i=-1,mx), Ueta(j=-1,my), and Uzeta(k=-1,mz); the audited
609 * point-block velocity stencil reads none of those planes, so the repair cannot
610 * change a coefficient. Revisit this choice and extend the periodic-localization
611 * tests if the model stencil is expanded to read any repaired normal buffer.
612 */
613 ierr = DMGlobalToLocalBegin(user->fda, current_solution, INSERT_VALUES, user->lUcont);
614 if (ierr) goto cleanup;
615 ierr = DMGlobalToLocalEnd(user->fda, current_solution, INSERT_VALUES, user->lUcont);
616 if (ierr) goto cleanup;
617 ierr = DMDAVecGetArrayRead(user->fda, user->lUcont, &ucont); if (ierr) goto cleanup;
618 ierr = DMDAVecGetArrayRead(user->fda, user->lCsi, &csi); if (ierr) goto cleanup;
619 ierr = DMDAVecGetArrayRead(user->fda, user->lEta, &eta); if (ierr) goto cleanup;
620 ierr = DMDAVecGetArrayRead(user->fda, user->lZet, &zet); if (ierr) goto cleanup;
621 ierr = DMDAVecGetArrayRead(user->da, user->lAj, &aj); if (ierr) goto cleanup;
622 ierr = DMDAVecGetArrayRead(user->da, user->lNvert, &nvert); if (ierr) goto cleanup;
623 if (has_eddy_viscosity) {
624 ierr = DMDAVecGetArrayRead(user->da, user->lNu_t, &nu_t); if (ierr) goto cleanup;
625 }
626 for (PetscInt k = info.zs; k < info.zs + info.zm; ++k) {
627 for (PetscInt j = info.ys; j < info.ys + info.ym; ++j) {
628 for (PetscInt i = info.xs; i < info.xs + info.xm; ++i) {
629 for (PetscInt component = 0; component < 3; ++component) {
630 MatStencil row = {.i = i, .j = j, .k = k, .c = component};
631 PetscInt ri, rj, rk;
633 user, (const PetscReal ***)nvert, i, j, k, component, &ri, &rj, &rk);
634 if (type == MOM_ROW_PHYSICAL) {
635 PetscScalar block[9];
636 MatStencil cols[3] = {
637 {.i = i, .j = j, .k = k, .c = 0},
638 {.i = i, .j = j, .k = k, .c = 1},
639 {.i = i, .j = j, .k = k, .c = 2}
640 };
641 FrozenMomentumJacobian_PointBlock(user, (const Cmpnts ***)ucont,
642 (const Cmpnts ***)csi, (const Cmpnts ***)eta, (const Cmpnts ***)zet,
643 (const PetscReal ***)aj, (const PetscReal ***)nu_t, i, j, k, block);
644 /* A TwoD-masked row is the time derivative alone in the residual,
645 so its block row is that derivative's diagonal. */
646 if (simCtx->TwoD == component + 1) {
647 for (PetscInt c = 0; c < 3; ++c)
648 block[3 * component + c] = (c == component)
649 ? MomentumBDFCoefficient(simCtx) / simCtx->dt : 0.0;
650 }
651 ierr = MatSetValuesStencil(preconditioning_matrix, 1, &row, 3, cols,
652 &block[3 * component], INSERT_VALUES);
653 if (ierr) goto cleanup;
654 }
655 }
656 }
657 }
658 }
659cleanup:
660 if (nu_t) { cleanup_ierr = DMDAVecRestoreArrayRead(user->da, user->lNu_t, &nu_t); if (!ierr) ierr = cleanup_ierr; }
661 if (nvert) { cleanup_ierr = DMDAVecRestoreArrayRead(user->da, user->lNvert, &nvert); if (!ierr) ierr = cleanup_ierr; }
662 if (aj) { cleanup_ierr = DMDAVecRestoreArrayRead(user->da, user->lAj, &aj); if (!ierr) ierr = cleanup_ierr; }
663 if (zet) { cleanup_ierr = DMDAVecRestoreArrayRead(user->fda, user->lZet, &zet); if (!ierr) ierr = cleanup_ierr; }
664 if (eta) { cleanup_ierr = DMDAVecRestoreArrayRead(user->fda, user->lEta, &eta); if (!ierr) ierr = cleanup_ierr; }
665 if (csi) { cleanup_ierr = DMDAVecRestoreArrayRead(user->fda, user->lCsi, &csi); if (!ierr) ierr = cleanup_ierr; }
666 if (ucont) { cleanup_ierr = DMDAVecRestoreArrayRead(user->fda, user->lUcont, &ucont); if (!ierr) ierr = cleanup_ierr; }
668 PetscFunctionReturn(ierr);
669}
670
671/** @brief Inserts all common fixed, homogeneous, and periodic-duplicate rows. */
673 UserCtx *user, Mat preconditioning_matrix)
674{
675 DMDALocalInfo info = user->info;
676 const PetscReal ***nvert = NULL;
677
678 PetscFunctionBeginUser;
680 PetscCall(DMDAVecGetArrayRead(user->da, user->lNvert, &nvert));
681 for (PetscInt k = info.zs; k < info.zs + info.zm; ++k) {
682 for (PetscInt j = info.ys; j < info.ys + info.ym; ++j) {
683 for (PetscInt i = info.xs; i < info.xs + info.xm; ++i) {
684 for (PetscInt component = 0; component < 3; ++component) {
685 PetscInt ri, rj, rk;
687 user, nvert, i, j, k, component, &ri, &rj, &rk);
688 if (type != MOM_ROW_PHYSICAL) {
689 MatStencil row = {.i = i, .j = j, .k = k, .c = component};
690 MatStencil columns[2] = {row, row};
691 PetscScalar values[2] = {1.0, -1.0};
692 PetscInt column_count = 1;
693 if (type == MOM_ROW_PERIODIC_DUPLICATE) {
694 columns[column_count++] = (MatStencil){
695 .i = ri, .j = rj, .k = rk, .c = component
696 };
697 }
698 PetscCall(MatSetValuesStencil(preconditioning_matrix, 1, &row,
699 column_count, columns, values,
700 INSERT_VALUES));
701 }
702 }
703 }
704 }
705 }
706 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lNvert, &nvert));
708 PetscFunctionReturn(PETSC_SUCCESS);
709}
710
715
716/** @brief Creates the selected Jacobian operator; currently PETSc MFFD only. */
717static PetscErrorCode MomentumNewtonJacobian_Create(SNES snes,
718 MomentumNewtonJacobian *jacobian)
719{
720 PetscFunctionBeginUser;
722 PetscCheck(jacobian->type == MOM_NK_JACOBIAN_FINITE_DIFFERENCE &&
724 PETSC_COMM_WORLD, PETSC_ERR_SUP,
725 "Unsupported Newton Krylov Jacobian type/finite-difference-mode combination.");
726 PetscCall(MatCreateSNESMF(snes, &jacobian->jacobian_operator));
727 PetscCall(MatSetOptionsPrefix(jacobian->jacobian_operator, "mom_nk_"));
728 PetscCall(PetscObjectSetName((PetscObject)jacobian->jacobian_operator,
729 "momentum_jacobian_finite_difference_matrix_free"));
731 PetscFunctionReturn(PETSC_SUCCESS);
732}
733
734/** @brief Updates the matrix-free finite-difference operator base. */
735static PetscErrorCode MomentumNewtonJacobian_Update(SNES snes, Vec current_solution,
736 MomentumNewtonJacobian *jacobian)
737{
738 PetscFunctionBeginUser;
740 PetscCall(MatMFFDComputeJacobian(snes, current_solution, jacobian->jacobian_operator,
741 jacobian->jacobian_operator, NULL));
743 PetscFunctionReturn(PETSC_SUCCESS);
744}
745
746/** @brief Registers the application orchestration callback and both SNES matrices. */
747static PetscErrorCode MomentumNewtonJacobian_Register(SNES snes,
750{
751 PetscFunctionBeginUser;
753 PetscCall(SNESSetJacobian(snes, jacobian->jacobian_operator,
757 PetscFunctionReturn(PETSC_SUCCESS);
758}
759
760/** @brief Destroys a partially or fully created Jacobian operator. */
762{
763 PetscFunctionBeginUser;
765 PetscCall(MatDestroy(&jacobian->jacobian_operator));
767 PetscFunctionReturn(PETSC_SUCCESS);
768}
769
770/** @brief Validates a model/structure and creates or aliases its matrix. */
772 Mat jacobian_operator, const MomentumPreconditionerDescription *requested,
774{
775 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 }
802 PetscFunctionReturn(PETSC_SUCCESS);
803}
804
805/** @brief Runs model insertion, common row handling, and final assembly. */
807 MomentumPreconditionerEngine *engine, UserCtx *user, Vec current_solution)
808{
809 PetscFunctionBeginUser;
811 if (engine->aliases_jacobian_operator) { PROFILE_FUNCTION_END; 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));
820 PetscFunctionReturn(PETSC_SUCCESS);
821}
822
823/** @brief Applies the validated model/structure-to-PETSc-PC mapping. */
825 MomentumPreconditionerEngine *engine, PC pc)
826{
827 PetscFunctionBeginUser;
829 PetscCall(PCSetType(pc, engine->petsc_pc_type));
831 PetscFunctionReturn(PETSC_SUCCESS);
832}
833
834/** @brief Rejects raw options that select an unvalidated PETSc PC backend. */
836 MomentumPreconditionerEngine *engine, PC pc)
837{
838 const char *actual_type = NULL;
839 PetscBool matches = PETSC_FALSE;
840
841 PetscFunctionBeginUser;
843 PetscCall(PCGetType(pc, &actual_type));
844 PetscCall(PetscStrcmp(actual_type, engine->petsc_pc_type, &matches));
845 PetscCheck(matches, PETSC_COMM_WORLD, PETSC_ERR_SUP,
846 "Newton Krylov preconditioner model/structure requires internal PETSc PC "
847 "type '%s', but raw option processing selected '%s'.",
848 engine->petsc_pc_type, actual_type ? actual_type : "(unset)");
850 PetscFunctionReturn(PETSC_SUCCESS);
851}
852
853/** @brief Destroys only a separately owned preconditioning matrix. */
856{
857 PetscFunctionBeginUser;
859 if (!engine->owns_preconditioning_matrix) engine->preconditioning_matrix = NULL;
860 PetscCall(MatDestroy(&engine->preconditioning_matrix));
861 engine->aliases_jacobian_operator = PETSC_FALSE;
862 engine->owns_preconditioning_matrix = PETSC_FALSE;
864 PetscFunctionReturn(PETSC_SUCCESS);
865}
866
867/** @brief Updates the Jacobian and then assembles any separate preconditioning matrix. */
868static PetscErrorCode MomentumNewtonKrylov_FormJacobian(SNES snes, Vec current_solution,
869 Mat jacobian_operator, Mat preconditioning_matrix, void *vctx)
870{
872 PetscFunctionBeginUser;
874 (void)jacobian_operator;
875 (void)preconditioning_matrix;
876 PetscCall(MomentumNewtonJacobian_Update(snes, current_solution, &ctx->jacobian));
878 &ctx->preconditioning_engine, ctx->user, current_solution));
880 PetscFunctionReturn(PETSC_SUCCESS);
881}
882
883/**
884 * @brief Creates the frozen point-block P matrix with its exact scalar pattern.
885 * @details Row and column ownership follows the velocity DMDA global vector.
886 * Physical rows reserve the three same-point components, fixed rows reserve
887 * only their diagonal, and periodic duplicate rows additionally reserve their
888 * wrapped representative. DMDA AO, local mapping, and stencil metadata are
889 * retained so the existing insertion paths keep their exact ordering.
890 */
892 UserCtx *user, Mat *preconditioning_matrix)
893{
894 DMDALocalInfo info;
895 ISLocalToGlobalMapping local_to_global = NULL;
896 Mat matrix = NULL;
897 MPI_Comm comm;
898 PetscInt local_size, global_size, ownership_start, ownership_end;
899 PetscInt *diagonal_nnz = NULL, *offdiagonal_nnz = NULL;
900 PetscInt ghost_starts[4] = {0, 0, 0, 0}, ghost_sizes[3] = {0, 0, 0};
901 const PetscReal ***nvert = NULL;
902 PetscErrorCode ierr = PETSC_SUCCESS, cleanup_ierr;
903
904 PetscFunctionBeginUser;
906 PetscCheck(preconditioning_matrix != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
907 "Point-block matrix output is NULL.");
908 *preconditioning_matrix = NULL;
909 comm = PetscObjectComm((PetscObject)user->fda);
910 info = user->info;
911 PetscCall(VecGetLocalSize(user->Ucont, &local_size));
912 PetscCall(VecGetSize(user->Ucont, &global_size));
913 PetscCall(VecGetOwnershipRange(user->Ucont, &ownership_start, &ownership_end));
914 PetscCheck(local_size == ownership_end - ownership_start, comm, PETSC_ERR_PLIB,
915 "Velocity ownership range does not match its local size.");
916 ierr = PetscCalloc2(local_size, &diagonal_nnz,
917 local_size, &offdiagonal_nnz); if (ierr) goto cleanup;
918 ierr = DMGetLocalToGlobalMapping(user->fda, &local_to_global); if (ierr) goto cleanup;
919 ghost_starts[0] = info.gxs; ghost_starts[1] = info.gys; ghost_starts[2] = info.gzs;
920 ghost_sizes[0] = info.gxm; ghost_sizes[1] = info.gym; ghost_sizes[2] = info.gzm;
921 ierr = DMDAVecGetArrayRead(user->da, user->lNvert, &nvert); if (ierr) goto cleanup;
922
923 for (PetscInt k = info.zs; k < info.zs + info.zm; ++k) {
924 for (PetscInt j = info.ys; j < info.ys + info.ym; ++j) {
925 for (PetscInt i = info.xs; i < info.xs + info.xm; ++i) {
926 for (PetscInt component = 0; component < 3; ++component) {
927 PetscInt ri, rj, rk, column_count;
928 MatStencil row_stencil = {.i = i, .j = j, .k = k, .c = component};
929 MatStencil column_stencils[3];
930 PetscInt row_local, row, column_locals[3], columns[3];
932 user, nvert, i, j, k, component, &ri, &rj, &rk);
933
934 if (type == MOM_ROW_PHYSICAL) {
935 column_count = 3;
936 for (PetscInt column_component = 0; column_component < 3;
937 ++column_component) {
938 column_stencils[column_component] = (MatStencil){
939 .i = i, .j = j, .k = k, .c = column_component
940 };
941 }
942 } else {
943 column_count = 1;
944 column_stencils[0] = row_stencil;
945 if (type == MOM_ROW_PERIODIC_DUPLICATE) {
946 column_stencils[column_count++] = (MatStencil){
947 .i = ri, .j = rj, .k = rk, .c = component
948 };
949 }
950 }
951 row_local = component + 3 * (
952 (i - ghost_starts[0]) + ghost_sizes[0] * (
953 (j - ghost_starts[1]) + ghost_sizes[1] *
954 (k - ghost_starts[2])));
955 for (PetscInt column_index = 0; column_index < column_count;
956 ++column_index) {
957 const MatStencil column = column_stencils[column_index];
958 if (!(column.i >= ghost_starts[0] &&
959 column.i < ghost_starts[0] + ghost_sizes[0] &&
960 column.j >= ghost_starts[1] &&
961 column.j < ghost_starts[1] + ghost_sizes[1] &&
962 column.k >= ghost_starts[2] &&
963 column.k < ghost_starts[2] + ghost_sizes[2])) {
964 ierr = PetscError(comm, __LINE__, PETSC_FUNCTION_NAME, __FILE__,
965 PETSC_ERR_ARG_OUTOFRANGE,
966 PETSC_ERROR_INITIAL,
967 "Point-block column lies outside the DMDA ghost stencil.");
968 goto cleanup;
969 }
970 column_locals[column_index] = column.c + 3 * (
971 (column.i - ghost_starts[0]) + ghost_sizes[0] * (
972 (column.j - ghost_starts[1]) + ghost_sizes[1] *
973 (column.k - ghost_starts[2])));
974 }
975 ierr = ISLocalToGlobalMappingApply(local_to_global, 1,
976 &row_local, &row);
977 if (ierr) goto cleanup;
978 ierr = ISLocalToGlobalMappingApply(local_to_global, column_count,
979 column_locals, columns);
980 if (ierr) goto cleanup;
981 if (!(row >= ownership_start && row < ownership_end)) {
982 ierr = PetscError(comm, __LINE__, PETSC_FUNCTION_NAME, __FILE__,
983 PETSC_ERR_PLIB, PETSC_ERROR_INITIAL,
984 "DMDA-mapped point-block row is not locally owned.");
985 goto cleanup;
986 }
987 for (PetscInt column_index = 0; column_index < column_count;
988 ++column_index) {
989 PetscBool duplicate = PETSC_FALSE;
990 for (PetscInt previous = 0; previous < column_index; ++previous)
991 if (columns[previous] == columns[column_index]) duplicate = PETSC_TRUE;
992 if (columns[column_index] < 0) {
993 ierr = PetscError(comm, __LINE__, PETSC_FUNCTION_NAME, __FILE__,
994 PETSC_ERR_PLIB, PETSC_ERROR_INITIAL,
995 "DMDA-mapped point-block column is invalid.");
996 goto cleanup;
997 }
998 if (duplicate) continue;
999 if (columns[column_index] >= ownership_start &&
1000 columns[column_index] < ownership_end)
1001 ++diagonal_nnz[row - ownership_start];
1002 else
1003 ++offdiagonal_nnz[row - ownership_start];
1004 }
1005 }
1006 }
1007 }
1008 }
1009
1010 ierr = MatCreateAIJ(comm, local_size, local_size, global_size, global_size,
1011 0, diagonal_nnz, 0, offdiagonal_nnz, &matrix);
1012 if (ierr) goto cleanup;
1013 ierr = MatSetBlockSize(matrix, 3); if (ierr) goto cleanup;
1014 ierr = MatSetLocalToGlobalMapping(matrix, local_to_global, local_to_global);
1015 if (ierr) goto cleanup;
1016 ierr = MatSetStencil(matrix, 3, ghost_sizes, ghost_starts, 3);
1017 if (ierr) goto cleanup;
1018 ierr = MatSetDM(matrix, user->fda); if (ierr) goto cleanup;
1019 ierr = MatSetOption(matrix, MAT_NEW_NONZERO_ALLOCATION_ERR, PETSC_TRUE);
1020 if (ierr) goto cleanup;
1021 ierr = PetscFree2(diagonal_nnz, offdiagonal_nnz); if (ierr) goto cleanup;
1022
1023 *preconditioning_matrix = matrix;
1024 matrix = NULL;
1025
1026cleanup:
1027 if (nvert) {
1028 cleanup_ierr = DMDAVecRestoreArrayRead(user->da, user->lNvert, &nvert);
1029 if (!ierr) ierr = cleanup_ierr;
1030 }
1031 cleanup_ierr = MatDestroy(&matrix); if (!ierr) ierr = cleanup_ierr;
1032 cleanup_ierr = PetscFree2(diagonal_nnz, offdiagonal_nnz);
1033 if (!ierr) ierr = cleanup_ierr;
1035 PetscFunctionReturn(ierr);
1036}
1037
1038/**
1039 * @brief Replaces every non-independent residual row with an explicit equation.
1040 *
1041 * Which rows are non-independent is decided by ClassifyMomentumRow(), shared with
1042 * the residual path; only the action taken differs. Conditioned face-normal rows
1043 * use F=X-Uconditioned, unconditioned legacy dummy/tangential rows use F=X, and
1044 * periodic duplicates use Fdup=Xdup-Xrep. These equations prevent the zero Jacobian
1045 * rows that EnforceRHSBoundaryConditions()'s zeroing would produce in a matrix-free
1046 * Newton operator. Immersed and interface rows are rejected before this callback is
1047 * installed. A TwoD-masked component needs no constraint: ComputeRHS() zeroes its
1048 * spatial terms, so its row is the time derivative alone, which holds a velocity that
1049 * is constant in time exactly, as the Picard solver's skipped row does.
1050 * @param ctx Active solve context.
1051 * @param X Unconditioned PETSc trial state.
1052 * @param F Residual vector to update in place.
1053 * @return PetscErrorCode 0 on success.
1054 */
1056 Vec X, Vec F)
1057{
1058 UserCtx *user = ctx->user;
1059 DMDALocalInfo info = user->info;
1060 Vec local_x = NULL;
1061 Cmpnts ***x = NULL, ***conditioned = NULL, ***f = NULL, ***lx = NULL;
1062 const PetscInt xs = info.xs, xe = info.xs + info.xm;
1063 const PetscInt ys = info.ys, ye = info.ys + info.ym;
1064 const PetscInt zs = info.zs, ze = info.zs + info.zm;
1065
1066 PetscFunctionBeginUser;
1068 PetscCall(DMGetLocalVector(user->fda, &local_x));
1069 PetscCall(DMGlobalToLocalBegin(user->fda, X, INSERT_VALUES, local_x));
1070 PetscCall(DMGlobalToLocalEnd(user->fda, X, INSERT_VALUES, local_x));
1071 PetscCall(DMDAVecGetArrayRead(user->fda, X, &x));
1072 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &conditioned));
1073 PetscCall(DMDAVecGetArray(user->fda, F, &f));
1074 PetscCall(DMDAVecGetArrayRead(user->fda, local_x, &lx));
1075
1076 for (PetscInt k = zs; k < ze; ++k) {
1077 for (PetscInt j = ys; j < ye; ++j) {
1078 for (PetscInt i = xs; i < xe; ++i) {
1079 PetscScalar *fv = &f[k][j][i].x;
1080 const PetscScalar *xv = &x[k][j][i].x;
1081 const PetscScalar *cv = &conditioned[k][j][i].x;
1082
1083 for (PetscInt component = 0; component < 3; ++component) {
1084 PetscInt ri, rj, rk;
1086 user, i, j, k, component, &ri, &rj, &rk);
1087 const PetscScalar *rv = &lx[rk][rj][ri].x;
1088
1089 if (row == MOM_ROW_FIXED_CONDITIONED) fv[component] = xv[component] - cv[component];
1090 else if (row == MOM_ROW_FIXED_HOMOGENEOUS) fv[component] = xv[component];
1091 else if (row == MOM_ROW_PERIODIC_DUPLICATE) fv[component] = xv[component] - rv[component];
1092 }
1093 }
1094 }
1095 }
1096
1097 PetscCall(DMDAVecRestoreArrayRead(user->fda, local_x, &lx));
1098 PetscCall(DMDAVecRestoreArray(user->fda, F, &f));
1099 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &conditioned));
1100 PetscCall(DMDAVecRestoreArrayRead(user->fda, X, &x));
1101 PetscCall(DMRestoreLocalVector(user->fda, &local_x));
1103 PetscFunctionReturn(PETSC_SUCCESS);
1104}
1105
1106/**
1107 * @brief Adapts a PETSc trial vector to the existing momentum residual path.
1108 *
1109 * A matrix-free SNES residual must be a deterministic function of the trial
1110 * vector X alone: F(X) may not depend on any state left by a previous residual
1111 * or MFFD evaluation, or finite-difference Jacobian actions become inconsistent.
1112 * To honor that contract this callback fully derives the Cartesian velocity
1113 * state (Ucat/lUcat) from X before the first boundary sweep -- see the inline
1114 * comment below for why ApplyBoundaryConditions()'s own internal reconstruction
1115 * is not sufficient for the first outlet pass.
1116 *
1117 * State invariants:
1118 * - On entry, X is the only input that determines the result; user->Ucont,
1119 * user->Ucat and their local ghosts are treated as scratch and are fully
1120 * overwritten from X.
1121 * - Supported handlers may overwrite flux totals and other diagnostics on
1122 * every call, but those values must not affect a later call at the same X;
1123 * the deterministic seed guarantees this.
1124 * - No histories, pressure, viscosity, or controller state advance here.
1125 *
1126 * Side effects: overwrites user->Ucont/lUcont, user->Ucat/lUcat, user->Rhs, the
1127 * boundary Ubcs targets, and boundary flux/area diagnostics; writes F.
1128 *
1129 * @param snes Calling nonlinear solver.
1130 * @param X Trial solution (read-only).
1131 * @param F Residual output.
1132 * @param vctx Pointer to MomentumNewtonKrylovContext.
1133 * @return PetscErrorCode 0 on success.
1134 */
1135static PetscErrorCode MomentumNewtonKrylov_FormResidual(SNES snes, Vec X, Vec F, void *vctx)
1136{
1138 UserCtx *user = ctx->user;
1139 const FieldId staggered_fields[] = {FIELD_ID_UCONT};
1140 const FieldId cell_fields[] = {FIELD_ID_UCAT};
1141
1142 PetscFunctionBeginUser;
1144 (void)snes;
1145 PetscCall(VecCopy(X, user->Ucont));
1146 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, staggered_fields));
1147
1148 /* Deterministic pre-boundary Cartesian seed. Establish the full Ucat/lUcat
1149 * state from the current X before any boundary handler runs:
1150 *
1151 * X -> Ucont/lUcont -> Ucat -> periodic Ucat -> lUcat -> boundaries
1152 *
1153 * Why this is required, and why it is NOT redundant with the reconstruction
1154 * already performed inside ApplyBoundaryConditions():
1155 *
1156 * 1. A matrix-free SNES residual must be a deterministic function of X. If
1157 * the Cartesian state is left over from a previous residual/MFFD call,
1158 * F(X) becomes history dependent and the finite-difference Jacobian
1159 * action Jv = (F(X+hv)-F(X))/h is invalidated.
1160 * 2. The conservation-outlet handler reads lUcat during the FIRST boundary
1161 * sweep (it measures the uncorrected outflow and builds the outlet
1162 * profile from the Cartesian field). Without this seed it would read the
1163 * stale lUcat from the preceding evaluation.
1164 * 3. ApplyBoundaryConditions() does reconstruct Ucat/lUcat, but only AFTER
1165 * each handler sweep (Contra2Cart runs after BoundarySystem_ExecuteStep
1166 * within every pass). Those internal updates therefore prepare passes 2
1167 * and 3 -- they cannot prepare the very first outlet read of pass 1.
1168 * 4. Contra2Cart() rebuilds the global Ucat interior from the current
1169 * lUcont, but it does not by itself refresh lUcat (nor lUcont; that was
1170 * done by the SynchronizePeriodicStaggeredFields call above).
1171 * 5. SynchronizePeriodicCellFields(FIELD_ID_UCAT) must run before the ghost
1172 * scatter so periodic duplicate planes are finalized consistently (it is
1173 * a no-op when no direction is periodic, as on the straight duct).
1174 * 6. UpdateLocalGhosts(FIELD_ID_UCAT) is required because the outlet handler reads
1175 * lUcat -- the local ghosted vector -- not merely the global Ucat.
1176 *
1177 * Do NOT "simplify" this to a bare Contra2Cart(user), and do NOT delete it
1178 * as apparently redundant with ApplyBoundaryConditions(): the three internal
1179 * boundary passes remain necessary (they refresh the Cartesian state after
1180 * each boundary correction), but only this sequence makes pass 1's input a
1181 * deterministic function of X. */
1182 PetscCall(Contra2Cart(user));
1183 PetscCall(SynchronizePeriodicCellFields(user, 1, cell_fields));
1184 PetscCall(UpdateLocalGhosts(user, FIELD_ID_UCAT));
1185
1186 PetscCall(ApplyBoundaryConditions(user));
1187 PetscCall(ComputeTotalResidual(user));
1188 PetscCall(VecCopy(user->Rhs, F));
1189 PetscCall(VecScale(F, -1.0));
1190 PetscCall(MomentumNewtonKrylov_ApplyConstraints(ctx, X, F));
1192 PetscFunctionReturn(PETSC_SUCCESS);
1193}
1194
1195/** @brief Builds the solve context MomentumSolver_NewtonKrylov() keeps for the run. */
1196static PetscErrorCode MomentumNewtonKrylov_Build(UserCtx *user)
1197{
1199 MomentumPreconditionerDescription preconditioner_description = {0};
1200 KSP ksp = NULL;
1201 PC pc = NULL;
1202
1203 PetscFunctionBeginUser;
1205 PetscCall(PetscNew(&user->momentumNewtonKrylov));
1206 ctx = user->momentumNewtonKrylov;
1207 ctx->user = user;
1208 PetscCall(VecDuplicate(user->Ucont, &ctx->solution));
1209 PetscCall(VecDuplicate(user->Ucont, &ctx->entry_backup));
1210 PetscCall(SNESCreate(PetscObjectComm((PetscObject)user->Ucont), &ctx->snes));
1211 PetscCall(MomentumNewtonKrylov_ReadLinearizationConfig(&ctx->jacobian, &preconditioner_description));
1212 PetscCall(SNESSetOptionsPrefix(ctx->snes, "mom_nk_"));
1213 PetscCall(SNESSetType(ctx->snes, SNESNEWTONLS));
1214 PetscCall(SNESSetDM(ctx->snes, user->fda));
1215 /* The residual and Jacobian callbacks are registered by MomentumSolver_NewtonKrylov() before
1216 every solve, so none is registered here. */
1217 PetscCall(MomentumNewtonJacobian_Create(ctx->snes, &ctx->jacobian));
1219 &preconditioner_description,
1220 &ctx->preconditioning_engine));
1221 PetscCall(SNESGetKSP(ctx->snes, &ksp));
1222 PetscCall(KSPSetType(ksp, KSPGMRES));
1223 PetscCall(KSPGetPC(ksp, &pc));
1225 PetscCall(SNESSetFromOptions(ctx->snes));
1226 PetscCall(SNESMonitorSet(ctx->snes, MomentumNewtonKrylov_Monitor, ctx, NULL));
1227 PetscCall(KSPMonitorSet(ksp, MomentumNewtonKrylov_LinearMonitor, ctx, NULL));
1230 PetscFunctionReturn(PETSC_SUCCESS);
1231}
1232
1233/**
1234 * @brief Implementation of \ref MomentumSolver_NewtonKrylov_Destroy().
1235 * @details Full API contract (arguments, ownership, side effects) is documented with
1236 * the header declaration in `include/momentumsolvers.h`.
1237 * @see MomentumSolver_NewtonKrylov_Destroy()
1238 */
1240{
1242
1243 PetscFunctionBeginUser;
1245 if (ctx) {
1246 PetscCall(VecDestroy(&ctx->entry_backup));
1247 PetscCall(VecDestroy(&ctx->solution));
1249 PetscCall(MomentumNewtonJacobian_Destroy(&ctx->jacobian));
1250 PetscCall(SNESDestroy(&ctx->snes));
1251 PetscCall(PetscFree(user->momentumNewtonKrylov));
1252 }
1254 PetscFunctionReturn(PETSC_SUCCESS);
1255}
1256
1257/**
1258 * @brief Measure the residual at the iterate a failed SNES solve holds, for the
1259 * failure policy in MomentumSolver_NewtonKrylov().
1260 *
1261 * SNES judges the residual by absolute and relative tolerances that do not scale with the
1262 * grid or the flow. Near a steady state the residual can reach the round-off floor of its
1263 * own evaluation; the line search then cannot decrease it and SNES reports divergence for
1264 * a state that is as converged as the discretization allows. The caller therefore judges
1265 * the held iterate itself: by the dual-time solver's dimensionless test,
1266 * `|R|_inf <= mom_resid_atol * a0 |U|_inf / dt`, and by whether its residual is finite and
1267 * no larger than the residual the step started from.
1268 *
1269 * @param[in] ctx Solve context; its SNES supplies the residual function and vector.
1270 * @param[in] solution Iterate to test; it is left unchanged.
1271 * @param[out] residual `|R|_inf` at @p solution.
1272 * @param[out] residual_2 `|R|_2` at @p solution, the norm SNES reports as its initial norm.
1273 * @param[out] reference `a0 |U|_inf / dt` at @p solution.
1274 * @param[out] satisfied True when `mom_resid_atol > 0` and the dimensionless test holds.
1275 */
1277 PetscReal *residual, PetscReal *residual_2,
1278 PetscReal *reference, PetscBool *satisfied)
1279{
1280 SimCtx *simCtx = ctx->user->simCtx;
1281 Vec F = NULL;
1282 PetscReal ucont_inf;
1283
1284 PetscFunctionBeginUser;
1286 PetscCall(SNESGetFunction(ctx->snes, &F, NULL, NULL));
1287 PetscCall(SNESComputeFunction(ctx->snes, solution, F));
1288 PetscCall(VecNorm(F, NORM_INFINITY, residual));
1289 PetscCall(VecNorm(F, NORM_2, residual_2));
1290 PetscCall(VecNorm(solution, NORM_INFINITY, &ucont_inf));
1291 *reference = MomentumBDFCoefficient(simCtx) * ucont_inf / simCtx->dt;
1292 *satisfied = (PetscBool)(simCtx->mom_resid_atol > 0.0 && *reference > 0.0 &&
1293 *residual <= simCtx->mom_resid_atol * *reference);
1295 PetscFunctionReturn(PETSC_SUCCESS);
1296}
1297
1298/*
1299 * Runs one matrix-free Newton--Krylov momentum solve, with the solve context built on the
1300 * first call and kept for the run. The public header owns the rendered API contract; this
1301 * definition retains the detailed lifecycle and rollback behavior below.
1302 */
1303PetscErrorCode MomentumSolver_NewtonKrylov(UserCtx *user, IBMNodes *ibm, FSInfo *fsi)
1304{
1305 PetscErrorCode ierr = PETSC_SUCCESS, cleanup_ierr;
1306 SimCtx *simCtx;
1307 SNES snes = NULL;
1308 Vec solution = NULL, entry_backup = NULL;
1309 MomentumNewtonKrylovContext *ctx = NULL;
1310 PetscBool restore_entry = PETSC_FALSE;
1311 PetscBool solve_started = PETSC_FALSE;
1312 PetscBool committed = PETSC_FALSE;
1313 PetscBool converged = PETSC_FALSE;
1314 SNESConvergedReason reason = SNES_CONVERGED_ITERATING;
1315 PetscInt nonlinear_its = 0, function_evals = 0, linear_its = 0;
1316 PetscReal final_norm = PETSC_MAX_REAL;
1317 const FieldId staggered_fields[] = {FIELD_ID_UCONT};
1318
1319 PetscFunctionBeginUser;
1321 PetscCall(MomentumNewtonKrylov_Validate(user));
1322 PetscCheck(ibm == NULL && fsi == NULL, PETSC_COMM_WORLD, PETSC_ERR_SUP,
1323 "Newton Krylov version one does not accept IBM or FSI objects.");
1324 simCtx = user->simCtx;
1325
1326 /* Rhs is kept for the run and zeroed for each step. */
1327 if (!user->Rhs) { ierr = VecDuplicate(user->Ucont, &user->Rhs); if (ierr) goto cleanup; }
1328 ierr = VecZeroEntries(user->Rhs); if (ierr) goto cleanup;
1329
1330 ierr = SynchronizePeriodicStaggeredFields(user, 1, staggered_fields); if (ierr) goto cleanup;
1331 ierr = ApplyBoundaryConditions(user); if (ierr) goto cleanup;
1332 if (!user->momentumNewtonKrylov) { ierr = MomentumNewtonKrylov_Build(user); if (ierr) goto cleanup; }
1333 ctx = user->momentumNewtonKrylov;
1334 ctx->have_initial_norm = PETSC_FALSE;
1335 ctx->initial_norm = 0.0;
1336 snes = ctx->snes;
1337 /* The residual and Jacobian callbacks live on fda's DMSNES, which every SNES on fda shares;
1338 registering them before each solve keeps another solver on fda from leaving its own in place. */
1339 ierr = SNESSetFunction(snes, NULL, MomentumNewtonKrylov_FormResidual, ctx); if (ierr) goto cleanup;
1340 ierr = MomentumNewtonJacobian_Register(snes, &ctx->jacobian,
1341 &ctx->preconditioning_engine, ctx); if (ierr) goto cleanup;
1342 solution = ctx->solution;
1343 entry_backup = ctx->entry_backup;
1344 ierr = VecCopy(user->Ucont, entry_backup); if (ierr) goto cleanup;
1345 restore_entry = PETSC_TRUE;
1346 ierr = VecCopy(user->Ucont, solution); if (ierr) goto cleanup;
1347
1349 "Newton Krylov Jacobian: finite_difference / matrix_free; "
1350 "Preconditioner: %s; PETSc Jacobian matrix type: MATMFFD; "
1351 "PETSc PC type: %s.\n",
1353 "none" : "frozen_momentum_jacobian / point_block",
1355
1357 solve_started = PETSC_TRUE;
1358 ierr = SNESSolve(snes, NULL, solution);
1359 if (ierr) goto cleanup;
1360 ierr = SNESGetConvergedReason(snes, &reason); if (ierr) goto cleanup;
1361 ierr = SNESGetIterationNumber(snes, &nonlinear_its); if (ierr) goto cleanup;
1362 ierr = SNESGetNumberFunctionEvals(snes, &function_evals); if (ierr) goto cleanup;
1363 ierr = SNESGetLinearSolveIterations(snes, &linear_its); if (ierr) goto cleanup;
1364 ierr = SNESGetFunctionNorm(snes, &final_norm); if (ierr) goto cleanup;
1365
1366 /* Failure policy, shared with the dual-time solver: a solve SNES did not declare
1367 converged still commits its iterate when the residual there is finite and no larger
1368 than at entry, and the step is reported as not converged. Only a non-finite or
1369 grown residual rolls the step back and stops the run. */
1370 if (reason > 0) {
1371 committed = PETSC_TRUE;
1372 converged = PETSC_TRUE;
1373 } else {
1374 PetscReal held_residual = 0.0, held_residual_2 = 0.0, held_reference = 0.0;
1375 ierr = MomentumNewtonKrylov_AssessIterate(ctx, solution, &held_residual, &held_residual_2,
1376 &held_reference, &converged); if (ierr) goto cleanup;
1377 committed = (PetscBool)(!PetscIsInfOrNanReal(held_residual_2) && ctx->have_initial_norm &&
1378 held_residual_2 <= ctx->initial_norm);
1379 if (!committed) converged = PETSC_FALSE;
1380 if (converged) {
1382 "Newton Krylov step %" PetscInt_FMT ": SNES reported %s, but |R|_inf = %.3e is within "
1383 "residual_absolute_tol (%.1e) x a0|U|/dt = %.3e; the step is committed as converged.\n",
1384 simCtx->step, SNESConvergedReasons[reason], (double)held_residual,
1385 (double)simCtx->mom_resid_atol, (double)held_reference);
1386 } else if (committed) {
1388 "Newton Krylov step %" PetscInt_FMT ": SNES reported %s with |R|_inf = %.3e, above "
1389 "residual_absolute_tol (%.1e) x a0|U|/dt = %.3e; |R|_2 = %.3e is no larger than %.3e at "
1390 "entry, so the step continues from the SNES iterate, not converged.\n",
1391 simCtx->step, SNESConvergedReasons[reason], (double)held_residual,
1392 (double)simCtx->mom_resid_atol, (double)held_reference,
1393 (double)held_residual_2, (double)ctx->initial_norm);
1394 } else {
1395 if (ctx->have_initial_norm) {
1397 "Newton Krylov step %" PetscInt_FMT ": SNES reported %s and |R|_2 at its iterate is "
1398 "%.3e against %.3e at entry; the step is rolled back.\n",
1399 simCtx->step, SNESConvergedReasons[reason], (double)held_residual_2,
1400 (double)ctx->initial_norm);
1401 } else {
1403 "Newton Krylov step %" PetscInt_FMT ": SNES reported %s before an entry residual norm "
1404 "was available (|R|_2 at its iterate is %.3e); the step is rolled back.\n",
1405 simCtx->step, SNESConvergedReasons[reason], (double)held_residual_2);
1406 }
1407 }
1408 }
1409
1410 ierr = VecCopy(committed ? solution : entry_backup, user->Ucont); if (ierr) goto cleanup;
1411 simCtx->mom_last_converged = converged;
1412 ierr = SynchronizePeriodicStaggeredFields(user, 1, staggered_fields); if (ierr) goto cleanup;
1413 ierr = ApplyBoundaryConditions(user); if (ierr) goto cleanup;
1414 restore_entry = PETSC_FALSE;
1415
1417 "Newton Krylov momentum solve: reason=%s (%" PetscInt_FMT "), Newton iterations=%" PetscInt_FMT ", residual evaluations=%" PetscInt_FMT ", Krylov iterations=%" PetscInt_FMT ", final norm=%.6e, state=%s.\n",
1418 SNESConvergedReasons[reason], (PetscInt)reason, nonlinear_its, function_evals,
1419 linear_its, (double)final_norm,
1420 !committed ? "rolled back" : converged ? "committed" : "committed, not converged");
1421 if (!committed) ierr = PETSC_ERR_CONV_FAILED;
1422
1423cleanup:
1424 /* A PETSc solve error can bypass the normal statistics path. Query whatever
1425 SNES retained without replacing the primary error so a failed attempt is
1426 still represented in the structured log. */
1427 if (solve_started && snes) {
1428 (void)SNESGetConvergedReason(snes, &reason);
1429 (void)SNESGetIterationNumber(snes, &nonlinear_its);
1430 (void)SNESGetNumberFunctionEvals(snes, &function_evals);
1431 (void)SNESGetLinearSolveIterations(snes, &linear_its);
1432 (void)SNESGetFunctionNorm(snes, &final_norm);
1433 }
1434 if (restore_entry && entry_backup) {
1435 cleanup_ierr = VecCopy(entry_backup, user->Ucont);
1436 if (!ierr) ierr = cleanup_ierr;
1437 cleanup_ierr = SynchronizePeriodicStaggeredFields(user, 1, staggered_fields);
1438 if (!ierr) ierr = cleanup_ierr;
1439 cleanup_ierr = ApplyBoundaryConditions(user);
1440 if (!ierr) ierr = cleanup_ierr;
1441 simCtx->mom_last_converged = PETSC_FALSE;
1442 committed = PETSC_FALSE;
1443 }
1444 if (ctx && ctx->history_file) {
1445 (void)fclose(ctx->history_file);
1446 ctx->history_file = NULL;
1447 }
1448 if (ctx && ctx->linear_history_file) {
1449 (void)fclose(ctx->linear_history_file);
1450 ctx->linear_history_file = NULL;
1451 }
1452 if (solve_started) {
1453 MomentumNewtonKrylov_WriteSummary(ctx, reason, nonlinear_its, function_evals,
1454 linear_its, final_norm, committed);
1455 }
1456 /* A failed step, or a failed first build, releases what the solver holds, so the next call
1457 starts afresh; a committed step keeps it for the next. */
1458 if (ierr) {
1459 cleanup_ierr = VecDestroy(&user->Rhs); if (!ierr) ierr = cleanup_ierr;
1460 cleanup_ierr = MomentumSolver_NewtonKrylov_Destroy(user); if (!ierr) ierr = cleanup_ierr;
1461 }
1463 PetscFunctionReturn(ierr);
1464}
MomentumRowType
Classification of one staggered momentum row (location + component).
Definition Boundaries.h:191
@ MOM_ROW_FIXED_HOMOGENEOUS
Dummy/tangential row carrying no unknown at all.
Definition Boundaries.h:194
@ MOM_ROW_PHYSICAL
Independent unknown governed by the momentum equation.
Definition Boundaries.h:192
@ MOM_ROW_PERIODIC_DUPLICATE
Duplicate of a wrapped representative row (see ri, rj, rk).
Definition Boundaries.h:195
@ MOM_ROW_FIXED_CONDITIONED
Strong Dirichlet row; the value comes from ApplyBoundaryConditions().
Definition Boundaries.h:193
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:139
PetscBool BoundaryFaceHasWallShear(const BoundaryFaceConfig *face)
Reports whether a boundary face is a wall that exerts shear on the flow.
Definition Boundaries.c:9
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:193
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
PetscErrorCode PicurvOpenRunCsv(SimCtx *simCtx, const char *dir, const char *filename, const char *header, PicurvCsvLifecycle lifecycle, PetscBool required, FILE **file)
Opens one of the run's CSV records (diagnostics, solver history, profiling) for writing.
Definition logging.c:3552
@ PICURV_CSV_TRUNCATE_ON_FRESH
A fresh run's first write empties the file; a continued run appends.
Definition logging.h:1146
#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:214
#define PROFILE_HELPER_BEGIN
Marks the beginning of a profiled helper: timed only when enabled by name.
Definition logging.h:1014
#define PROFILE_HELPER_END
Alias of PROFILE_FUNCTION_END for a function that began with PROFILE_HELPER_BEGIN.
Definition logging.h:1033
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:1029
#define LOG(scope, level, fmt,...)
Logging macro for PETSc-based applications with scope control.
Definition logging.h:86
@ LOG_ERROR
Critical errors that may halt the program.
Definition logging.h:29
@ 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 function: an always-on aggregate timer.
Definition logging.h:1000
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 MomentumNewtonKrylov_AssessIterate(MomentumNewtonKrylovContext *ctx, Vec solution, PetscReal *residual, PetscReal *residual_2, PetscReal *reference, PetscBool *satisfied)
Measure the residual at the iterate a failed SNES solve holds, for the failure policy in MomentumSolv...
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 files (per Newton and per Krylov iteration).
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 MomentumNewtonKrylov_Build(UserCtx *user)
Builds the solve context MomentumSolver_NewtonKrylov() keeps for the run.
static FILE * MomentumNewtonKrylov_OpenCsv(const MomentumNewtonKrylovContext *ctx, const char *kind, const char *header)
Opens one of the block's Newton CSV records on rank 0, or returns NULL.
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:3313
PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:2593
PetscErrorCode(* Describe)(UserCtx *, MomentumPreconditionerDescription *)
PetscErrorCode(* AssembleInterior)(UserCtx *, Vec, Mat)
#define MomentumSolver_NewtonKrylov_Destroy
PetscBool mom_nk_monitor_history
Definition variables.h:975
PetscInt movefsi
Definition variables.h:950
@ INLET
Definition variables.h:323
@ OUTLET
Definition variables.h:322
@ PERIODIC
Definition variables.h:325
@ WALL
Definition variables.h:319
PetscInt moveframe
Definition variables.h:951
PetscInt TwoD
Definition variables.h:951
PetscMPIInt rank
Definition variables.h:919
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:1182
PetscInt block_number
Definition variables.h:1009
Vec lNvert
Definition variables.h:1197
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1160
PetscBool mom_last_converged
Definition variables.h:971
@ BC_HANDLER_PERIODIC_GEOMETRIC
Definition variables.h:352
@ BC_HANDLER_PERIODIC_DRIVEN_INITIAL_FLUX
Definition variables.h:355
@ BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX
Definition variables.h:354
@ BC_HANDLER_WALL_MOVING
Definition variables.h:346
@ BC_HANDLER_INLET_PRESCRIBED_VELOCITY
Definition variables.h:348
@ BC_HANDLER_WALL_NOSLIP
Definition variables.h:345
@ BC_HANDLER_OUTLET_CONSERVATION
Definition variables.h:350
@ BC_HANDLER_WALL_FREE_SLIP
Definition variables.h:347
PetscReal ren
Definition variables.h:965
BCHandlerType handler_type
Definition variables.h:427
PetscInt _this
Definition variables.h:1175
PetscReal dt
Definition variables.h:933
Vec Ucont
Definition variables.h:1197
PetscInt rotatefsi
Refused at setup: immersed boundaries and moving bodies are not implemented.
Definition variables.h:950
PetscScalar x
Definition variables.h:145
char log_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:941
Vec lNu_t
Definition variables.h:1259
PetscScalar z
Definition variables.h:145
struct MomentumNewtonKrylovContext * momentumNewtonKrylov
Definition variables.h:1235
Vec lUcont
Definition variables.h:1197
PetscInt step
Definition variables.h:924
DMDALocalInfo info
Definition variables.h:1169
PetscScalar y
Definition variables.h:145
PetscInt les
Active LES closure; an LESModelType value.
Definition variables.h:1041
Vec Nvert
Definition variables.h:1197
BCType mathematical_type
Definition variables.h:426
PetscReal mom_resid_atol
Definition variables.h:960
PetscInt rotateframe
moveframe/rotateframe are refused at setup.
Definition variables.h:951
PetscInt immersed
Definition variables.h:950
BCFace
Identifies the six logical faces of a structured computational block.
Definition variables.h:308
@ BC_FACE_NEG_X
Definition variables.h:309
@ BC_FACE_POS_Z
Definition variables.h:311
@ BC_FACE_POS_Y
Definition variables.h:310
@ BC_FACE_NEG_Z
Definition variables.h:311
@ BC_FACE_POS_X
Definition variables.h:309
@ BC_FACE_NEG_Y
Definition variables.h:310
Holds the complete configuration for one of the six boundary faces.
Definition variables.h:424
A 3D point or vector with PetscScalar components.
Definition variables.h:144
Holds all data related to the state and motion of a body in FSI.
Definition variables.h:536
Represents a collection of nodes forming a surface for the IBM.
Definition variables.h:463
The master context for the entire simulation.
Definition variables.h:916
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1157
double nu_t(double yplus)
Computes turbulent eddy viscosity ratio (ν_t / ν)