PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
Data Structures | Macros | Enumerations | Functions
momentumsolvers.h File Reference
#include "variables.h"
#include "logging.h"
#include "rhs.h"
#include "Boundaries.h"
Include dependency graph for momentumsolvers.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  MomStabilityReport
 Diagnostic report produced by ComputeMomentumStabilityEstimate(). More...
 

Macros

#define MomentumSolver_DualTime_Picard_RK4   MomentumSolver_DualTime_Picard_JamesonRK
 

Enumerations

enum  MomStabCandidate { MOM_STAB_CAND_B = 0 , MOM_STAB_CAND_C = 1 , MOM_STAB_CAND_D = 2 }
 Convective-estimate candidate selector. More...
 
enum  MomStabLimiter { MOM_STAB_LIMITER_TIME = 0 , MOM_STAB_LIMITER_CONVECTION = 1 , MOM_STAB_LIMITER_VISCOSITY = 2 }
 Dominant stiffness contributor at the controlling cell. More...
 

Functions

PetscErrorCode MomentumSolver_Explicit_RungeKutta4 (UserCtx *user, IBMNodes *ibm, FSInfo *fsi)
 Advances the momentum equations using an explicit 4th-order Runge-Kutta scheme.
 
PetscErrorCode MomentumSolver_NewtonKrylov (UserCtx *user, IBMNodes *ibm, FSInfo *fsi)
 Solves one physical momentum step with matrix-free Newton–Krylov.
 
PetscErrorCode MomentumSolver_DualTime_Picard_JamesonRK (UserCtx *user, IBMNodes *ibm, FSInfo *fsi)
 Solves the momentum equations using dual-time Picard iteration with Jameson RK smoothing.
 
PetscBool MomentumUsesBDF2 (SimCtx *simCtx)
 Returns whether the current physical step uses the BDF2 discretization.
 
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 ComputeMomentumStabilityEstimate (UserCtx *user, PetscInt block_number, PetscReal dt, MomStabCandidate candidate, MomStabilityReport *rep)
 Compute the momentum pseudo-time stability estimate (shadow/diagnostic).
 
PetscInt MomCellActiveRows (PetscReal ***nvert, PetscInt k, PetscInt j, PetscInt i, PetscInt mx, PetscInt my, PetscInt mz, PetscBool np_x1, PetscBool np_y1, PetscBool np_z1, PetscInt twoD)
 Active staggered-momentum row mask for a cell (exposed for unit testing).
 

Data Structure Documentation

◆ MomStabilityReport

struct MomStabilityReport

Diagnostic report produced by ComputeMomentumStabilityEstimate().

This is a PRACTICAL CONSERVATIVE STABILITY ESTIMATE (operator-scaled pseudo-time estimate), not a proven spectral radius. All lambda_* are global maxima in [1/s].

Definition at line 165 of file momentumsolvers.h.

Data Fields
PetscReal lambda
PetscReal lambda_t
PetscReal lambda_c
PetscReal lambda_v
PetscReal lambda_B
PetscReal lambda_C
PetscReal lambda_D
PetscInt ci
PetscInt cj
PetscInt ck
PetscInt cblock
PetscInt cclass
PetscInt one_sided
PetscInt active_cells
PetscBool estimate_incomplete
MomStabLimiter limiter

Macro Definition Documentation

◆ MomentumSolver_DualTime_Picard_RK4

#define MomentumSolver_DualTime_Picard_RK4   MomentumSolver_DualTime_Picard_JamesonRK

Enumeration Type Documentation

◆ MomStabCandidate

Convective-estimate candidate selector.

See ComputeMomentumStabilityEstimate().

B: six-face transport scale (f_c * Aj * sum|U_f| / 2). C: B + frozen-advector discrete-divergence diagonal term. D: C + nonlinear velocity-gradient row-norm term (lambda_grad_u).

Enumerator
MOM_STAB_CAND_B 
MOM_STAB_CAND_C 
MOM_STAB_CAND_D 

Definition at line 144 of file momentumsolvers.h.

144 {
145 MOM_STAB_CAND_B = 0,
146 MOM_STAB_CAND_C = 1,
MomStabCandidate
Convective-estimate candidate selector.
@ MOM_STAB_CAND_B
@ MOM_STAB_CAND_C
@ MOM_STAB_CAND_D

◆ MomStabLimiter

Dominant stiffness contributor at the controlling cell.

Enumerator
MOM_STAB_LIMITER_TIME 
MOM_STAB_LIMITER_CONVECTION 
MOM_STAB_LIMITER_VISCOSITY 

Definition at line 153 of file momentumsolvers.h.

153 {
MomStabLimiter
Dominant stiffness contributor at the controlling cell.
@ MOM_STAB_LIMITER_CONVECTION
@ MOM_STAB_LIMITER_VISCOSITY
@ MOM_STAB_LIMITER_TIME

Function Documentation

◆ MomentumSolver_Explicit_RungeKutta4()

PetscErrorCode MomentumSolver_Explicit_RungeKutta4 ( UserCtx user,
IBMNodes ibm,
FSInfo fsi 
)
extern

Advances the momentum equations using an explicit 4th-order Runge-Kutta scheme.

Parameters
userArray of UserCtx structs for all blocks.
ibm(Optional) Pointer to IBM data. Pass NULL if disabled.
fsi(Optional) Pointer to FSI data. Pass NULL if disabled.
Returns
PetscErrorCode 0 on success.
Note
Testing status: The explicit RK path remains on the near-term backlog for direct positive-path bespoke coverage; today it is weaker than the dual-time path in the test surface.

Advances the momentum equations using an explicit 4th-order Runge-Kutta scheme.

Local to this translation unit.

Definition at line 93 of file momentumsolvers.c.

94{
95 PetscErrorCode ierr;
96 (void)fsi;
97 // --- Context Acquisition ---
98 // Get the master simulation context from the first block's UserCtx.
99 // This is the bridge to access all former global variables.
100 SimCtx *simCtx = user[0].simCtx;
101 PetscReal dt = simCtx->dt;
102 //PetscReal st = simCtx->st;
103 PetscInt istage;
104 PetscReal alfa[] = {0.25, 1.0/3.0, 0.5, 1.0};
105
106 PetscFunctionBeginUser;
107
109
110 LOG_ALLOW(GLOBAL, LOG_INFO, "Executing explicit momentum solver (Runge-Kutta) for %d block(s).\n",simCtx->block_number);
111
112 // --- 1. Pre-Loop Initialization (Legacy Logic) ---
113 // This block prepares boundary conditions and allocates the RHS vector for all blocks
114 // before the main RK loop begins. This logic is preserved from the original code.
115 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Preparing all blocks for Runge-Kutta solve...\n");
116 for (PetscInt bi = 0; bi < simCtx->block_number; bi++) {
117
118 ierr = ApplyBoundaryConditions(&user[bi]); CHKERRQ(ierr);
119
120 /*
121 // Immersed boundary interpolation (if enabled)
122 if (simCtx->immersed) {
123 LOG_ALLOW(LOCAL, LOG_DEBUG, " Performing pre-RK IBM interpolation for block %d.\n", bi);
124 for (PetscInt ibi = 0; ibi < simCtx->NumberOfBodies; ibi++) {
125 // The 'ibm' and 'fsi' pointers are passed directly from FlowSolver
126 ierr = ibm_interpolation_advanced(&user[bi], &ibm[ibi], ibi, 1); CHKERRQ(ierr);
127 }
128 }
129 */
130
131 // Allocate the persistent RHS vector for this block's context
132 ierr = VecDuplicate(user[bi].Ucont, &user[bi].Rhs); CHKERRQ(ierr);
133 }
134 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Pre-loop initialization complete.\n");
135
136 // --- 2. Main Runge-Kutta Loop ---
137 // The legacy code had an outer `pseudot` loop that only ran once. We preserve it.
138 for (PetscInt pseudot = 0; pseudot < 1; pseudot++) {
139 // Loop over each block to perform the RK stages
140 for (PetscInt bi = 0; bi < simCtx->block_number; bi++) {
141 for (istage = 0; istage < 4; istage++) {
142 LOG_ALLOW(LOCAL, LOG_DEBUG, " Block %d, RK Stage %d (alpha=%.4f)...\n", bi, istage, alfa[istage]);
143
144 // a. Calculate the Right-Hand Side (RHS) of the momentum equation.
145 ierr = ComputeRHS(&user[bi], user[bi].Rhs); CHKERRQ(ierr);
146
147 // b. Advance Ucont to the next intermediate stage using the RK coefficient.
148 // Ucont_new = Ucont_old + alpha * dt * RHS
149 ierr = VecWAXPY(user[bi].Ucont, alfa[istage] * dt, user[bi].Rhs, user[bi].Ucont_o); CHKERRQ(ierr);
150
151 // c. Synchronize periodic endpoints and local ghosts for the new intermediate Ucont.
152 const char *staggered_fields[] = {"Ucont"};
153 ierr = SynchronizePeriodicStaggeredFields(&user[bi], 1, staggered_fields); CHKERRQ(ierr);
154
155 // d. Re-apply boundary conditions for the new intermediate velocity.
156 // This is crucial for the stability and accuracy of the multi-stage scheme.
157 ierr = ApplyBoundaryConditions(&user[bi]); CHKERRQ(ierr);
158
159 } // End of RK stages for one block
160
161 /*
162 // Final IBM Interpolation for the block (if enabled)
163 if (simCtx->immersed) {
164 LOG_ALLOW(LOCAL, LOG_DEBUG, " Performing post-RK IBM interpolation for block %d.\n", bi);
165 for (PetscInt ibi = 0; ibi < simCtx->NumberOfBodies; ibi++) {
166 ierr = ibm_interpolation_advanced(&user[bi], &ibm[ibi], ibi, 1); CHKERRQ(ierr);
167 }
168 }
169 */
170
171 } // End loop over blocks
172
173 // --- 3. Inter-Block Communication (Legacy Logic) ---
174 // This is called after all blocks have completed their RK stages.
175 if (simCtx->block_number > 1) {
176 // LOG_ALLOW(GLOBAL, LOG_DEBUG, "Updating multi-block interfaces after RK stages.\n");
177 // ierr = Block_Interface_U(user); CHKERRQ(ierr);
178 }
179
180 } // End of pseudo-time loop
181
182 // --- 4. Cleanup ---
183 // Destroy the RHS vectors that were created at the start of this function.
184 for (PetscInt bi = 0; bi < simCtx->block_number; bi++) {
185 ierr = VecDestroy(&user[bi].Rhs); CHKERRQ(ierr);
186 }
187
188 LOG_ALLOW(GLOBAL, LOG_INFO, "Runge-Kutta solve completed for all blocks.\n");
189
191
192 PetscFunctionReturn(0);
193}
PetscErrorCode SynchronizePeriodicStaggeredFields(UserCtx *user, PetscInt num_fields, const char *field_names[])
Synchronizes persistent component-staggered vector fields.
PetscErrorCode ApplyBoundaryConditions(UserCtx *user)
Main boundary-condition orchestrator executed during solver timestepping.
#define LOCAL
Logging scope definitions for controlling message output.
Definition logging.h:44
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:45
#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:199
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:827
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:30
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:31
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:818
PetscErrorCode ComputeRHS(UserCtx *user, Vec Rhs)
Computes the Right-Hand Side (RHS) of the momentum equations.
Definition rhs.c:1100
PetscInt block_number
Definition variables.h:768
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:879
PetscReal dt
Definition variables.h:699
The master context for the entire simulation.
Definition variables.h:684
Here is the call graph for this function:
Here is the caller graph for this function:

◆ MomentumSolver_NewtonKrylov()

PetscErrorCode MomentumSolver_NewtonKrylov ( UserCtx user,
IBMNodes ibm,
FSInfo fsi 
)

Solves one physical momentum step with matrix-free Newton–Krylov.

Version one uses a finite-difference matrix-free Jacobian, GMRES, and no preconditioner. All PETSc solver objects are local to this call. Rows removed by legacy boundary residual enforcement are made explicit: conditioned normal rows use X-Uconditioned, untouched dummy/tangential rows use X, and periodic duplicates use Xdup-Xrepresentative. Unsupported masked, interface, and component-disabled rows are rejected before setup.

Parameters
userSingle-block momentum context.
ibmMust be NULL; immersed boundaries are not supported in version one.
fsiMust be NULL; moving-body coupling is not supported in version one.
Returns
0 on convergence, PETSC_ERR_CONV_FAILED after rollback on nonconvergence.

Solves one physical momentum step with matrix-free Newton–Krylov.

Parameters
userSingle-block momentum context.
ibmMust be NULL in version one.
fsiMust be NULL in version one.
Returns
0 on convergence or an error after restoring the canonical entry state.

Definition at line 461 of file momentum_newton_krylov.c.

462{
463 PetscErrorCode ierr = PETSC_SUCCESS, cleanup_ierr;
464 SimCtx *simCtx;
465 SNES snes = NULL;
466 Mat J = NULL;
467 Vec solution = NULL, entry_backup = NULL;
468 KSP ksp = NULL;
469 PC pc = NULL;
470 const char *pc_type = NULL;
471 PetscBool pc_is_none = PETSC_FALSE;
472 PetscBool restore_entry = PETSC_FALSE;
473 PetscBool rhs_created = PETSC_FALSE;
474 PetscBool solve_started = PETSC_FALSE;
475 PetscBool committed = PETSC_FALSE;
476 SNESConvergedReason reason = SNES_CONVERGED_ITERATING;
477 PetscInt nonlinear_its = 0, function_evals = 0, linear_its = 0;
478 PetscReal final_norm = PETSC_MAX_REAL;
480 const char *staggered_fields[] = {"Ucont"};
481
482 PetscFunctionBeginUser;
483 PetscCall(MomentumNewtonKrylov_Validate(user));
484 PetscCheck(ibm == NULL && fsi == NULL, PETSC_COMM_WORLD, PETSC_ERR_SUP,
485 "Newton Krylov version one does not accept IBM or FSI objects.");
486 PetscCheck(user->Rhs == NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
487 "Newton Krylov requires UserCtx::Rhs to be unallocated on entry.");
488 simCtx = user->simCtx;
489
490 ierr = VecDuplicate(user->Ucont, &solution); if (ierr) goto cleanup;
491 ierr = VecDuplicate(user->Ucont, &entry_backup); if (ierr) goto cleanup;
492 ierr = VecDuplicate(user->Ucont, &user->Rhs); if (ierr) goto cleanup;
493 rhs_created = PETSC_TRUE;
494 ierr = SNESCreate(PetscObjectComm((PetscObject)user->Ucont), &snes); if (ierr) goto cleanup;
495
496 ierr = SynchronizePeriodicStaggeredFields(user, 1, staggered_fields); if (ierr) goto cleanup;
497 ierr = ApplyBoundaryConditions(user); if (ierr) goto cleanup;
498 ierr = VecCopy(user->Ucont, entry_backup); if (ierr) goto cleanup;
499 restore_entry = PETSC_TRUE;
500 ierr = VecCopy(user->Ucont, solution); if (ierr) goto cleanup;
501
502 ctx.user = user;
503 ierr = SNESSetOptionsPrefix(snes, "mom_nk_"); if (ierr) goto cleanup;
504 ierr = SNESSetType(snes, SNESNEWTONLS); if (ierr) goto cleanup;
505 ierr = SNESSetDM(snes, user->fda); if (ierr) goto cleanup;
506 ierr = SNESSetFunction(snes, NULL, MomentumNewtonKrylov_FormResidual, &ctx); if (ierr) goto cleanup;
507 ierr = MatCreateSNESMF(snes, &J); if (ierr) goto cleanup;
508 ierr = SNESSetJacobian(snes, J, J, MatMFFDComputeJacobian, NULL); if (ierr) goto cleanup;
509 ierr = SNESGetKSP(snes, &ksp); if (ierr) goto cleanup;
510 ierr = KSPSetType(ksp, KSPGMRES); if (ierr) goto cleanup;
511 ierr = KSPGetPC(ksp, &pc); if (ierr) goto cleanup;
512 ierr = PCSetType(pc, PCNONE); if (ierr) goto cleanup;
513 ierr = SNESSetFromOptions(snes); if (ierr) goto cleanup;
514 ierr = SNESMonitorSet(snes, MomentumNewtonKrylov_Monitor, &ctx, NULL); if (ierr) goto cleanup;
515 ierr = PCGetType(pc, &pc_type); if (ierr) goto cleanup;
516 ierr = PetscStrcmp(pc_type, PCNONE, &pc_is_none); if (ierr) goto cleanup;
517 if (!pc_is_none) {
519 "Newton Krylov version one requires PCNONE; option processing selected PC type '%s'.\n",
520 pc_type ? pc_type : "(unset)");
521 ierr = PETSC_ERR_SUP;
522 goto cleanup;
523 }
524
526 solve_started = PETSC_TRUE;
527 ierr = SNESSolve(snes, NULL, solution);
528 if (ierr) goto cleanup;
529 ierr = SNESGetConvergedReason(snes, &reason); if (ierr) goto cleanup;
530 ierr = SNESGetIterationNumber(snes, &nonlinear_its); if (ierr) goto cleanup;
531 ierr = SNESGetNumberFunctionEvals(snes, &function_evals); if (ierr) goto cleanup;
532 ierr = SNESGetLinearSolveIterations(snes, &linear_its); if (ierr) goto cleanup;
533 ierr = SNESGetFunctionNorm(snes, &final_norm); if (ierr) goto cleanup;
534
535 if (reason > 0) {
536 ierr = VecCopy(solution, user->Ucont); if (ierr) goto cleanup;
537 simCtx->mom_last_converged = PETSC_TRUE;
538 } else {
539 ierr = VecCopy(entry_backup, user->Ucont); if (ierr) goto cleanup;
540 simCtx->mom_last_converged = PETSC_FALSE;
541 }
542 ierr = SynchronizePeriodicStaggeredFields(user, 1, staggered_fields); if (ierr) goto cleanup;
543 ierr = ApplyBoundaryConditions(user); if (ierr) goto cleanup;
544 restore_entry = PETSC_FALSE;
545 committed = (PetscBool)(reason > 0);
546
548 "Newton Krylov momentum solve: reason=%s (%d), Newton iterations=%d, residual evaluations=%d, Krylov iterations=%d, final norm=%.6e, state=%s.\n",
549 SNESConvergedReasons[reason], (PetscInt)reason, nonlinear_its, function_evals,
550 linear_its, (double)final_norm, reason > 0 ? "committed" : "rolled back");
551 if (reason <= 0) ierr = PETSC_ERR_CONV_FAILED;
552
553cleanup:
554 /* A PETSc solve error can bypass the normal statistics path. Query whatever
555 SNES retained without replacing the primary error so a failed attempt is
556 still represented in the structured log. */
557 if (solve_started && snes) {
558 (void)SNESGetConvergedReason(snes, &reason);
559 (void)SNESGetIterationNumber(snes, &nonlinear_its);
560 (void)SNESGetNumberFunctionEvals(snes, &function_evals);
561 (void)SNESGetLinearSolveIterations(snes, &linear_its);
562 (void)SNESGetFunctionNorm(snes, &final_norm);
563 }
564 if (restore_entry && entry_backup) {
565 cleanup_ierr = VecCopy(entry_backup, user->Ucont);
566 if (!ierr) ierr = cleanup_ierr;
567 cleanup_ierr = SynchronizePeriodicStaggeredFields(user, 1, staggered_fields);
568 if (!ierr) ierr = cleanup_ierr;
569 cleanup_ierr = ApplyBoundaryConditions(user);
570 if (!ierr) ierr = cleanup_ierr;
571 simCtx->mom_last_converged = PETSC_FALSE;
572 committed = PETSC_FALSE;
573 }
574 if (ctx.history_file) {
575 (void)fclose(ctx.history_file);
576 ctx.history_file = NULL;
577 }
578 if (solve_started) {
579 MomentumNewtonKrylov_WriteSummary(&ctx, reason, nonlinear_its, function_evals,
580 linear_its, final_norm, committed);
581 }
582 if (rhs_created) {
583 cleanup_ierr = VecDestroy(&user->Rhs);
584 if (!ierr) ierr = cleanup_ierr;
585 }
586 cleanup_ierr = VecDestroy(&entry_backup); if (!ierr) ierr = cleanup_ierr;
587 cleanup_ierr = VecDestroy(&solution); if (!ierr) ierr = cleanup_ierr;
588 cleanup_ierr = MatDestroy(&J); if (!ierr) ierr = cleanup_ierr;
589 cleanup_ierr = SNESDestroy(&snes); if (!ierr) ierr = cleanup_ierr;
590 PetscFunctionReturn(ierr);
591}
#define LOG(scope, level, fmt,...)
Logging macro for PETSc-based applications with scope control.
Definition logging.h:83
@ LOG_ERROR
Critical errors that may halt the program.
Definition logging.h:28
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.
static void MomentumNewtonKrylov_OpenHistory(MomentumNewtonKrylovContext *ctx)
Opens the optional rank-zero Newton iteration-history file.
static PetscErrorCode MomentumNewtonKrylov_Validate(UserCtx *user)
Rejects configurations outside the audited version-one feature set.
static PetscErrorCode MomentumNewtonKrylov_FormResidual(SNES snes, Vec X, Vec F, void *ctx)
Adapts a PETSc trial vector to the existing momentum residual path.
static PetscErrorCode MomentumNewtonKrylov_Monitor(SNES snes, PetscInt iteration, PetscReal norm, void *ctx)
Captures SNES iteration norms and optionally writes PICurv history rows.
Vec Rhs
Definition variables.h:912
PetscBool mom_last_converged
Definition variables.h:738
Vec Ucont
Definition variables.h:904
Here is the call graph for this function:

◆ MomentumSolver_DualTime_Picard_JamesonRK()

PetscErrorCode MomentumSolver_DualTime_Picard_JamesonRK ( UserCtx user,
IBMNodes ibm,
FSInfo fsi 
)

Solves the momentum equations using dual-time Picard iteration with Jameson RK smoothing.


GLOSSARY & THEORETICAL BASIS

  1. METHODOLOGY: Dual-Time Stepping (Pseudo-Time Integration) We aim to solve the implicit BDF equation: R_spatial(U) + dU/dt_physical = 0. We do this by introducing a fictitious "Pseudo-Time" (tau) and iterating to steady state: dU/d(tau) = - [ R_spatial(U) + BDF_Terms(U) ] When dU/d(tau) -> 0, the physical time step is satisfied.
  2. ALGORITHM: Fixed-Point Iteration with Explicit Runge-Kutta This is technically a Fixed-Point iteration on the operator: U_new = U_old + pseudo_dtau * alfa_stage * Total_Residual(U_old) where pseudo_dtau = pseudo_cfl / lambda_max is the spectral-radius-based pseudo-time step (lambda_max = global max convective spectral radius). This makes pseudo_cfl a true dimensionless Courant number, independent of the physical time step dt. We use a 4-Stage Explicit RK scheme (Jameson-Schmidt-Turkel coeffs) to smooth errors.
  3. STABILITY: Adaptive Pseudo-CFL Trial Acceptance and Rollback If a pseudo-time trial causes excessive residual growth, the solver restores the

previous accepted state, reduces the global pseudo-CFL, and retries.

VARIABLE MAPPING

– Physics Variables (Legacy Names Kept) – ti : Physical Time Step Index. dt : Physical Time Step size (Delta t). st : Pseudo-Time Step size (Delta tau). alfa : Runge-Kutta stage coefficients {1/4, 1/3, 1/2, 1}. – Convergence & Solver Control (Renamed) – pseudo_iter : Counter for the inner dual-time loop. pseudo_dtau : Adaptive pseudo-time step [physical time], = pseudo_cfl / lambda_max. lambda_max : Global max convective spectral radius [1/s] from the current field. delta_sol_norm : The L_inf norm of the change in solution (dU).

resid_norm : The L_inf norm of the Total Residual (RHS).

Parameters
userPrimary UserCtx input for the operation.
ibmParameter ibm passed to MomentumSolver_DualTime_Picard_JamesonRK().
fsiParameter fsi passed to MomentumSolver_DualTime_Picard_JamesonRK().
Returns
PetscErrorCode 0 on success.
Note
Testing status: This solver is covered primarily through runtime smoke and orchestration tests. A smaller direct invariant-style positive-path harness remains part of the next-gap backlog.

Solves the momentum equations using dual-time Picard iteration with Jameson RK smoothing.

Local to this translation unit.

Definition at line 742 of file momentumsolvers.c.

743{
744 (void)ibm;
745 (void)fsi;
746 // --- CONTEXT ACQUISITION BLOCK ---
747 SimCtx *simCtx = user[0].simCtx;
748 const PetscInt block_number = simCtx->block_number;
749
750 // Legacy Names (Physics/Time) - Kept for brevity in formulas
751 const PetscInt ti = simCtx->step;
752 const PetscReal dt = simCtx->dt; // Physical Time Step
753 /* cfl: initial pseudo-CFL Courant number (dimensionless, flow-independent since Phase 3).
754 * Actual dtau = cfl / lambda_max, computed after the spectral radius estimate below. */
755 const PetscReal cfl = simCtx->pseudo_cfl;
756 const PetscReal alfa[] = {0.25, 1.0/3.0, 0.5, 1.0}; // Jameson RK smoothing coefficients
757 Vec *pRhs; // Per-block backup of Rhs at last accepted pseudo-state.
758
759 // State flags
760 PetscBool force_restart = PETSC_FALSE;
761
762 // Renamed Solver Parameters
763 const PetscInt max_pseudo_steps = simCtx->mom_max_pseudo_steps; // Max Dual-Time Iterations
764 const PetscReal tol_abs_delta = simCtx->mom_atol; // Stop if |dU| < tol
765 const PetscReal tol_rtol_delta = simCtx->mom_rtol; // Stop if |dU|/|dU0| < tol
766 // --- END CONTEXT ACQUISITION BLOCK ---
767
768 PetscErrorCode ierr;
769 PetscMPIInt rank;
770 PetscInt istage, pseudo_iter;
771 PetscInt accepted_iter = 0, rejected_iter = 0, recovery_streak = 0;
772 PetscReal ts, te, cput;
773
774 // --- Global Convergence Metrics ---
775 PetscReal global_norm_delta = 10.0;
776 PetscReal global_rel_delta = 1.0;
777 PetscReal global_norm_resid = 1.0;
778 PetscReal global_rel_resid = 1.0;
779 PetscReal smoothed_trial_ratio = 1.0; /* EMA-smoothed ratio; initialized neutral */
780
781 // --- Spectral-radius-based pseudo-time step ---
782 /* lambda_max: global maximum spectral radius [1/s], computed once per physical timestep.
783 * pseudo_dtau[bi]: physical pseudo-time step dtau = pseudo_cfl / lambda_max [time].
784 * Unlike the old scheme (dtau = pseudo_cfl * dt), this is independent of dt and makes
785 * pseudo_cfl a true Courant number. (2.83 is only the scalar imaginary-axis RK limit,
786 * not a generally-stable value for the actual operator -- to be characterized in A4.)
787 * dtau_min / dtau_max: per-timestep bounds derived from simCtx->min/max_pseudo_cfl. */
788 PetscReal lambda_max = 0.0;
789 PetscReal dtau_min, dtau_max;
790
791 // --- Local Metric Arrays (Per Block) ---
792 PetscReal *delta_sol_norm_init, *delta_sol_norm_prev, *delta_sol_norm_curr, *delta_sol_rel_curr;
793 PetscReal *resid_norm_init, *resid_norm_prev, *resid_norm_curr, *resid_rel_curr;
794 PetscReal *pseudo_dtau; /* per-block pseudo-time step dtau [physical time, NOT dtau/dt] */
795 PetscReal *trial_ratio_log; /* per-block step-to-step residual ratio (for diagnostics) */
796 PetscReal last_accepted_resid; /* last accepted |R|, for final summary */
797
798 PetscFunctionBeginUser;
800 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
801 LOG_ALLOW(GLOBAL, LOG_INFO, "Executing Dual-Time Momentum Solver for %d block(s).\n", block_number);
802
803 // --- Allocate metric arrays ---
804 ierr = PetscMalloc2(block_number, &delta_sol_norm_init, block_number, &delta_sol_norm_prev); CHKERRQ(ierr);
805 ierr = PetscMalloc2(block_number, &delta_sol_norm_curr, block_number, &delta_sol_rel_curr); CHKERRQ(ierr);
806 ierr = PetscMalloc2(block_number, &resid_norm_init, block_number, &resid_norm_prev); CHKERRQ(ierr);
807 ierr = PetscMalloc2(block_number, &resid_norm_curr, block_number, &pseudo_dtau); CHKERRQ(ierr);
808 ierr = PetscMalloc1(block_number, &resid_rel_curr); CHKERRQ(ierr);
809 ierr = PetscMalloc1(block_number, &trial_ratio_log); CHKERRQ(ierr);
810 ierr = PetscMalloc1(block_number, &pRhs); CHKERRQ(ierr);
811 last_accepted_resid = 0.0;
812
813 ierr = PetscTime(&ts); CHKERRQ(ierr);
814
815 // --- 1. Pre-Loop Initialization ---
816
817 if (block_number > 1) {
818 // ierr = Block_Interface_U(user); CHKERRQ(ierr);
819 }
820
821 for (PetscInt bi = 0; bi < block_number; bi++) {
822 ierr = ApplyBoundaryConditions(&user[bi]); CHKERRQ(ierr);
823
824 // Immersed boundary interpolation (if enabled)
825
826 // Allocate workspace
827 ierr = VecDuplicate(user[bi].Ucont, &user[bi].Rhs); CHKERRQ(ierr);
828 ierr = VecDuplicate(user[bi].Rhs, &pRhs[bi]); CHKERRQ(ierr);
829 ierr = VecDuplicate(user[bi].Ucont, &user[bi].dUcont); CHKERRQ(ierr);
830 ierr = VecDuplicate(user[bi].Ucont, &user[bi].pUcont); CHKERRQ(ierr);
831
832 // Initialize Backup (pUcont) with current state
833 ierr = VecCopy(user[bi].Ucont, user[bi].pUcont); CHKERRQ(ierr);
834
835 // --- Calculate Initial Total Residual (Spatial + Temporal) ---
836 ierr = ComputeTotalResidual(&user[bi]); CHKERRQ(ierr);
837
838 // Backup Rhs with current state
839 ierr = VecCopy(user[bi].Rhs, pRhs[bi]); CHKERRQ(ierr);
840
841 // Compute Initial Norms
842 ierr = VecNorm(user[bi].Rhs, NORM_INFINITY, &resid_norm_init[bi]); CHKERRQ(ierr);
843
844 // Initialize history for backtracking logic
845 resid_norm_prev[bi] = resid_norm_init[bi]; /* meaningful ratio on first iteration */
846 delta_sol_norm_prev[bi] = 1000.0;
847 /* pseudo_dtau[bi] is set after ComputeGlobalSpectralRadiusEstimate below. */
848
849 LOG_ALLOW(GLOBAL,LOG_INFO," Block %d | Max RHS = %.6f | initial pseudo-CFL = %.4f .\n", bi, resid_norm_init[bi], cfl);
850 }
851
852 /* --- Spectral Radius Estimate (computed once per physical timestep) ---
853 * lambda_max [1/s] is the global maximum of (face_flux_sum * inverse_cell_volume)
854 * over all owned cells across all MPI ranks. See ComputeGlobalSpectralRadiusEstimate().
855 *
856 * dtau = pseudo_cfl / lambda_max (pseudo_cfl is now a true Courant number)
857 * dtau_min/max are derived from the user-specified min/max_pseudo_cfl bounds.
858 *
859 * The BDF2 lower bound inside the helper ensures lambda_max >= 1.5/dt, so dtau
860 * remains finite even when the velocity field is zero (startup / stagnant ICs). */
861 ierr = ComputeGlobalSpectralRadiusEstimate(user, block_number, dt, &lambda_max); CHKERRQ(ierr);
862 dtau_min = simCtx->min_pseudo_cfl / lambda_max;
863 dtau_max = simCtx->max_pseudo_cfl / lambda_max;
864
865 /* Initialise per-block pseudo-time step from the warm-start CFL (carried from last timestep). */
866 for (PetscInt bi = 0; bi < block_number; bi++) {
867 pseudo_dtau[bi] = cfl / lambda_max; /* dtau [physical time], NOT a fraction of dt */
868 }
869
870 /* --- SHADOW MODE: new operator-scaled stability estimate (diagnostic only) ---
871 * Computes the Workstream-A estimate alongside the legacy one and logs a compact
872 * comparison. The legacy estimate above continues to drive production dtau; the
873 * shadow estimate changes nothing. Enable with -mom_stability_shadow. */
874 {
875 PetscBool shadow = PETSC_FALSE;
876 ierr = PetscOptionsGetBool(NULL, NULL, "-mom_stability_shadow", &shadow, NULL); CHKERRQ(ierr);
877 if (shadow) {
879 ierr = ComputeMomentumStabilityEstimate(user, block_number, dt, MOM_STAB_CAND_C, &rep); CHKERRQ(ierr);
880 const char *lim = (rep.limiter==MOM_STAB_LIMITER_TIME) ? "time"
881 : (rep.limiter==MOM_STAB_LIMITER_CONVECTION) ? "convection" : "viscosity";
882 /* Detailed shadow comparison at DEBUG only (gated by level + function allowlist);
883 never printed unconditionally. Enable in tests via the logging controls. */
885 "Momentum scale [shadow]: legacy_dtau=%.4e new_dtau=%.4e ratio=%.4f limiter=%s | "
886 "lambda_legacy=%.4e lambda_new=%.4e (B=%.4e C=%.4e D=%.4e) | "
887 "lt=%.4e lc=%.4e lv=%.4e | cell=(%d,%d,%d) blk=%d class=%d onesided=%d\n",
888 cfl / lambda_max, cfl / rep.lambda, rep.lambda / lambda_max, lim,
889 lambda_max, rep.lambda, rep.lambda_B, rep.lambda_C, rep.lambda_D,
890 rep.lambda_t, rep.lambda_c, rep.lambda_v,
891 (int)rep.ci, (int)rep.cj, (int)rep.ck, (int)rep.cblock, (int)rep.cclass, (int)rep.one_sided);
892 }
893 }
894
896 "Dual-time solver: lambda_max=%.4e [1/s] dtau_init=%.4e dtau range [%.4e, %.4e] "
897 "CFL range [%.4f, %.4f] rejection_threshold=%.3f (EMA alpha=%.2f) "
898 "growth=%.3f reduction=%.3f max_accepted=%d.\n",
899 lambda_max, cfl / lambda_max, dtau_min, dtau_max,
900 simCtx->min_pseudo_cfl, simCtx->max_pseudo_cfl,
902 simCtx->mom_ratio_ema_alpha,
904 max_pseudo_steps);
905
906 // --- 2. Main Pseudo-Time Iteration Loop ---
907 pseudo_iter = 0;
908 PetscBool residual_convergence_enabled =
909 (PetscBool)(simCtx->mom_resid_atol > 0.0 || simCtx->mom_resid_rtol > 0.0);
910 PetscBool converged = PETSC_FALSE;
911 PetscBool last_trial_nonfinite = PETSC_FALSE;
912 /* Rejected iterations no longer consume the accepted-iteration budget.
913 A hard cap of 3x max_pseudo_steps guards against infinite rejection loops. */
914 const PetscInt max_total_attempts = max_pseudo_steps * 3;
915 while (!converged && accepted_iter < max_pseudo_steps && pseudo_iter < max_total_attempts)
916 {
917 pseudo_iter++;
918 force_restart = PETSC_FALSE;
919
920 // Every attempt starts from the last globally accepted state.
921 for (PetscInt bi = 0; bi < block_number; bi++) {
922 ierr = VecCopy(user[bi].pUcont, user[bi].Ucont); CHKERRQ(ierr);
923 ierr = VecCopy(pRhs[bi], user[bi].Rhs); CHKERRQ(ierr);
924 const char *staggered_fields[] = {"Ucont"};
925 ierr = SynchronizePeriodicStaggeredFields(&user[bi], 1, staggered_fields); CHKERRQ(ierr);
926 ierr = ApplyBoundaryConditions(&user[bi]); CHKERRQ(ierr);
927 }
928
929 for (PetscInt bi = 0; bi < block_number; bi++) {
930
931 // === 4-Stage Jameson RK Smoothing Loop ===
932 for (istage = 0; istage < 4; istage++) {
933
934 LOG_ALLOW(GLOBAL,LOG_TRACE," Pseudo-Iter: %d | RK-Stage: %d\n", pseudo_iter, istage);
935
936 /* RK Update: U_new = U_old + (dtau * alpha) * Residual
937 * pseudo_dtau[bi] IS dtau [physical time] — no further multiplication by dt.
938 * (In the old scheme dtau = pseudo_cfl * dt; here dtau = pseudo_cfl / lambda_max.) */
939 ierr = VecWAXPY(user[bi].Ucont,
940 pseudo_dtau[bi] * alfa[istage],
941 user[bi].Rhs,
942 user[bi].pUcont); CHKERRQ(ierr);
943
944 // Sync Ghosts & Re-apply BCs for intermediate stage
945 const char *staggered_fields[] = {"Ucont"};
946 ierr = SynchronizePeriodicStaggeredFields(&user[bi], 1, staggered_fields); CHKERRQ(ierr);
947
948 ierr = ApplyBoundaryConditions(&user[bi]); CHKERRQ(ierr);
949
950 // --- Re-calculate Total Residual for next stage ---
951 ierr = ComputeTotalResidual(&user[bi]); CHKERRQ(ierr);
952
953 } // End RK Stages
954
955 // Immersed boundary interpolation (if enabled)
956
957 // === Convergence Metrics Calculation ===
958
959 // Calculate dU = U_current - U_backup
960 ierr = VecWAXPY(user[bi].dUcont, -1.0, user[bi].pUcont, user[bi].Ucont); CHKERRQ(ierr);
961
962 // Compute Infinity Norms
963 ierr = VecNorm(user[bi].dUcont, NORM_INFINITY, &delta_sol_norm_curr[bi]); CHKERRQ(ierr);
964 ierr = VecNorm(user[bi].Rhs, NORM_INFINITY, &resid_norm_curr[bi]); CHKERRQ(ierr);
965
966 // Normalize relative metrics
967 if (pseudo_iter == 1) {
968 delta_sol_norm_init[bi] = delta_sol_norm_curr[bi];
969 delta_sol_rel_curr[bi] = 1.0;
970 resid_rel_curr[bi] = 1.0;
971 // resid_norm_init[bi] set correctly before the loop — do not overwrite
972 } else {
973 if (delta_sol_norm_init[bi] > 1.0e-10)
974 delta_sol_rel_curr[bi] = delta_sol_norm_curr[bi] / delta_sol_norm_init[bi];
975 else
976 delta_sol_rel_curr[bi] = 0.0;
977
978 if(resid_norm_init[bi] > 1.0e-10)
979 resid_rel_curr[bi] = resid_norm_curr[bi] / resid_norm_init[bi];
980 else
981 resid_rel_curr[bi] = 0.0;
982 }
983
984 /* Pre-compute per-block step-to-step trial ratio for later file logging. */
985 {
986 const PetscReal resid_floor_log = 1.0e-30;
987 if (resid_norm_prev[bi] > resid_floor_log)
988 trial_ratio_log[bi] = resid_norm_curr[bi] / resid_norm_prev[bi];
989 else if (resid_norm_curr[bi] <= resid_floor_log)
990 trial_ratio_log[bi] = 0.0;
991 else
992 trial_ratio_log[bi] = PETSC_MAX_REAL;
993 }
994 } // End loop over blocks
995
996 // --- Update Global Convergence Criteria ---
997 global_norm_delta = -1.0e20;
998 global_rel_delta = -1.0e20;
999 global_norm_resid = -1.0e20;
1000 global_rel_resid = -1.0e20;
1001
1002 for (PetscInt bi = 0; bi < block_number; bi++) {
1003 global_norm_delta = PetscMax(delta_sol_norm_curr[bi], global_norm_delta);
1004 global_rel_delta = PetscMax(delta_sol_rel_curr[bi], global_rel_delta);
1005 global_norm_resid = PetscMax(resid_norm_curr[bi], global_norm_resid);
1006 global_rel_resid = PetscMax(resid_rel_curr[bi], global_rel_resid);
1007 }
1008 ierr = PetscTime(&te); CHKERRQ(ierr);
1009 cput = te - ts;
1010 LOG_ALLOW(GLOBAL, LOG_INFO, " Pseudo-Iter(k) %d: |dUk|=%e, |dUk|/|dU0| = %e, |Rk|/|R0| = %e, CPU=%.2fs\n",
1011 pseudo_iter, global_norm_delta, global_rel_delta, global_rel_resid, cput);
1012
1013 // === Adaptive Pseudo-CFL Trial Acceptance and Rollback ===
1014 const PetscReal resid_floor = 1.0e-30;
1015 PetscReal global_trial_ratio = 0.0;
1016 PetscBool global_nonfinite = PETSC_FALSE;
1017 for (PetscInt bi = 0; bi < block_number; bi++) {
1018 PetscReal ratio;
1019 if (resid_norm_prev[bi] > resid_floor) {
1020 ratio = resid_norm_curr[bi] / resid_norm_prev[bi];
1021 } else if (resid_norm_curr[bi] <= resid_floor) {
1022 ratio = 0.0;
1023 } else {
1024 ratio = PETSC_MAX_REAL;
1025 }
1026 global_trial_ratio = PetscMax(global_trial_ratio, ratio);
1027 global_nonfinite = (PetscBool)(global_nonfinite ||
1028 PetscIsInfOrNanReal(delta_sol_norm_curr[bi]) ||
1029 PetscIsInfOrNanReal(resid_norm_curr[bi]) ||
1030 PetscIsInfOrNanReal(ratio));
1031 }
1032
1033 PetscReal reduced_trial_ratio;
1034 PetscMPIInt local_nonfinite = global_nonfinite ? 1 : 0;
1035 PetscMPIInt reduced_nonfinite = 0;
1036 ierr = MPI_Allreduce(&global_trial_ratio, &reduced_trial_ratio, 1, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD); CHKERRQ(ierr);
1037 ierr = MPI_Allreduce(&local_nonfinite, &reduced_nonfinite, 1, MPI_INT, MPI_LOR, PETSC_COMM_WORLD); CHKERRQ(ierr);
1038 global_trial_ratio = reduced_trial_ratio;
1039 global_nonfinite = reduced_nonfinite ? PETSC_TRUE : PETSC_FALSE;
1040 last_trial_nonfinite = global_nonfinite;
1041
1042 /* EMA-smooth the trial ratio to tolerate occasional non-monotonic residual bumps.
1043 Non-finite trials bypass smoothing and always trigger rollback. */
1044 if (!global_nonfinite) {
1045 smoothed_trial_ratio = simCtx->mom_ratio_ema_alpha * global_trial_ratio
1046 + (1.0 - simCtx->mom_ratio_ema_alpha) * smoothed_trial_ratio;
1047 }
1049 " [k=%d] raw_ratio=%.4e smoothed_ratio=%.4e (threshold=%.3f) | "
1050 "|R_prev|=%.6e | |R_curr|=%.6e | CFL=%.6f\n",
1051 pseudo_iter, global_trial_ratio, smoothed_trial_ratio,
1053 resid_norm_prev[0], resid_norm_curr[0], pseudo_dtau[0]);
1054
1055 if (simCtx->no_pseudo_cfl_backtrack) {
1056 /* Diagnostic mode: commit every finite trial; only non-finite triggers rollback. */
1057 force_restart = global_nonfinite;
1058 } else {
1059 force_restart = (PetscBool)(global_nonfinite ||
1060 smoothed_trial_ratio > simCtx->mom_dt_jameson_residual_norm_noise_allowance_factor);
1061 }
1062
1063 if (force_restart) {
1064 /* old_dtau: dtau used for this (rejected) trial [physical time].
1065 * next_dtau: reduced dtau for the retry, clamped to dtau_min.
1066 * Both are physical-time values; the effective CFL = dtau * lambda_max. */
1067 PetscReal old_dtau = pseudo_dtau[0];
1068 PetscReal next_dtau = PetscMax(dtau_min, old_dtau * simCtx->pseudo_cfl_reduction_factor);
1069 rejected_iter++;
1070 recovery_streak = 0;
1071 for (PetscInt bi = 0; bi < block_number; bi++) {
1072 ierr = VecCopy(user[bi].pUcont, user[bi].Ucont); CHKERRQ(ierr);
1073 ierr = VecCopy(pRhs[bi], user[bi].Rhs); CHKERRQ(ierr);
1074 const char *staggered_fields[] = {"Ucont"};
1075 ierr = SynchronizePeriodicStaggeredFields(&user[bi], 1, staggered_fields); CHKERRQ(ierr);
1076 ierr = ApplyBoundaryConditions(&user[bi]); CHKERRQ(ierr);
1077 pseudo_dtau[bi] = next_dtau;
1078 }
1080 " Trial %d REJECTED (raw_ratio=%.4e, smoothed=%.4e, nonfinite=%d); "
1081 "dtau %.4e -> %.4e (cfl_eff %.4f -> %.4f)%s\n",
1082 pseudo_iter, global_trial_ratio, smoothed_trial_ratio, (int)global_nonfinite,
1083 old_dtau, next_dtau, old_dtau * lambda_max, next_dtau * lambda_max,
1084 (old_dtau == next_dtau) ? " [AT FLOOR — no dtau reduction]" : "");
1085 /* Log rejected trial to file before rolling back. */
1086 if (!rank) {
1087 for (PetscInt bi = 0; bi < block_number; bi++) {
1088 FILE *f;
1089 char filen[PETSC_MAX_PATH_LEN + 128];
1090 ierr = PetscSNPrintf(filen, sizeof(filen),
1091 "%s/Momentum_Solver_DualTime_Picard_Jameson_RK_History_Block_%1d.log",
1092 simCtx->log_dir, bi); CHKERRQ(ierr);
1093 if (simCtx->step == simCtx->StartStep + 1 && pseudo_iter == 1 && !simCtx->continueMode)
1094 f = fopen(filen, "w");
1095 else
1096 f = fopen(filen, "a");
1097 if (simCtx->continueMode && simCtx->step == simCtx->StartStep + 1 && pseudo_iter == 1)
1098 PetscFPrintf(PETSC_COMM_WORLD, f, "# Continuation from step %" PetscInt_FMT "\n", simCtx->StartStep);
1099 /* Log format: dtau [physical time] + cfl_eff [dimensionless Courant number = dtau*lambda_max] */
1100 PetscFPrintf(PETSC_COMM_WORLD, f,
1101 "Step: %d | PseudoIter(k): %d | dtau: %.6e | cfl_eff: %.4f | |dUk|: %le | "
1102 "|dUk|/|dU0|: %le | |Rk|: %le | |Rk|/|R0|: %le | "
1103 "trial_ratio: %le | smoothed_ratio: %le | status: rejected | "
1104 "dtau_after: %.6e | cfl_eff_after: %.4f\n",
1105 (int)ti, (int)pseudo_iter, old_dtau, old_dtau * lambda_max,
1106 delta_sol_norm_curr[bi], delta_sol_rel_curr[bi],
1107 resid_norm_curr[bi], resid_rel_curr[bi],
1108 trial_ratio_log[bi], smoothed_trial_ratio,
1109 next_dtau, next_dtau * lambda_max);
1110 fclose(f);
1111 }
1112 }
1113 if (old_dtau <= dtau_min) {
1114 if (global_nonfinite) {
1115 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_CONV_FAILED,
1116 "Momentum solver produced a non-finite trial at minimum pseudo-CFL.");
1117 }
1118 /* Finite rejection at the dtau floor: reducing further is impossible.
1119 Break instead of retrying bit-identically until max_pseudo_steps. */
1121 " Trial %d REJECTED (ratio=%.4e) at minimum dtau (%.4e, cfl_eff=%.4f) "
1122 "with no further reduction possible; breaking retry loop.\n",
1123 pseudo_iter, global_trial_ratio, old_dtau, old_dtau * lambda_max);
1124 break;
1125 }
1126 continue;
1127 }
1128
1129 accepted_iter++;
1130 last_trial_nonfinite = PETSC_FALSE;
1131 last_accepted_resid = resid_norm_curr[0]; /* track for final summary */
1132 for (PetscInt bi = 0; bi < block_number; bi++) {
1133 ierr = VecCopy(user[bi].Ucont, user[bi].pUcont); CHKERRQ(ierr);
1134 ierr = VecCopy(user[bi].Rhs, pRhs[bi]); CHKERRQ(ierr);
1135 resid_norm_prev[bi] = resid_norm_curr[bi];
1136 delta_sol_norm_prev[bi] = delta_sol_norm_curr[bi];
1137 }
1138
1139 /* old_dtau: dtau used for this (accepted) trial.
1140 * next_dtau: dtau for the next trial, grown/reduced based on convergence rate.
1141 * Bounds (dtau_min, dtau_max) are derived from simCtx->min/max_pseudo_cfl / lambda_max. */
1142 PetscReal old_dtau = pseudo_dtau[0]; /* save before any update */
1143 PetscReal next_dtau = old_dtau;
1144 if (global_trial_ratio < 0.90) {
1145 /* Residual decreasing fast: grow dtau to accelerate convergence. */
1146 next_dtau *= simCtx->pseudo_cfl_growth_factor;
1147 recovery_streak = 0;
1148 } else if (global_trial_ratio <= 1.0) {
1149 /* Residual barely decreasing: grow only after 3 clean consecutive trials. */
1150 recovery_streak++;
1151 if (recovery_streak >= 3) {
1152 next_dtau *= simCtx->pseudo_cfl_growth_factor;
1153 recovery_streak = 0;
1154 }
1155 } else {
1156 /* Accepted but residual slightly grew (within EMA noise allowance): reduce dtau. */
1157 next_dtau *= simCtx->pseudo_cfl_reduction_factor;
1158 recovery_streak = 0;
1159 }
1160 next_dtau = PetscMin(next_dtau, dtau_max);
1161 next_dtau = PetscMax(next_dtau, dtau_min);
1162 for (PetscInt bi = 0; bi < block_number; bi++) pseudo_dtau[bi] = next_dtau;
1163
1165 " Trial %d ACCEPTED (raw_ratio=%.4e, smoothed=%.4e); |dU|=%.6e | "
1166 "dtau %.4e -> %.4e (cfl_eff %.4f -> %.4f)\n",
1167 pseudo_iter, global_trial_ratio, smoothed_trial_ratio, global_norm_delta,
1168 old_dtau, next_dtau, old_dtau * lambda_max, next_dtau * lambda_max);
1169
1170 /* --- Post-decision file logging (one row per accepted trial) --- */
1171 if (!rank) {
1172 for (PetscInt bi = 0; bi < block_number; bi++) {
1173 FILE *f;
1174 char filen[PETSC_MAX_PATH_LEN + 128];
1175 ierr = PetscSNPrintf(filen, sizeof(filen),
1176 "%s/Momentum_Solver_DualTime_Picard_Jameson_RK_History_Block_%1d.log",
1177 simCtx->log_dir, bi); CHKERRQ(ierr);
1178 if (simCtx->step == simCtx->StartStep + 1 && pseudo_iter == 1 && !simCtx->continueMode)
1179 f = fopen(filen, "w");
1180 else
1181 f = fopen(filen, "a");
1182 if (simCtx->continueMode && simCtx->step == simCtx->StartStep + 1 && pseudo_iter == 1)
1183 PetscFPrintf(PETSC_COMM_WORLD, f, "# Continuation from step %" PetscInt_FMT "\n", simCtx->StartStep);
1184 /* Log format: dtau [physical time] + cfl_eff [dimensionless Courant number = dtau*lambda_max] */
1185 PetscFPrintf(PETSC_COMM_WORLD, f,
1186 "Step: %d | PseudoIter(k): %d | dtau: %.6e | cfl_eff: %.4f | |dUk|: %le | "
1187 "|dUk|/|dU0|: %le | |Rk|: %le | |Rk|/|R0|: %le | "
1188 "trial_ratio: %le | smoothed_ratio: %le | status: accepted | "
1189 "dtau_after: %.6e | cfl_eff_after: %.4f\n",
1190 (int)ti, (int)pseudo_iter, old_dtau, old_dtau * lambda_max,
1191 delta_sol_norm_curr[bi], delta_sol_rel_curr[bi],
1192 resid_norm_curr[bi], resid_rel_curr[bi],
1193 trial_ratio_log[bi], smoothed_trial_ratio,
1194 next_dtau, next_dtau * lambda_max);
1195 fclose(f);
1196 }
1197 }
1198
1199 PetscBool update_pass;
1200 PetscBool residual_pass = PETSC_TRUE;
1201 if (residual_convergence_enabled) {
1202 update_pass = (PetscBool)(global_norm_delta <= tol_abs_delta && global_rel_delta <= tol_rtol_delta);
1203 residual_pass = (PetscBool)(
1204 (simCtx->mom_resid_atol > 0.0 && global_norm_resid <= simCtx->mom_resid_atol) ||
1205 (simCtx->mom_resid_rtol > 0.0 && global_rel_resid <= simCtx->mom_resid_rtol));
1206 } else {
1207 update_pass = (PetscBool)(global_norm_delta <= tol_abs_delta && global_rel_delta <= tol_rtol_delta);
1208 }
1209 converged = (PetscBool)(update_pass && residual_pass);
1210
1211 if (block_number > 1) {
1212 // ierr = Block_Interface_U(user); CHKERRQ(ierr);
1213 }
1214 } // End while loop
1215
1216 if (last_trial_nonfinite) {
1217 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_CONV_FAILED,
1218 "Momentum solver exhausted its attempt limit while recovering from a non-finite trial.");
1219 }
1220
1221 /* Convert the final dtau back to a CFL number for the warm-start of the next physical timestep.
1222 * simCtx->pseudo_cfl stores a dimensionless Courant number (dtau * lambda_max); the next
1223 * timestep's lambda_max may differ as the flow evolves, so storing the CFL number (not dtau)
1224 * ensures the warm-start adapts correctly to the new spectral radius. */
1225 PetscReal next_dtau_start = pseudo_dtau[0];
1226 PetscReal next_cfl_warmstart = next_dtau_start * lambda_max; /* CFL = dtau * lambda_max */
1227
1228 if (!rank) {
1230 " Step %d finished: dtau=%.4e cfl_eff=%.4f lambda_max=%.4e [1/s]. "
1231 "Next step warm-starts at cfl=%.4f.\n",
1232 simCtx->step, next_dtau_start, next_cfl_warmstart, lambda_max, next_cfl_warmstart);
1233 }
1234
1235 /* Store the CFL number (not dtau) so it remains meaningful across physical timesteps. */
1236 simCtx->pseudo_cfl = next_cfl_warmstart;
1237 simCtx->mom_last_lambda_max = lambda_max;
1238
1239 /* Expose the last accepted finite state. On converged or floor-break exits pUcont==Ucont
1240 already (VecCopy would be an identity), but ghosts may have gone stale; always re-sync
1241 and re-apply BCs. Only restore the solution vector itself when nothing was ever accepted. */
1242 if (accepted_iter == 0) {
1243 for (PetscInt bi = 0; bi < block_number; bi++) {
1244 ierr = VecCopy(user[bi].pUcont, user[bi].Ucont); CHKERRQ(ierr);
1245 ierr = VecCopy(pRhs[bi], user[bi].Rhs); CHKERRQ(ierr);
1246 }
1247 }
1248 for (PetscInt bi = 0; bi < block_number; bi++) {
1249 const char *staggered_fields[] = {"Ucont"};
1250 ierr = SynchronizePeriodicStaggeredFields(&user[bi], 1, staggered_fields); CHKERRQ(ierr);
1251 ierr = ApplyBoundaryConditions(&user[bi]); CHKERRQ(ierr);
1252 }
1253
1254 simCtx->mom_last_converged = converged;
1255 if (!converged) {
1256 PetscPrintf(PETSC_COMM_WORLD,
1257 "[WARNING] Momentum solver step %d: reached %d total attempts (%d accepted, %d rejected) "
1258 "without convergence; continuing from last accepted finite state.\n",
1259 (int)ti, pseudo_iter, accepted_iter, rejected_iter);
1260 }
1261 if (accepted_iter == 0) {
1262 PetscPrintf(PETSC_COMM_WORLD,
1263 "[WARNING] Momentum solver step %d: no pseudo-time trials were accepted; "
1264 "retaining physical-step entry state.\n", (int)ti);
1265 }
1267 "Momentum solver finished: %d attempts (%d accepted, %d rejected) of %d max accepted / %d hard cap, "
1268 "converged=%s, last accepted |R|=%.6e, last accepted |dU|=%.6e, "
1269 "next_dtau=%.4e (cfl=%.4f).\n",
1270 pseudo_iter, accepted_iter, rejected_iter, max_pseudo_steps, max_total_attempts,
1271 converged ? "yes" : "no",
1272 (accepted_iter > 0) ? last_accepted_resid : resid_norm_init[0],
1273 (accepted_iter > 0) ? delta_sol_norm_prev[0] : 0.0,
1274 next_dtau_start, next_cfl_warmstart);
1275
1276 // --- Final Cleanup ---
1277 for (PetscInt bi = 0; bi < block_number; bi++) {
1278 ierr = VecDestroy(&user[bi].Rhs); CHKERRQ(ierr);
1279 ierr = VecDestroy(&user[bi].dUcont); CHKERRQ(ierr);
1280 ierr = VecDestroy(&user[bi].pUcont); CHKERRQ(ierr);
1281 ierr = VecDestroy(&pRhs[bi]); CHKERRQ(ierr);
1282 }
1283 ierr = PetscFree(pRhs); CHKERRQ(ierr);
1284
1285 ierr = PetscFree2(delta_sol_norm_init, delta_sol_norm_prev);CHKERRQ(ierr);
1286 ierr = PetscFree2(delta_sol_norm_curr, delta_sol_rel_curr);CHKERRQ(ierr);
1287 ierr = PetscFree2(resid_norm_init, resid_norm_prev);CHKERRQ(ierr);
1288 ierr = PetscFree2(resid_norm_curr, pseudo_dtau); CHKERRQ(ierr);
1289 ierr = PetscFree(resid_rel_curr); CHKERRQ(ierr);
1290 ierr = PetscFree(trial_ratio_log); CHKERRQ(ierr);
1291
1293 PetscFunctionReturn(0);
1294}
@ LOG_TRACE
Very fine-grained tracing information for in-depth debugging.
Definition logging.h:32
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:29
static PetscErrorCode ComputeGlobalSpectralRadiusEstimate(UserCtx *user, PetscInt block_number, PetscReal dt, PetscReal *lambda_max_out)
Compute a conservative global pseudo-time spectral radius estimate.
PetscErrorCode ComputeTotalResidual(UserCtx *user)
Shared implementation of ComputeTotalResidual().
PetscErrorCode ComputeMomentumStabilityEstimate(UserCtx *user, PetscInt block_number, PetscReal dt, MomStabCandidate candidate, MomStabilityReport *rep)
Practical conservative momentum pseudo-time stability estimate (shadow).
MomStabLimiter limiter
Diagnostic report produced by ComputeMomentumStabilityEstimate().
PetscBool continueMode
Definition variables.h:701
PetscReal mom_rtol
Definition variables.h:726
PetscReal mom_last_lambda_max
Definition variables.h:739
PetscReal pseudo_cfl_reduction_factor
Definition variables.h:733
PetscReal min_pseudo_cfl
Definition variables.h:734
PetscReal mom_atol
Definition variables.h:726
PetscBool no_pseudo_cfl_backtrack
Definition variables.h:736
PetscInt StartStep
Definition variables.h:694
PetscReal max_pseudo_cfl
Definition variables.h:734
char log_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:709
PetscReal mom_dt_jameson_residual_norm_noise_allowance_factor
Definition variables.h:735
PetscReal pseudo_cfl_growth_factor
Definition variables.h:733
PetscReal mom_resid_rtol
Definition variables.h:726
PetscInt step
Definition variables.h:692
PetscInt mom_max_pseudo_steps
Definition variables.h:725
PetscReal mom_ratio_ema_alpha
Definition variables.h:737
PetscReal mom_resid_atol
Definition variables.h:726
PetscReal pseudo_cfl
Definition variables.h:732
Here is the call graph for this function:
Here is the caller graph for this function:

◆ MomentumUsesBDF2()

PetscBool MomentumUsesBDF2 ( SimCtx simCtx)

Returns whether the current physical step uses the BDF2 discretization.

Single source of truth for the BDF1/BDF2 selection. The predicate is identical to the one historically inlined in ComputeTotalResidual(): BDF2 when COEF_TIME_ACCURACY > 1.1 AND step != StartStep AND step != 1, otherwise BDF1 (startup step and the first step after a restart).

Parameters
simCtxMaster simulation context (reads step, StartStep).
Returns
PETSC_TRUE for BDF2, PETSC_FALSE for BDF1.

Returns whether the current physical step uses the BDF2 discretization.

See header.

This reproduces exactly the predicate historically inlined in ComputeTotalResidual(): BDF1 on the cold-start first step (ti == 1) and on the first step after a restart (ti == tistart); BDF2 thereafter (when the second- order time accuracy is enabled). Restart-history validity is a separate concern and is intentionally not addressed here.

Definition at line 19 of file momentumsolvers.c.

20{
21 const PetscInt ti = simCtx->step;
22 const PetscInt tistart = simCtx->StartStep;
23 return (PetscBool)(COEF_TIME_ACCURACY > 1.1 && ti != tistart && ti != 1);
24}
#define COEF_TIME_ACCURACY
Coefficient controlling the temporal accuracy scheme (e.g., 1.5 for 2nd Order Backward Difference).
Definition variables.h:57
Here is the caller graph for this function:

◆ MomentumBDFCoefficient()

PetscReal MomentumBDFCoefficient ( SimCtx simCtx)

Returns the BDF physical-time coefficient a0 for the current step.

a0 = 1.5 (== COEF_TIME_ACCURACY) for BDF2, a0 = 1.0 for BDF1. Used both as the leading coefficient of the physical-time term in the residual and as the additive temporal contribution lambda_t = a0/dt in the momentum stability estimate, keeping the two numerically consistent.

Parameters
simCtxMaster simulation context.
Returns
a0 in {1.0, 1.5}.

Returns the BDF physical-time coefficient a0 for the current step.

See header.

Definition at line 31 of file momentumsolvers.c.

32{
33 return MomentumUsesBDF2(simCtx) ? COEF_TIME_ACCURACY : 1.0;
34}
PetscBool MomentumUsesBDF2(SimCtx *simCtx)
Single source of truth for the BDF1/BDF2 selection.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ComputeTotalResidual()

PetscErrorCode ComputeTotalResidual ( UserCtx user)

Computes the shared spatial-plus-BDF momentum residual in user->Rhs.

Parameters
userBlock context with an allocated Rhs vector.
Returns
PetscErrorCode 0 on success.

Computes the shared spatial-plus-BDF momentum residual in user->Rhs.

Definition at line 42 of file momentumsolvers.c.

43{
44 PetscErrorCode ierr;
45 SimCtx *simCtx = user->simCtx;
46
47 // Extract Time Parameters from Context.
48 // BDF step selection now lives in MomentumUsesBDF2() (shared with the stability estimate).
49 const PetscReal dt = simCtx->dt;
50
51 PetscFunctionBeginUser;
53 // 1. Calculate Spatial Terms (stored in user->Rhs)
54 // Rhs = -Div(Flux) + Viscous + Source
55 ierr = ComputeRHS(user, user->Rhs); CHKERRQ(ierr);
56
57 // 2. Add Physical Time Derivative Terms (BDF Discretization)
58 // The equation solved is: dU/dtau = RHS_Spatial + RHS_Temporal
59
60 /* BDF order + leading coefficient from the shared helpers (single source of truth,
61 shared with the momentum stability estimate). a0 = 1.5 (==COEF_TIME_ACCURACY) for
62 BDF2, 1.0 for BDF1; using a0 for the current-state term keeps the residual
63 numerically identical to the historical inlined coefficients. */
64 const PetscBool use_bdf2 = MomentumUsesBDF2(simCtx);
65 const PetscReal a0 = MomentumBDFCoefficient(simCtx);
66 if (use_bdf2) {
67 // --- BDF2 (Second Order Backward Difference) ---
68 // (a0*U^{n} - 2.0*U^{n-1} + 0.5*U^{n-2}) / dt = RHS_Spatial(U^{n})
69 ierr = VecAXPY(user->Rhs, -a0/dt, user->Ucont); CHKERRQ(ierr);
70 ierr = VecAXPY(user->Rhs, +2.0/dt, user->Ucont_o); CHKERRQ(ierr);
71 ierr = VecAXPY(user->Rhs, -0.5/dt, user->Ucont_rm1); CHKERRQ(ierr);
72 } else {
73 // --- BDF1 (First Order / Euler Implicit) ---
74 // (a0*U^{n} - U^{n-1}) / dt = RHS_Spatial(U^{n}), with a0 = 1.0
75 ierr = VecAXPY(user->Rhs, -a0/dt, user->Ucont); CHKERRQ(ierr);
76 ierr = VecAXPY(user->Rhs, +1.0/dt, user->Ucont_o); CHKERRQ(ierr);
77 }
78
79 // 3. Enforce Boundary Conditions on the Residual
80 ierr = EnforceRHSBoundaryConditions(user); CHKERRQ(ierr);
81
83 PetscFunctionReturn(0);
84}
PetscErrorCode EnforceRHSBoundaryConditions(UserCtx *user)
Enforces boundary conditions on the momentum equation's Right-Hand-Side (RHS) vector.
Definition Boundaries.c:591
PetscReal MomentumBDFCoefficient(SimCtx *simCtx)
Returns a0 in {1.0, 1.5} for the current physical step.
Vec Ucont_o
Definition variables.h:911
Vec Ucont_rm1
Definition variables.h:912
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ComputeMomentumStabilityEstimate()

PetscErrorCode ComputeMomentumStabilityEstimate ( UserCtx user,
PetscInt  block_number,
PetscReal  dt,
MomStabCandidate  candidate,
MomStabilityReport rep 
)

Compute the momentum pseudo-time stability estimate (shadow/diagnostic).

Conservative, operator-scaled estimate: lambda = max_cell (a0/dt + lambda_c + lambda_nu), over active, non-solid cells, blocks, and MPI ranks, where lambda_c already includes the per-direction QUICK scheme factors. Read-only; performs no halo exchange, but does perform global scalar collectives (see implementation). This is a PRACTICAL CONSERVATIVE estimate, not a proven spectral radius. See the Workstream-A design.

Call-site preconditions (NOT enforced internally): lUcont, lUcat, lNu_t, lNvert must be fresh; lAj, face Jacobians and face metrics are static after grid init.

Parameters
[in]userArray of UserCtx (one per block).
[in]block_numberNumber of blocks.
[in]dtPhysical time step.
[in]candidateConvective candidate driving rep->lambda (B, C or D).
[out]repFilled diagnostic report (global maxima + breakdown).
Returns
PetscErrorCode 0 on success.

Compute the momentum pseudo-time stability estimate (shadow/diagnostic).

See header.

Operator-scaled estimate lambda = max_cell (a0/dt + lambda_c + lambda_nu) over active, non-solid interior cells, blocks and MPI ranks (lambda_c already carries the per-direction QUICK scheme factors – it is NOT multiplied by f_c again here). Read-only; no halo exchange, 5 global scalar collectives. NOT a proven spectral radius. Drives nothing in shadow mode.

Definition at line 396 of file momentumsolvers.c.

399{
400 PetscErrorCode ierr;
401 PetscMPIInt rank;
402 PetscFunctionBeginUser;
403
404 /* ---- input validation (no silent fallback) ---- */
405 PetscCheck(user != NULL, PETSC_COMM_WORLD, PETSC_ERR_ARG_NULL, "user is NULL");
406 PetscCheck(rep != NULL, PETSC_COMM_WORLD, PETSC_ERR_ARG_NULL, "rep is NULL");
407 PetscCheck(block_number > 0, PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE, "block_number must be > 0");
408 PetscCheck(candidate >= MOM_STAB_CAND_B && candidate <= MOM_STAB_CAND_D,
409 PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE, "unsupported stability candidate enum");
410 PetscCheck(PetscIsNormalReal(dt) && dt > 0.0, PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
411 "dt must be finite and positive");
412
413 SimCtx *simCtx = user[0].simCtx;
414 PetscCheck(simCtx != NULL, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE, "user[0].simCtx is NULL");
415 const PetscReal a0 = MomentumBDFCoefficient(simCtx);
416 const PetscBool centered = (PetscBool)(simCtx->les || simCtx->central);
417 const PetscBool has_nut = (PetscBool)(simCtx->les || simCtx->rans);
418 const PetscBool inviscid = (PetscBool)simCtx->invicid;
419 PetscCheck(inviscid || (PetscIsNormalReal(simCtx->ren) && simCtx->ren > 0.0),
420 PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE, "Reynolds number must be finite and positive when viscous");
421 const PetscReal lambda_t = a0 / dt;
422 const PetscReal nu_mol = inviscid ? 0.0 : 1.0 / simCtx->ren;
423 const PetscInt twoD = simCtx->TwoD;
424
425 /* Shadow-mode completeness flag. The estimate does NOT cover the Clark nonlinear stress
426 Jacobian, nor RANS eddy-viscosity sign behaviour (not verified sign-definite).
427 Body forces in the supported configs read a per-timestep-frozen scalar
428 (simCtx->bulkVelocityCorrection) inside ComputeRHS, so within a pseudo-solve they are a
429 constant forcing with ZERO velocity Jacobian (consistent with the frozen-pressure
430 treatment) -- they do not make the estimate incomplete. */
431 rep->estimate_incomplete = (PetscBool)(simCtx->clark || simCtx->rans);
432
433 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
434
435 /* Per-candidate local maxima; selected candidate also tracks the controlling cell. */
436 PetscReal locmaxB = 0.0, locmaxC = 0.0, locmaxD = 0.0, sel_max = 0.0;
437 PetscReal sel_lc = 0.0, sel_lv = 0.0;
438 PetscInt sel_ci = -1, sel_cj = -1, sel_ck = -1, sel_blk = -1, sel_class = 0, sel_os = 0;
439 PetscInt local_active = 0;
440
441 for (PetscInt bi = 0; bi < block_number; bi++) {
442 UserCtx *u = &user[bi];
443 DMDALocalInfo info = u->info;
444 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
445 const PetscInt xs = info.xs, xe = xs + info.xm;
446 const PetscInt ys = info.ys, ye = ys + info.ym;
447 const PetscInt zs = info.zs, ze = zs + info.zm;
448 /* Interior range == where ComputeRHS evaluates the residual; boundary faces
449 (0 and m-1) are RHS-masked, so excluding them implements active-row masking
450 for the normal boundary rows. The last physical face (m-2) stays IN with the
451 full cell estimate (its tangential rows remain active). */
452 const PetscInt lxs = (xs==0)?xs+1:xs, lxe = (xe==mx)?xe-1:xe;
453 const PetscInt lys = (ys==0)?ys+1:ys, lye = (ye==my)?ye-1:ye;
454 const PetscInt lzs = (zs==0)?zs+1:zs, lze = (ze==mz)?ze-1:ze;
455
456 /* Non-periodic boundary flags (QUICK stencil is degraded near these). */
457 const PetscBool npx0 = (PetscBool)(u->boundary_faces[BC_FACE_NEG_X].mathematical_type != PERIODIC);
458 const PetscBool npx1 = (PetscBool)(u->boundary_faces[BC_FACE_POS_X].mathematical_type != PERIODIC);
459 const PetscBool npy0 = (PetscBool)(u->boundary_faces[BC_FACE_NEG_Y].mathematical_type != PERIODIC);
460 const PetscBool npy1 = (PetscBool)(u->boundary_faces[BC_FACE_POS_Y].mathematical_type != PERIODIC);
461 const PetscBool npz0 = (PetscBool)(u->boundary_faces[BC_FACE_NEG_Z].mathematical_type != PERIODIC);
462 const PetscBool npz1 = (PetscBool)(u->boundary_faces[BC_FACE_POS_Z].mathematical_type != PERIODIC);
463 const PetscBool wxn = (PetscBool)(u->boundary_faces[BC_FACE_NEG_X].mathematical_type == WALL);
464 const PetscBool wxp = (PetscBool)(u->boundary_faces[BC_FACE_POS_X].mathematical_type == WALL);
465 const PetscBool wyn = (PetscBool)(u->boundary_faces[BC_FACE_NEG_Y].mathematical_type == WALL);
466 const PetscBool wyp = (PetscBool)(u->boundary_faces[BC_FACE_POS_Y].mathematical_type == WALL);
467 const PetscBool wzn = (PetscBool)(u->boundary_faces[BC_FACE_NEG_Z].mathematical_type == WALL);
468 const PetscBool wzp = (PetscBool)(u->boundary_faces[BC_FACE_POS_Z].mathematical_type == WALL);
469
470 Cmpnts ***ucont, ***ucat = NULL;
471 Cmpnts ***icsi, ***ieta, ***izet, ***jcsi, ***jeta, ***jzet, ***kcsi, ***keta, ***kzet;
472 Cmpnts ***csi = NULL, ***eta = NULL, ***zet = NULL;
473 PetscReal ***aj, ***iaj, ***jaj, ***kaj, ***nvert, ***nut = NULL;
474
475 ierr = DMDAVecGetArrayRead(u->fda, u->lUcont, &ucont); CHKERRQ(ierr);
476 ierr = DMDAVecGetArrayRead(u->da, u->lAj, &aj); CHKERRQ(ierr);
477 ierr = DMDAVecGetArrayRead(u->da, u->lIAj, &iaj); CHKERRQ(ierr);
478 ierr = DMDAVecGetArrayRead(u->da, u->lJAj, &jaj); CHKERRQ(ierr);
479 ierr = DMDAVecGetArrayRead(u->da, u->lKAj, &kaj); CHKERRQ(ierr);
480 ierr = DMDAVecGetArrayRead(u->fda, u->lICsi, &icsi); CHKERRQ(ierr);
481 ierr = DMDAVecGetArrayRead(u->fda, u->lIEta, &ieta); CHKERRQ(ierr);
482 ierr = DMDAVecGetArrayRead(u->fda, u->lIZet, &izet); CHKERRQ(ierr);
483 ierr = DMDAVecGetArrayRead(u->fda, u->lJCsi, &jcsi); CHKERRQ(ierr);
484 ierr = DMDAVecGetArrayRead(u->fda, u->lJEta, &jeta); CHKERRQ(ierr);
485 ierr = DMDAVecGetArrayRead(u->fda, u->lJZet, &jzet); CHKERRQ(ierr);
486 ierr = DMDAVecGetArrayRead(u->fda, u->lKCsi, &kcsi); CHKERRQ(ierr);
487 ierr = DMDAVecGetArrayRead(u->fda, u->lKEta, &keta); CHKERRQ(ierr);
488 ierr = DMDAVecGetArrayRead(u->fda, u->lKZet, &kzet); CHKERRQ(ierr);
489 ierr = DMDAVecGetArrayRead(u->da, u->lNvert, &nvert); CHKERRQ(ierr);
490 if (has_nut) { ierr = DMDAVecGetArrayRead(u->da, u->lNu_t, &nut); CHKERRQ(ierr); }
491 /* Cartesian velocity + cell metrics: always read so candidate D's gradient term
492 (and hence rep->lambda_D) is meaningful regardless of the selected candidate.
493 Precondition: lUcat fresh (Contra2Cart has run in the preceding residual eval). */
494 ierr = DMDAVecGetArrayRead(u->fda, u->lUcat, &ucat); CHKERRQ(ierr);
495 ierr = DMDAVecGetArrayRead(u->fda, u->lCsi, &csi); CHKERRQ(ierr);
496 ierr = DMDAVecGetArrayRead(u->fda, u->lEta, &eta); CHKERRQ(ierr);
497 ierr = DMDAVecGetArrayRead(u->fda, u->lZet, &zet); CHKERRQ(ierr);
498
499 /* Error flag for the cell loop: on a bad metric/estimate we record the location and
500 jump to block_cleanup so EVERY acquired array is restored before the error returns. */
501 PetscErrorCode cell_err = 0;
502 PetscInt be_i = -1, be_j = -1, be_k = -1;
503 const char *be_what = NULL;
504
505 for (PetscInt k = lzs; k < lze; k++) {
506 for (PetscInt j = lys; j < lye; j++) {
507 for (PetscInt i = lxs; i < lxe; i++) {
508 /* ---- active-row mask: skip only locations with no active unknown ---- */
509 const PetscInt rows = MomCellActiveRows(nvert, k, j, i, mx, my, mz,
510 npx1, npy1, npz1, twoD);
511 if (rows == 0) continue;
512 local_active++;
513 const PetscReal Ajc = aj[k][j][i];
514 if (!(PetscIsNormalReal(Ajc) && Ajc > 0.0)) {
515 cell_err = PETSC_ERR_FP; be_i = i; be_j = j; be_k = k;
516 be_what = "non-finite/non-positive cell inverse-Jacobian";
517 goto block_cleanup;
518 }
519
520 /* ---- directional QUICK modification (per direction; both faces; 2nd neighbour) ---- */
521 const PetscBool mod_x = MomQuickDirModified(nvert, k, j, i, i, mx, npx0, npx1,
522 info.gxs, info.gxs+info.gxm, 'i');
523 const PetscBool mod_y = MomQuickDirModified(nvert, k, j, i, j, my, npy0, npy1,
524 info.gys, info.gys+info.gym, 'j');
525 const PetscBool mod_z = MomQuickDirModified(nvert, k, j, i, k, mz, npz0, npz1,
526 info.gzs, info.gzs+info.gzm, 'k');
527
528 /* ---- classify cell: interior / physical-boundary / IB-adjacent ---- */
529 const PetscBool bnd = (PetscBool)((npx0 && i<=1) || (npx1 && i>=mx-2) ||
530 (npy0 && j<=1) || (npy1 && j>=my-2) ||
531 (npz0 && k<=1) || (npz1 && k>=mz-2));
532 const PetscBool ib = MomCellHasSolidNeighbor(nvert, k, j, i);
533 const PetscInt cls = ib ? 2 : (bnd ? 1 : 0);
534
535 /* ---- convective: six contravariant face fluxes, DIRECTIONAL QUICK factors ---- */
536 const PetscReal Uxp = ucont[k][j][i].x, Uxm = ucont[k][j][i-1].x;
537 const PetscReal Uyp = ucont[k][j][i].y, Uym = ucont[k][j-1][i].y;
538 const PetscReal Uzp = ucont[k][j][i].z, Uzm = ucont[k-1][j][i].z;
539 const PetscReal divU = PetscAbsReal((Uxp-Uxm)+(Uyp-Uym)+(Uzp-Uzm));
540 const PetscReal fx = centered ? 1.0 : (mod_x ? 2.5 : (4.0/3.0));
541 const PetscReal fy = centered ? 1.0 : (mod_y ? 2.5 : (4.0/3.0));
542 const PetscReal fz = centered ? 1.0 : (mod_z ? 2.5 : (4.0/3.0));
543
544 /* per-direction transport scale; C's divergence term stays unscaled by f_d. */
545 const PetscReal lcB = 0.5 * Ajc * (
546 fx * (PetscAbsReal(Uxp)+PetscAbsReal(Uxm))
547 + fy * (PetscAbsReal(Uyp)+PetscAbsReal(Uym))
548 + fz * (PetscAbsReal(Uzp)+PetscAbsReal(Uzm)) );
549 const PetscReal lcC = lcB + 0.5 * Ajc * divU;
550 /* lambda_grad_u = max_i sum_j |du_i/dx_j| from fresh Cartesian velocity
551 (the nonlinear zero-order Jacobian term delta_u . grad u).
552 Physical gradient: d/dx_dir = Ajc*(Csi.dir d/dxi + Eta.dir d/deta + Zet.dir d/dzeta). */
553 const Cmpnts duc = { 0.5*(ucat[k][j][i+1].x-ucat[k][j][i-1].x),
554 0.5*(ucat[k][j][i+1].y-ucat[k][j][i-1].y),
555 0.5*(ucat[k][j][i+1].z-ucat[k][j][i-1].z) };
556 const Cmpnts due = { 0.5*(ucat[k][j+1][i].x-ucat[k][j-1][i].x),
557 0.5*(ucat[k][j+1][i].y-ucat[k][j-1][i].y),
558 0.5*(ucat[k][j+1][i].z-ucat[k][j-1][i].z) };
559 const Cmpnts duz = { 0.5*(ucat[k+1][j][i].x-ucat[k-1][j][i].x),
560 0.5*(ucat[k+1][j][i].y-ucat[k-1][j][i].y),
561 0.5*(ucat[k+1][j][i].z-ucat[k-1][j][i].z) };
562 const Cmpnts C = csi[k][j][i], E = eta[k][j][i], Z = zet[k][j][i];
563 #define MOM_ROW(cmp) ( \
564 PetscAbsReal(Ajc*(C.x*duc.cmp + E.x*due.cmp + Z.x*duz.cmp)) + \
565 PetscAbsReal(Ajc*(C.y*duc.cmp + E.y*due.cmp + Z.y*duz.cmp)) + \
566 PetscAbsReal(Ajc*(C.z*duc.cmp + E.z*due.cmp + Z.z*duz.cmp)) )
567 const PetscReal lgrad = PetscMax(MOM_ROW(x), PetscMax(MOM_ROW(y), MOM_ROW(z)));
568 #undef MOM_ROW
569 const PetscReal lcD = lcC + lgrad;
570
571 /* ---- viscous: six faces, face Jacobians, full metric rows ---- */
572 PetscReal lv = 0.0;
573 if (!inviscid) {
574 /* xi+ (i), xi- (i-1) */
575 for (PetscInt s = 0; s < 2; s++) {
576 const PetscInt fi = (s==0) ? i : i-1;
577 if (!(PetscIsNormalReal(iaj[k][j][fi]) && iaj[k][j][fi] > 0.0)) {
578 cell_err = PETSC_ERR_FP; be_i=i; be_j=j; be_k=k;
579 be_what = "non-finite/non-positive xi-face inverse-Jacobian"; goto block_cleanup; }
580 PetscReal nuf = nu_mol;
581 if (has_nut) {
582 PetscReal nt = 0.5*(nut[k][j][fi] + nut[k][j][fi+1]);
583 if ((wxn && fi==0) || (wxp && fi==mx-2)) nt = 0.0;
584 nuf += nt;
585 }
586 lv += PetscAbsReal(nuf) * Ajc * iaj[k][j][fi]
587 * MomFaceGabs(icsi[k][j][fi], ieta[k][j][fi], izet[k][j][fi]);
588 }
589 /* eta+ (j), eta- (j-1) */
590 for (PetscInt s = 0; s < 2; s++) {
591 const PetscInt fj = (s==0) ? j : j-1;
592 if (!(PetscIsNormalReal(jaj[k][fj][i]) && jaj[k][fj][i] > 0.0)) {
593 cell_err = PETSC_ERR_FP; be_i=i; be_j=j; be_k=k;
594 be_what = "non-finite/non-positive eta-face inverse-Jacobian"; goto block_cleanup; }
595 PetscReal nuf = nu_mol;
596 if (has_nut) {
597 PetscReal nt = 0.5*(nut[k][fj][i] + nut[k][fj+1][i]);
598 if ((wyn && fj==0) || (wyp && fj==my-2)) nt = 0.0;
599 nuf += nt;
600 }
601 /* eta-face normal is the eta metric (jeta) -> pass as N. */
602 lv += PetscAbsReal(nuf) * Ajc * jaj[k][fj][i]
603 * MomFaceGabs(jeta[k][fj][i], jcsi[k][fj][i], jzet[k][fj][i]);
604 }
605 /* zeta+ (k), zeta- (k-1) */
606 for (PetscInt s = 0; s < 2; s++) {
607 const PetscInt fk = (s==0) ? k : k-1;
608 if (!(PetscIsNormalReal(kaj[fk][j][i]) && kaj[fk][j][i] > 0.0)) {
609 cell_err = PETSC_ERR_FP; be_i=i; be_j=j; be_k=k;
610 be_what = "non-finite/non-positive zeta-face inverse-Jacobian"; goto block_cleanup; }
611 PetscReal nuf = nu_mol;
612 if (has_nut) {
613 PetscReal nt = 0.5*(nut[fk][j][i] + nut[fk+1][j][i]);
614 if ((wzn && fk==0) || (wzp && fk==mz-2)) nt = 0.0;
615 nuf += nt;
616 }
617 /* zeta-face normal is the zeta metric (kzet) -> pass as N. */
618 lv += PetscAbsReal(nuf) * Ajc * kaj[fk][j][i]
619 * MomFaceGabs(kzet[fk][j][i], kcsi[fk][j][i], keta[fk][j][i]);
620 }
621 lv *= 4.0; /* 2 (two-face row-sum) x 2 (longitudinal full-stress) */
622 }
623
624 /* one-sided viscous cross-derivative branch near a solid: conservative x2,
625 applied ONCE per cell, after a complete 3x3x3 solid-band check. */
626 PetscInt one_sided = 0;
627 if (!inviscid && MomCellUsesOneSidedViscousStencil(nvert, k, j, i)) {
628 one_sided = 1;
629 lv *= 2.0;
630 }
631
632 /* ---- candidate totals and running maxima ---- */
633 const PetscReal lB = lambda_t + lcB + lv;
634 const PetscReal lC = lambda_t + lcC + lv;
635 const PetscReal lD = lambda_t + lcD + lv;
636 if (PetscIsInfOrNanReal(lD)) {
637 cell_err = PETSC_ERR_FP; be_i=i; be_j=j; be_k=k;
638 be_what = "non-finite stability estimate"; goto block_cleanup;
639 }
640 locmaxB = PetscMax(locmaxB, lB);
641 locmaxC = PetscMax(locmaxC, lC);
642 locmaxD = PetscMax(locmaxD, lD);
643
644 const PetscReal lsel = (candidate==MOM_STAB_CAND_B)?lB:(candidate==MOM_STAB_CAND_C)?lC:lD;
645 const PetscReal lcsel = (candidate==MOM_STAB_CAND_B)?lcB:(candidate==MOM_STAB_CAND_C)?lcC:lcD;
646 if (lsel > sel_max) {
647 sel_max = lsel; sel_lc = lcsel; sel_lv = lv;
648 sel_ci = i; sel_cj = j; sel_ck = k; sel_blk = bi;
649 sel_class = cls; sel_os = one_sided;
650 }
651 }
652 }
653 }
654
655 /* Single cleanup point: reached on normal completion AND on any cell-loop error.
656 Every successful GetArrayRead above has exactly one matching restore here. */
657 block_cleanup:
658 ierr = DMDAVecRestoreArrayRead(u->fda, u->lUcont, &ucont); CHKERRQ(ierr);
659 ierr = DMDAVecRestoreArrayRead(u->da, u->lAj, &aj); CHKERRQ(ierr);
660 ierr = DMDAVecRestoreArrayRead(u->da, u->lIAj, &iaj); CHKERRQ(ierr);
661 ierr = DMDAVecRestoreArrayRead(u->da, u->lJAj, &jaj); CHKERRQ(ierr);
662 ierr = DMDAVecRestoreArrayRead(u->da, u->lKAj, &kaj); CHKERRQ(ierr);
663 ierr = DMDAVecRestoreArrayRead(u->fda, u->lICsi, &icsi); CHKERRQ(ierr);
664 ierr = DMDAVecRestoreArrayRead(u->fda, u->lIEta, &ieta); CHKERRQ(ierr);
665 ierr = DMDAVecRestoreArrayRead(u->fda, u->lIZet, &izet); CHKERRQ(ierr);
666 ierr = DMDAVecRestoreArrayRead(u->fda, u->lJCsi, &jcsi); CHKERRQ(ierr);
667 ierr = DMDAVecRestoreArrayRead(u->fda, u->lJEta, &jeta); CHKERRQ(ierr);
668 ierr = DMDAVecRestoreArrayRead(u->fda, u->lJZet, &jzet); CHKERRQ(ierr);
669 ierr = DMDAVecRestoreArrayRead(u->fda, u->lKCsi, &kcsi); CHKERRQ(ierr);
670 ierr = DMDAVecRestoreArrayRead(u->fda, u->lKEta, &keta); CHKERRQ(ierr);
671 ierr = DMDAVecRestoreArrayRead(u->fda, u->lKZet, &kzet); CHKERRQ(ierr);
672 ierr = DMDAVecRestoreArrayRead(u->da, u->lNvert, &nvert); CHKERRQ(ierr);
673 if (has_nut) { ierr = DMDAVecRestoreArrayRead(u->da, u->lNu_t, &nut); CHKERRQ(ierr); }
674 ierr = DMDAVecRestoreArrayRead(u->fda, u->lUcat, &ucat); CHKERRQ(ierr);
675 ierr = DMDAVecRestoreArrayRead(u->fda, u->lCsi, &csi); CHKERRQ(ierr);
676 ierr = DMDAVecRestoreArrayRead(u->fda, u->lEta, &eta); CHKERRQ(ierr);
677 ierr = DMDAVecRestoreArrayRead(u->fda, u->lZet, &zet); CHKERRQ(ierr);
678
679 /* Now that this block's arrays are restored, surface any cell-loop error. */
680 PetscCheck(cell_err == 0, PETSC_COMM_SELF, cell_err, "%s at (%" PetscInt_FMT
681 ",%" PetscInt_FMT ",%" PetscInt_FMT ")", be_what ? be_what : "error", be_i, be_j, be_k);
682 }
683
684 /* ---- global reductions (no ghost/halo exchange; scalar collectives only) ----
685 5 collectives total: one 3-element MAX (B/C/D), one SUM (active-cell count),
686 one MIN (portable owner selection), and two broadcasts (the controlling-cell
687 real breakdown and integer indices). */
688 PetscReal loc3[3] = { locmaxB, locmaxC, locmaxD }, glo3[3];
689 ierr = MPI_Allreduce(loc3, glo3, 3, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD); CHKERRQ(ierr);
690
691 PetscInt global_active = 0;
692 ierr = MPI_Allreduce(&local_active, &global_active, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
693
694 /* The selected candidate's global max equals glo3[candidate] (no extra reduction).
695 Portable owner pick: lowest rank whose local sel_max equals the global max. */
696 const PetscReal sel_global = glo3[(int)candidate];
697 PetscMPIInt nproc, claim, owner;
698 ierr = MPI_Comm_size(PETSC_COMM_WORLD, &nproc); CHKERRQ(ierr);
699 claim = (sel_max == sel_global) ? rank : nproc;
700 ierr = MPI_Allreduce(&claim, &owner, 1, MPI_INT, MPI_MIN, PETSC_COMM_WORLD); CHKERRQ(ierr);
701 if (owner == nproc) owner = 0; /* no active cell anywhere: deterministic fallback owner */
702
703 PetscReal dbuf[2] = { sel_lc, sel_lv };
704 PetscInt ibuf[6] = { sel_ci, sel_cj, sel_ck, sel_blk, sel_class, sel_os };
705 ierr = MPI_Bcast(dbuf, 2, MPIU_REAL, owner, PETSC_COMM_WORLD); CHKERRQ(ierr);
706 ierr = MPI_Bcast(ibuf, 6, MPIU_INT, owner, PETSC_COMM_WORLD); CHKERRQ(ierr);
707
708 rep->lambda_t = lambda_t;
709 rep->lambda_c = dbuf[0];
710 rep->lambda_v = dbuf[1];
711 rep->lambda = sel_global;
712 rep->lambda_B = glo3[0]; rep->lambda_C = glo3[1]; rep->lambda_D = glo3[2];
713 rep->ci = ibuf[0]; rep->cj = ibuf[1]; rep->ck = ibuf[2];
714 rep->cblock = ibuf[3]; rep->cclass = ibuf[4]; rep->one_sided = ibuf[5];
715 rep->active_cells = global_active;
716
717 /* Only a genuinely empty active set (all cells masked) may fall back to the temporal
718 term alone. A non-finite or non-positive estimate with active cells is a hard error. */
719 if (global_active == 0) {
720 rep->lambda = lambda_t;
721 } else {
722 PetscCheck(PetscIsNormalReal(rep->lambda) && rep->lambda > 0.0, PETSC_COMM_WORLD, PETSC_ERR_FP,
723 "momentum stability estimate is non-finite or non-positive with %" PetscInt_FMT
724 " active cells", global_active);
725 }
726
727 /* Dominant limiter at the controlling cell. */
728 if (rep->lambda_t >= rep->lambda_c && rep->lambda_t >= rep->lambda_v) rep->limiter = MOM_STAB_LIMITER_TIME;
729 else if (rep->lambda_c >= rep->lambda_v) rep->limiter = MOM_STAB_LIMITER_CONVECTION;
731
732 PetscFunctionReturn(0);
733}
#define MOM_ROW(cmp)
static PetscBool MomQuickDirModified(PetscReal ***nvert, PetscInt a, PetscInt b, PetscInt c, PetscInt idx, PetscInt m, PetscBool np0, PetscBool np1, PetscInt g0, PetscInt g1, char dir)
static PetscBool MomCellHasSolidNeighbor(PetscReal ***nvert, PetscInt k, PetscInt j, PetscInt i)
static PetscReal MomFaceGabs(Cmpnts N, Cmpnts A, Cmpnts B)
PetscInt MomCellActiveRows(PetscReal ***nvert, PetscInt k, PetscInt j, PetscInt i, PetscInt mx, PetscInt my, PetscInt mz, PetscBool np_x1, PetscBool np_y1, PetscBool np_z1, PetscInt twoD)
Active staggered-momentum row mask for a cell (exposed for unit testing).
static PetscBool MomCellUsesOneSidedViscousStencil(PetscReal ***nvert, PetscInt k, PetscInt j, PetscInt i)
PetscBool estimate_incomplete
PetscInt clark
Definition variables.h:790
@ PERIODIC
Definition variables.h:290
@ WALL
Definition variables.h:284
PetscInt TwoD
Definition variables.h:715
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:896
Vec lIEta
Definition variables.h:930
Vec lIZet
Definition variables.h:930
Vec lNvert
Definition variables.h:904
PetscInt rans
Definition variables.h:789
Vec lZet
Definition variables.h:927
PetscReal ren
Definition variables.h:732
Vec lIAj
Definition variables.h:930
Vec lKEta
Definition variables.h:932
Vec lJCsi
Definition variables.h:931
PetscScalar x
Definition variables.h:101
PetscInt invicid
Definition variables.h:715
Vec lKZet
Definition variables.h:932
Vec lNu_t
Definition variables.h:935
Vec lJEta
Definition variables.h:931
Vec lCsi
Definition variables.h:927
PetscScalar z
Definition variables.h:101
Vec lKCsi
Definition variables.h:932
PetscInt central
Definition variables.h:730
Vec lJZet
Definition variables.h:931
Vec lUcont
Definition variables.h:904
Vec lAj
Definition variables.h:927
Vec lICsi
Definition variables.h:930
DMDALocalInfo info
Definition variables.h:883
Vec lUcat
Definition variables.h:904
PetscScalar y
Definition variables.h:101
Vec lEta
Definition variables.h:927
PetscInt les
Definition variables.h:789
BCType mathematical_type
Definition variables.h:366
Vec lJAj
Definition variables.h:931
Vec lKAj
Definition variables.h:932
@ BC_FACE_NEG_X
Definition variables.h:260
@ BC_FACE_POS_Z
Definition variables.h:262
@ BC_FACE_POS_Y
Definition variables.h:261
@ BC_FACE_NEG_Z
Definition variables.h:262
@ BC_FACE_POS_X
Definition variables.h:260
@ BC_FACE_NEG_Y
Definition variables.h:261
A 3D point or vector with PetscScalar components.
Definition variables.h:100
User-defined context containing data specific to a single computational grid level.
Definition variables.h:876
Here is the call graph for this function:
Here is the caller graph for this function:

◆ MomCellActiveRows()

PetscInt MomCellActiveRows ( PetscReal ***  nvert,
PetscInt  k,
PetscInt  j,
PetscInt  i,
PetscInt  mx,
PetscInt  my,
PetscInt  mz,
PetscBool  np_x1,
PetscBool  np_y1,
PetscBool  np_z1,
PetscInt  twoD 
)

Active staggered-momentum row mask for a cell (exposed for unit testing).

Returns a 3-bit mask (xi=1, eta=2, zeta=4). A solid cell yields 0 (all inactive); a positive solid neighbour or a positive non-periodic physical face clears the corresponding normal row; TwoD (1/2/3) clears the homogeneous direction's row.

Parameters
nvertLocal nvert array (ghosted).
kCell k index.
jCell j index.
iCell i index.
mxGlobal x dimension.
myGlobal y dimension.
mzGlobal z dimension.
np_x1True if the positive-x face is non-periodic.
np_y1True if the positive-y face is non-periodic.
np_z1True if the positive-z face is non-periodic.
twoDTwoD homogeneous-direction selector (0 none, 1 xi, 2 eta, 3 zeta).
Returns
3-bit active-row mask (0 when the location carries no active unknown).

Definition at line 368 of file momentumsolvers.c.

371{
372 if (MOM_SKIP_SOLID(nvert[k][j][i])) return 0; /* solid cell: all rows inactive */
373 PetscInt bits = 0x7;
374 if (MOM_SKIP_SOLID(nvert[k][j][i+1])) bits &= ~0x1; /* positive xi neighbour solid */
375 if (MOM_SKIP_SOLID(nvert[k][j+1][i])) bits &= ~0x2; /* positive eta neighbour solid */
376 if (MOM_SKIP_SOLID(nvert[k+1][j][i])) bits &= ~0x4; /* positive zeta neighbour solid */
377 if (np_x1 && i == mx-2) bits &= ~0x1; /* positive non-periodic xi face */
378 if (np_y1 && j == my-2) bits &= ~0x2;
379 if (np_z1 && k == mz-2) bits &= ~0x4;
380 if (twoD == 1) bits &= ~0x1; /* TwoD homogeneous direction: xi */
381 else if (twoD == 2) bits &= ~0x2; /* eta */
382 else if (twoD == 3) bits &= ~0x4; /* zeta */
383 return bits;
384}
#define MOM_SKIP_SOLID(v)
Here is the caller graph for this function: