PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
Macros | Functions
momentumsolvers.c File Reference
#include "momentumsolvers.h"
Include dependency graph for momentumsolvers.c:

Go to the source code of this file.

Macros

#define MOM_CFL_CAP_SAFETY   0.60
 
#define MOM_CFL_CAP_RELAX   1.005
 
#define __FUNCT__   "MomentumUsesBDF2"
 
#define __FUNCT__   "MomentumBDFCoefficient"
 
#define __FUNCT__   "ComputeTotalResidual"
 
#define __FUNCT__   "Momentum_Solver_Explicit_RungeKutta4"
 
#define __FUNCT__   "ComputeGlobalSpectralRadiusEstimate"
 
#define MOM_SKIP_SOLID(v)   ((v) > 0.1)
 
#define MOM_VISC_ONESIDED(v)   ((v) > 0.5 && (v) < 7.0)
 
#define MOM_QUICK_BLOCKS(v)   ((v) >= 0.1 && (v) <= 7.0)
 
#define __FUNCT__   "ComputeMomentumStabilityEstimate"
 
#define MOM_ROW(cmp)
 
#define __FUNCT__   "MomentumSolver_DualTime_Picard_JamesonRK"
 

Functions

PetscBool MomentumUsesBDF2 (SimCtx *simCtx)
 Single source of truth for the BDF1/BDF2 selection.
 
PetscReal MomentumBDFCoefficient (SimCtx *simCtx)
 Returns a0 in {1.0, 1.5} for the current physical step.
 
PetscErrorCode ComputeTotalResidual (UserCtx *user)
 Shared implementation of ComputeTotalResidual().
 
PetscErrorCode MomentumSolver_Explicit_RungeKutta4 (UserCtx *user, IBMNodes *ibm, FSInfo *fsi)
 Internal helper implementation: MomentumSolver_Explicit_RungeKutta4().
 
static PetscErrorCode ComputeGlobalSpectralRadiusEstimate (UserCtx *user, PetscInt block_number, PetscReal dt, PetscReal *lambda_max_out)
 Compute a conservative global pseudo-time spectral radius estimate.
 
static PetscReal MomFaceGabs (Cmpnts N, Cmpnts A, Cmpnts B)
 
static PetscBool MomCellUsesOneSidedViscousStencil (PetscReal ***nvert, PetscInt k, PetscInt j, PetscInt i)
 
static PetscBool MomCellHasSolidNeighbor (PetscReal ***nvert, PetscInt k, PetscInt j, PetscInt i)
 
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)
 
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).
 
PetscErrorCode ComputeMomentumStabilityEstimate (UserCtx *user, PetscInt block_number, PetscReal dt, MomStabCandidate candidate, MomStabilityReport *rep)
 Practical conservative momentum pseudo-time stability estimate (shadow).
 
PetscErrorCode MomentumSolver_DualTime_Picard_JamesonRK (UserCtx *user, IBMNodes *ibm, FSInfo *fsi)
 Internal helper implementation: MomentumSolver_DualTime_Picard_JamesonRK().
 

Macro Definition Documentation

◆ MOM_CFL_CAP_SAFETY

#define MOM_CFL_CAP_SAFETY   0.60

Definition at line 6 of file momentumsolvers.c.

◆ MOM_CFL_CAP_RELAX

#define MOM_CFL_CAP_RELAX   1.005

Definition at line 7 of file momentumsolvers.c.

◆ __FUNCT__ [1/7]

#define __FUNCT__   "MomentumUsesBDF2"

Definition at line 15 of file momentumsolvers.c.

◆ __FUNCT__ [2/7]

#define __FUNCT__   "MomentumBDFCoefficient"

Definition at line 15 of file momentumsolvers.c.

◆ __FUNCT__ [3/7]

#define __FUNCT__   "ComputeTotalResidual"

Definition at line 15 of file momentumsolvers.c.

◆ __FUNCT__ [4/7]

#define __FUNCT__   "Momentum_Solver_Explicit_RungeKutta4"

Definition at line 15 of file momentumsolvers.c.

◆ __FUNCT__ [5/7]

#define __FUNCT__   "ComputeGlobalSpectralRadiusEstimate"

Definition at line 15 of file momentumsolvers.c.

◆ MOM_SKIP_SOLID

#define MOM_SKIP_SOLID (   v)    ((v) > 0.1)

Definition at line 330 of file momentumsolvers.c.

◆ MOM_VISC_ONESIDED

#define MOM_VISC_ONESIDED (   v)    ((v) > 0.5 && (v) < 7.0)

Definition at line 331 of file momentumsolvers.c.

◆ MOM_QUICK_BLOCKS

#define MOM_QUICK_BLOCKS (   v)    ((v) >= 0.1 && (v) <= 7.0)

Definition at line 332 of file momentumsolvers.c.

◆ __FUNCT__ [6/7]

#define __FUNCT__   "ComputeMomentumStabilityEstimate"

Definition at line 15 of file momentumsolvers.c.

◆ MOM_ROW

#define MOM_ROW (   cmp)
Value:
( \
PetscAbsReal(Ajc*(C.x*duc.cmp + E.x*due.cmp + Z.x*duz.cmp)) + \
PetscAbsReal(Ajc*(C.y*duc.cmp + E.y*due.cmp + Z.y*duz.cmp)) + \
PetscAbsReal(Ajc*(C.z*duc.cmp + E.z*due.cmp + Z.z*duz.cmp)) )

◆ __FUNCT__ [7/7]

#define __FUNCT__   "MomentumSolver_DualTime_Picard_JamesonRK"

Definition at line 15 of file momentumsolvers.c.

Function Documentation

◆ MomentumUsesBDF2()

PetscBool MomentumUsesBDF2 ( SimCtx *  simCtx)

Single source of truth for the BDF1/BDF2 selection.

Returns whether the current physical step uses the BDF2 discretization.

See header.

The run loop increments step before invoking the momentum solve. Therefore, BDF1 applies at a fresh start until two physical states exist. A committed checkpoint restores Ucont_rm1, so a restarted solve can retain BDF2 on its first advanced step.

Definition at line 24 of file momentumsolvers.c.

25{
26 const PetscInt ti = simCtx->step;
27 const PetscInt tistart = simCtx->StartStep;
28 return (PetscBool)(COEF_TIME_ACCURACY > 1.1 && ti != 1 &&
29 (simCtx->restartHistoryAvailable || ti > tistart + 1));
30}
PetscInt StartStep
Definition variables.h:869
PetscInt step
Definition variables.h:867
PetscBool restartHistoryAvailable
Definition variables.h:888
#define COEF_TIME_ACCURACY
Coefficient controlling the temporal accuracy scheme (e.g., 1.5 for 2nd Order Backward Difference).
Definition variables.h:75
Here is the caller graph for this function:

◆ MomentumBDFCoefficient()

PetscReal MomentumBDFCoefficient ( SimCtx *  simCtx)

Returns a0 in {1.0, 1.5} for the current physical step.

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

See header.

Definition at line 37 of file momentumsolvers.c.

38{
39 return MomentumUsesBDF2(simCtx) ? COEF_TIME_ACCURACY : 1.0;
40}
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)

Shared implementation of ComputeTotalResidual().

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

Definition at line 48 of file momentumsolvers.c.

49{
50 PetscErrorCode ierr;
51 SimCtx *simCtx = user->simCtx;
52
53 // Extract Time Parameters from Context.
54 // BDF step selection now lives in MomentumUsesBDF2() (shared with the stability estimate).
55 const PetscReal dt = simCtx->dt;
56
57 PetscFunctionBeginUser;
59 // 1. Calculate Spatial Terms (stored in user->Rhs)
60 // Rhs = -Div(Flux) + Viscous + Source
61 ierr = ComputeRHS(user, user->Rhs); CHKERRQ(ierr);
62
63 // 2. Add Physical Time Derivative Terms (BDF Discretization)
64 // The equation solved is: dU/dtau = RHS_Spatial + RHS_Temporal
65
66 /* BDF order + leading coefficient from the shared helpers (single source of truth,
67 shared with the momentum stability estimate). a0 = 1.5 (==COEF_TIME_ACCURACY) for
68 BDF2, 1.0 for BDF1; using a0 for the current-state term keeps the residual
69 numerically identical to the historical inlined coefficients. */
70 const PetscBool use_bdf2 = MomentumUsesBDF2(simCtx);
71 const PetscReal a0 = MomentumBDFCoefficient(simCtx);
72 if (use_bdf2) {
73 // --- BDF2 (Second Order Backward Difference) ---
74 // (a0*U^{n} - 2.0*U^{n-1} + 0.5*U^{n-2}) / dt = RHS_Spatial(U^{n})
75 ierr = VecAXPY(user->Rhs, -a0/dt, user->Ucont); CHKERRQ(ierr);
76 ierr = VecAXPY(user->Rhs, +2.0/dt, user->Ucont_o); CHKERRQ(ierr);
77 ierr = VecAXPY(user->Rhs, -0.5/dt, user->Ucont_rm1); CHKERRQ(ierr);
78 } else {
79 // --- BDF1 (First Order / Euler Implicit) ---
80 // (a0*U^{n} - U^{n-1}) / dt = RHS_Spatial(U^{n}), with a0 = 1.0
81 ierr = VecAXPY(user->Rhs, -a0/dt, user->Ucont); CHKERRQ(ierr);
82 ierr = VecAXPY(user->Rhs, +1.0/dt, user->Ucont_o); CHKERRQ(ierr);
83 }
84
85 // 3. Enforce Boundary Conditions on the Residual
86 ierr = EnforceRHSBoundaryConditions(user); CHKERRQ(ierr);
87
89 PetscFunctionReturn(0);
90}
PetscErrorCode EnforceRHSBoundaryConditions(UserCtx *user)
Zeroes every momentum RHS row that does not carry an independent unknown.
Definition Boundaries.c:686
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:894
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:885
PetscReal MomentumBDFCoefficient(SimCtx *simCtx)
Returns a0 in {1.0, 1.5} for the current physical step.
PetscErrorCode ComputeRHS(UserCtx *user, Vec Rhs)
Computes the Right-Hand Side (RHS) of the momentum equations.
Definition rhs.c:1157
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1077
PetscReal dt
Definition variables.h:874
Vec Ucont
Definition variables.h:1113
Vec Ucont_o
Definition variables.h:1120
Vec Ucont_rm1
Definition variables.h:1121
The master context for the entire simulation.
Definition variables.h:859
Here is the call graph for this function:
Here is the caller graph for this function:

◆ MomentumSolver_Explicit_RungeKutta4()

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

Internal helper implementation: MomentumSolver_Explicit_RungeKutta4().

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

Local to this translation unit.

Definition at line 99 of file momentumsolvers.c.

100{
101 PetscErrorCode ierr;
102 (void)fsi;
103 // --- Context Acquisition ---
104 // Get the master simulation context from the first block's UserCtx.
105 // This is the bridge to access all former global variables.
106 SimCtx *simCtx = user[0].simCtx;
107 PetscReal dt = simCtx->dt;
108 //PetscReal st = simCtx->st;
109 PetscInt istage;
110 PetscReal alfa[] = {0.25, 1.0/3.0, 0.5, 1.0};
111
112 PetscFunctionBeginUser;
113
115
116 LOG_ALLOW(GLOBAL, LOG_INFO, "Executing explicit momentum solver (Runge-Kutta) for %d block(s).\n",simCtx->block_number);
117
118 // --- 1. Pre-Loop Initialization (Legacy Logic) ---
119 // This block prepares boundary conditions and allocates the RHS vector for all blocks
120 // before the main RK loop begins. This logic is preserved from the original code.
121 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Preparing all blocks for Runge-Kutta solve...\n");
122 for (PetscInt bi = 0; bi < simCtx->block_number; bi++) {
123
124 ierr = ApplyBoundaryConditions(&user[bi]); CHKERRQ(ierr);
125
126 /*
127 // Immersed boundary interpolation (if enabled)
128 if (simCtx->immersed) {
129 LOG_ALLOW(LOCAL, LOG_DEBUG, " Performing pre-RK IBM interpolation for block %d.\n", bi);
130 for (PetscInt ibi = 0; ibi < simCtx->NumberOfBodies; ibi++) {
131 // The 'ibm' and 'fsi' pointers are passed directly from FlowSolver
132 ierr = ibm_interpolation_advanced(&user[bi], &ibm[ibi], ibi, 1); CHKERRQ(ierr);
133 }
134 }
135 */
136
137 // Allocate the persistent RHS vector for this block's context
138 ierr = VecDuplicate(user[bi].Ucont, &user[bi].Rhs); CHKERRQ(ierr);
139 }
140 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Pre-loop initialization complete.\n");
141
142 // --- 2. Main Runge-Kutta Loop ---
143 // The legacy code had an outer `pseudot` loop that only ran once. We preserve it.
144 for (PetscInt pseudot = 0; pseudot < 1; pseudot++) {
145 // Loop over each block to perform the RK stages
146 for (PetscInt bi = 0; bi < simCtx->block_number; bi++) {
147 for (istage = 0; istage < 4; istage++) {
148 LOG_ALLOW(LOCAL, LOG_DEBUG, " Block %d, RK Stage %d (alpha=%.4f)...\n", bi, istage, alfa[istage]);
149
150 // a. Calculate the Right-Hand Side (RHS) of the momentum equation.
151 ierr = ComputeRHS(&user[bi], user[bi].Rhs); CHKERRQ(ierr);
152
153 // b. Advance Ucont to the next intermediate stage using the RK coefficient.
154 // Ucont_new = Ucont_old + alpha * dt * RHS
155 ierr = VecWAXPY(user[bi].Ucont, alfa[istage] * dt, user[bi].Rhs, user[bi].Ucont_o); CHKERRQ(ierr);
156
157 // c. Synchronize periodic endpoints and local ghosts for the new intermediate Ucont.
158 const FieldId staggered_fields[] = {FIELD_ID_UCONT};
159 ierr = SynchronizePeriodicStaggeredFields(&user[bi], 1, staggered_fields); CHKERRQ(ierr);
160
161 // d. Re-apply boundary conditions for the new intermediate velocity.
162 // This is crucial for the stability and accuracy of the multi-stage scheme.
163 ierr = ApplyBoundaryConditions(&user[bi]); CHKERRQ(ierr);
164
165 } // End of RK stages for one block
166
167 /* Nothing limits dt on this path, so an unstable step only showed up later,
168 as a non-finite Poisson solve blamed on the multigrid depth. Name the
169 actual cause where it arises. */
170 {
171 /* Divergence usually overflows the Poisson right-hand side before the
172 velocity itself stops being finite, so a jump no flow can make in one
173 step is treated the same as a non-finite value. */
174 PetscReal ucont_max = 0.0, ucont_start = 0.0;
175 ierr = VecNorm(user[bi].Ucont, NORM_INFINITY, &ucont_max); CHKERRQ(ierr);
176 ierr = VecNorm(user[bi].Ucont_o, NORM_INFINITY, &ucont_start); CHKERRQ(ierr);
177 PetscCheck(!PetscIsInfOrNanReal(ucont_max) && ucont_max <= 1.0e10 * (1.0 + ucont_start),
178 PETSC_COMM_WORLD, PETSC_ERR_FP,
179 "Explicit RK4 diverged on block %" PetscInt_FMT
180 " at step %" PetscInt_FMT " with dt = %g: the step exceeds the explicit "
181 "stability limit. RK4 needs roughly dt < 2.8 / (4 nu (1/dx^2 + 1/dy^2 + 1/dz^2)) "
182 "for viscosity and a convective CFL of order 1. Reduce dt, or use "
183 "'Dual Time Picard Jameson RK', which has no such limit.",
184 bi, simCtx->step, (double)dt);
185 }
186
187 /*
188 // Final IBM Interpolation for the block (if enabled)
189 if (simCtx->immersed) {
190 LOG_ALLOW(LOCAL, LOG_DEBUG, " Performing post-RK IBM interpolation for block %d.\n", bi);
191 for (PetscInt ibi = 0; ibi < simCtx->NumberOfBodies; ibi++) {
192 ierr = ibm_interpolation_advanced(&user[bi], &ibm[ibi], ibi, 1); CHKERRQ(ierr);
193 }
194 }
195 */
196
197 } // End loop over blocks
198
199 // --- 3. Inter-Block Communication (Legacy Logic) ---
200 // This is called after all blocks have completed their RK stages.
201 if (simCtx->block_number > 1) {
202 // LOG_ALLOW(GLOBAL, LOG_DEBUG, "Updating multi-block interfaces after RK stages.\n");
203 // ierr = Block_Interface_U(user); CHKERRQ(ierr);
204 }
205
206 } // End of pseudo-time loop
207
208 // --- 4. Cleanup ---
209 // Destroy the RHS vectors that were created at the start of this function.
210 for (PetscInt bi = 0; bi < simCtx->block_number; bi++) {
211 ierr = VecDestroy(&user[bi].Rhs); CHKERRQ(ierr);
212 }
213
214 LOG_ALLOW(GLOBAL, LOG_INFO, "Runge-Kutta solve completed for all blocks.\n");
215
217
218 PetscFunctionReturn(0);
219}
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.
FieldId
Compile-time identity for a catalogued Eulerian field.
@ FIELD_ID_UCONT
#define LOCAL
Logging scope definitions for controlling message output.
Definition logging.h:45
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:46
#define LOG_ALLOW(scope, level, fmt,...)
Logging macro that checks both the log level and whether the calling function is in the allowed-funct...
Definition logging.h:200
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
PetscInt block_number
Definition variables.h:952
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ComputeGlobalSpectralRadiusEstimate()

static PetscErrorCode ComputeGlobalSpectralRadiusEstimate ( UserCtx *  user,
PetscInt  block_number,
PetscReal  dt,
PetscReal *  lambda_max_out 
)
static

Compute a conservative global pseudo-time spectral radius estimate.

The spectral radius lambda_max (units: 1/s) governs the pseudo-time stability limit for the explicit Jameson 4-stage RK smoother. The pseudo-time step is then:

dtau = pseudo_cfl / lambda_max

making pseudo_cfl a true dimensionless Courant number independent of the physical timestep dt. Note: 2.83 is only the 4-stage RK imaginary-axis scalar limit; it is NOT a generally-stable bound for the actual nonlinear/non-normal operator (see A4 validation).

Per owned cell (k,j,i) the convective spectral radius contribution is:

lambda_cell = (|U_xi_flux| + |U_eta_flux| + |U_zeta_flux|) x (1/V)

where:

  • U_xi_flux = ucont[k][j][i].x : volumetric flux through the xi-face [m3/s]
  • U_eta_flux = ucont[k][j][i].y : volumetric flux through the eta-face [m3/s]
  • U_zeta_flux = ucont[k][j][i].z : volumetric flux through the zeta-face [m3/s]
  • 1/V = lAj[k][j][i] : inverse cell volume (cell Jacobian) [1/m3]

Product units: [m3/s * 1/m3] = [1/s] = spectral radius.

Only the 'positive' face at index (k,j,i) is used per direction (no neighbor access), so no ghost-cell scatter is needed. lUcont is valid after the pre-loop SynchronizePeriodicStaggeredFields; lAj is set at grid initialisation and never changes. No DMGlobalToLocal calls are added.

A BDF2 lower bound COEF_TIME_ACCURACY/dt is applied after the MPI reduction so that zero-flow startup (all ucont == 0) gives dtau = pseudo_cfl * dt / 1.5 rather than infinity.

Parameters
[in]userArray of UserCtx (one per block).
[in]block_numberNumber of blocks.
[in]dtPhysical time step (for the BDF2 lower bound).
[out]lambda_max_outGlobal spectral radius [1/s].

Definition at line 261 of file momentumsolvers.c.

263{
264 PetscErrorCode ierr;
265 PetscReal local_max = 0.0;
266
267 PetscFunctionBeginUser;
268
269 for (PetscInt bi = 0; bi < block_number; bi++) {
270 DMDALocalInfo info = user[bi].info; /* pre-stored local ownership info */
271 Cmpnts ***ucont;
272 PetscReal ***aj;
273
274 /* Read-only access: lUcont (valid after SynchronizePeriodicStaggeredFields),
275 lAj (grid metric, unchanged after initialisation). */
276 ierr = DMDAVecGetArrayRead(user[bi].fda, user[bi].lUcont, &ucont); CHKERRQ(ierr);
277 ierr = DMDAVecGetArrayRead(user[bi].da, user[bi].lAj, &aj); CHKERRQ(ierr);
278
279 /* Loop over owned cells only — no ghost-cell indices accessed. */
280 for (PetscInt k = info.zs; k < info.zs + info.zm; k++) {
281 for (PetscInt j = info.ys; j < info.ys + info.ym; j++) {
282 for (PetscInt i = info.xs; i < info.xs + info.xm; i++) {
283 /* Sum absolute face-flux magnitudes (one face per coordinate direction).
284 * Using only the 'positive' face (index i/j/k, not i-1/j-1/k-1) avoids
285 * neighbor access. For smooth velocity fields the error is < 2x and the
286 * adaptive CFL controller handles any residual conservatism. */
287 PetscReal flux_sum = PetscAbsReal(ucont[k][j][i].x)
288 + PetscAbsReal(ucont[k][j][i].y)
289 + PetscAbsReal(ucont[k][j][i].z);
290
291 /* aj = 1/J = 1/cell_volume; lambda = flux/volume [1/s] */
292 PetscReal lambda = flux_sum * aj[k][j][i];
293 local_max = PetscMax(local_max, lambda);
294 }
295 }
296 }
297
298 ierr = DMDAVecRestoreArrayRead(user[bi].fda, user[bi].lUcont, &ucont); CHKERRQ(ierr);
299 ierr = DMDAVecRestoreArrayRead(user[bi].da, user[bi].lAj, &aj); CHKERRQ(ierr);
300 }
301
302 /* Reduce across all MPI ranks to get the global maximum. */
303 ierr = MPI_Allreduce(&local_max, lambda_max_out, 1, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD);
304 CHKERRQ(ierr);
305
306 /* BDF2 lower bound: if the velocity field is zero (startup, stagnant IC) lambda_max
307 would be 0, giving dtau = inf. Clamp to 1.5/dt so dtau = pseudo_cfl * dt/1.5 in
308 that degenerate case (conservative but finite). */
309 *lambda_max_out = PetscMax(*lambda_max_out, COEF_TIME_ACCURACY / dt);
310
311 PetscFunctionReturn(0);
312}
DMDALocalInfo info
Definition variables.h:1086
A 3D point or vector with PetscScalar components.
Definition variables.h:121
Here is the caller graph for this function:

◆ MomFaceGabs()

static PetscReal MomFaceGabs ( Cmpnts  N,
Cmpnts  A,
Cmpnts  B 
)
inlinestatic

Definition at line 318 of file momentumsolvers.c.

319{
320 const PetscReal NN = N.x*N.x + N.y*N.y + N.z*N.z;
321 const PetscReal AN = A.x*N.x + A.y*N.y + A.z*N.z;
322 const PetscReal BN = B.x*N.x + B.y*N.y + B.z*N.z;
323 return PetscAbsReal(NN) + PetscAbsReal(AN) + PetscAbsReal(BN);
324}
PetscScalar x
Definition variables.h:122
PetscScalar z
Definition variables.h:122
PetscScalar y
Definition variables.h:122
Here is the caller graph for this function:

◆ MomCellUsesOneSidedViscousStencil()

static PetscBool MomCellUsesOneSidedViscousStencil ( PetscReal ***  nvert,
PetscInt  k,
PetscInt  j,
PetscInt  i 
)
static

Definition at line 338 of file momentumsolvers.c.

339{
340 for (PetscInt dk = -1; dk <= 1; dk++)
341 for (PetscInt dj = -1; dj <= 1; dj++)
342 for (PetscInt di = -1; di <= 1; di++) {
343 if (!dk && !dj && !di) continue;
344 if (MOM_VISC_ONESIDED(nvert[k+dk][j+dj][i+di])) return PETSC_TRUE;
345 }
346 return PETSC_FALSE;
347}
#define MOM_VISC_ONESIDED(v)
Here is the caller graph for this function:

◆ MomCellHasSolidNeighbor()

static PetscBool MomCellHasSolidNeighbor ( PetscReal ***  nvert,
PetscInt  k,
PetscInt  j,
PetscInt  i 
)
static

Definition at line 351 of file momentumsolvers.c.

352{
353 for (PetscInt dk = -1; dk <= 1; dk++)
354 for (PetscInt dj = -1; dj <= 1; dj++)
355 for (PetscInt di = -1; di <= 1; di++) {
356 if (!dk && !dj && !di) continue;
357 if (MOM_SKIP_SOLID(nvert[k+dk][j+dj][i+di])) return PETSC_TRUE;
358 }
359 return PETSC_FALSE;
360}
#define MOM_SKIP_SOLID(v)
Here is the caller graph for this function:

◆ MomQuickDirModified()

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

Definition at line 367 of file momentumsolvers.c.

370{
371 /* (a,b,c) are the fixed indices; idx is the moving index in direction `dir`.
372 The two contributing faces (idx, idx-1) have QUICK stencils spanning idx-2..idx+2. */
373 if (np0 && idx <= 1) return PETSC_TRUE; /* faces idx,idx-1 reach the negative edge */
374 if (np1 && idx >= m-2) return PETSC_TRUE; /* ... or the positive edge */
375 for (PetscInt d = -2; d <= 2; d++) {
376 if (d == 0) continue;
377 const PetscInt p = idx + d;
378 /* Required stencil information unavailable in the local ghost range: do NOT assume
379 fluid -> conservatively classify the direction as modified (use 2.5). */
380 if (p < g0 || p >= g1) return PETSC_TRUE;
381 PetscReal v;
382 if (dir == 'i') v = nvert[a][b][p];
383 else if (dir == 'j') v = nvert[a][p][c];
384 else v = nvert[p][b][c];
385 if (MOM_QUICK_BLOCKS(v)) return PETSC_TRUE;
386 }
387 return PETSC_FALSE;
388}
#define MOM_QUICK_BLOCKS(v)
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 394 of file momentumsolvers.c.

397{
398 if (MOM_SKIP_SOLID(nvert[k][j][i])) return 0; /* solid cell: all rows inactive */
399 PetscInt bits = 0x7;
400 if (MOM_SKIP_SOLID(nvert[k][j][i+1])) bits &= ~0x1; /* positive xi neighbour solid */
401 if (MOM_SKIP_SOLID(nvert[k][j+1][i])) bits &= ~0x2; /* positive eta neighbour solid */
402 if (MOM_SKIP_SOLID(nvert[k+1][j][i])) bits &= ~0x4; /* positive zeta neighbour solid */
403 if (np_x1 && i == mx-2) bits &= ~0x1; /* positive non-periodic xi face */
404 if (np_y1 && j == my-2) bits &= ~0x2;
405 if (np_z1 && k == mz-2) bits &= ~0x4;
406 if (twoD == 1) bits &= ~0x1; /* TwoD homogeneous direction: xi */
407 else if (twoD == 2) bits &= ~0x2; /* eta */
408 else if (twoD == 3) bits &= ~0x4; /* zeta */
409 return bits;
410}
Here is the caller graph for this function:

◆ ComputeMomentumStabilityEstimate()

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

Practical conservative momentum pseudo-time stability estimate (shadow).

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 422 of file momentumsolvers.c.

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

◆ MomentumSolver_DualTime_Picard_JamesonRK()

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

Internal helper implementation: MomentumSolver_DualTime_Picard_JamesonRK().

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

Local to this translation unit.

Definition at line 768 of file momentumsolvers.c.

769{
770 (void)ibm;
771 (void)fsi;
772 // --- CONTEXT ACQUISITION BLOCK ---
773 SimCtx *simCtx = user[0].simCtx;
774 const PetscInt block_number = simCtx->block_number;
775
776 // Legacy Names (Physics/Time) - Kept for brevity in formulas
777 const PetscInt ti = simCtx->step;
778 const PetscReal dt = simCtx->dt; // Physical Time Step
779 /* cfl: initial pseudo-CFL Courant number (dimensionless, flow-independent since Phase 3).
780 * Actual dtau = cfl / lambda_max, computed after the spectral radius estimate below. */
781 const PetscReal cfl = simCtx->pseudo_cfl;
782 const PetscReal alfa[] = {0.25, 1.0/3.0, 0.5, 1.0}; // Jameson RK smoothing coefficients
783 Vec *pRhs; // Per-block backup of Rhs at last accepted pseudo-state.
784
785 // State flags
786 PetscBool force_restart = PETSC_FALSE;
787
788 // Renamed Solver Parameters
789 const PetscInt max_pseudo_steps = simCtx->mom_max_pseudo_steps; // Max Dual-Time Iterations
790 const PetscReal tol_abs_delta = simCtx->mom_atol; // Stop if |dU| < tol
791 const PetscReal tol_rtol_delta = simCtx->mom_rtol; // Stop if |dU|/|dU0| < tol
792 // --- END CONTEXT ACQUISITION BLOCK ---
793
794 PetscErrorCode ierr;
795 PetscMPIInt rank;
796 PetscInt istage, pseudo_iter;
797 PetscInt accepted_iter = 0, rejected_iter = 0, recovery_streak = 0;
798 PetscReal ts, te, cput;
799
800 // --- Global Convergence Metrics ---
801 PetscReal global_norm_delta = 10.0;
802 PetscReal global_rel_delta = 1.0;
803 PetscReal global_norm_resid = 1.0;
804 PetscReal global_rel_resid = 1.0;
805 PetscReal smoothed_trial_ratio = 1.0; /* EMA-smoothed ratio; initialized neutral */
806
807 // --- Spectral-radius-based pseudo-time step ---
808 /* lambda_max: global maximum spectral radius [1/s], computed once per physical timestep.
809 * pseudo_dtau[bi]: physical pseudo-time step dtau = pseudo_cfl / lambda_max [time].
810 * Unlike the old scheme (dtau = pseudo_cfl * dt), this is independent of dt and makes
811 * pseudo_cfl a true Courant number. (2.83 is only the scalar imaginary-axis RK limit,
812 * not a generally-stable value for the actual operator -- to be characterized in A4.)
813 * dtau_min / dtau_max: per-timestep bounds derived from simCtx->min/max_pseudo_cfl. */
814 PetscReal lambda_max = 0.0;
815 /* cfl_cap: dimensionless memory of where trials have failed. Without it the controller
816 * re-discovers the stability limit every physical step -- measured on the laminar
817 * channel it climbed 1.36 -> 2.00, rejected, recovered, and climbed straight back, with
818 * 28.8% of all trials spent above the limit. Carried in CFL (not dtau) so it stays
819 * meaningful as lambda_max evolves; see the rejection and growth paths below. */
820 PetscReal cfl_cap = simCtx->max_pseudo_cfl;
821 /* resid_ref: reference scale for the residual [same units as R], recomputed each
822 * physical step. See the assembly below for why the absolute residual tolerance is
823 * measured against this rather than being a raw dimensional bound. */
824 PetscReal resid_ref = 0.0;
825 PetscReal dtau_min, dtau_max;
826
827 // --- Local Metric Arrays (Per Block) ---
828 PetscReal *delta_sol_norm_init, *delta_sol_norm_prev, *delta_sol_norm_curr, *delta_sol_rel_curr;
829 PetscReal *resid_norm_init, *resid_norm_prev, *resid_norm_curr, *resid_rel_curr;
830 PetscReal *pseudo_dtau; /* per-block pseudo-time step dtau [physical time, NOT dtau/dt] */
831 PetscReal *trial_ratio_log; /* per-block step-to-step residual ratio (for diagnostics) */
832 PetscReal last_accepted_resid; /* last accepted |R|, for final summary */
833
834 PetscFunctionBeginUser;
836 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
837 LOG_ALLOW(GLOBAL, LOG_INFO, "Executing Dual-Time Momentum Solver for %d block(s).\n", block_number);
838
839 // --- Allocate metric arrays ---
840 ierr = PetscMalloc2(block_number, &delta_sol_norm_init, block_number, &delta_sol_norm_prev); CHKERRQ(ierr);
841 ierr = PetscMalloc2(block_number, &delta_sol_norm_curr, block_number, &delta_sol_rel_curr); CHKERRQ(ierr);
842 ierr = PetscMalloc2(block_number, &resid_norm_init, block_number, &resid_norm_prev); CHKERRQ(ierr);
843 ierr = PetscMalloc2(block_number, &resid_norm_curr, block_number, &pseudo_dtau); CHKERRQ(ierr);
844 ierr = PetscMalloc1(block_number, &resid_rel_curr); CHKERRQ(ierr);
845 ierr = PetscMalloc1(block_number, &trial_ratio_log); CHKERRQ(ierr);
846 ierr = PetscMalloc1(block_number, &pRhs); CHKERRQ(ierr);
847 last_accepted_resid = 0.0;
848
849 ierr = PetscTime(&ts); CHKERRQ(ierr);
850
851 // --- 1. Pre-Loop Initialization ---
852
853 if (block_number > 1) {
854 // ierr = Block_Interface_U(user); CHKERRQ(ierr);
855 }
856
857 for (PetscInt bi = 0; bi < block_number; bi++) {
858 ierr = ApplyBoundaryConditions(&user[bi]); CHKERRQ(ierr);
859
860 // Immersed boundary interpolation (if enabled)
861
862 // Allocate workspace
863 ierr = VecDuplicate(user[bi].Ucont, &user[bi].Rhs); CHKERRQ(ierr);
864 ierr = VecDuplicate(user[bi].Rhs, &pRhs[bi]); CHKERRQ(ierr);
865 ierr = VecDuplicate(user[bi].Ucont, &user[bi].dUcont); CHKERRQ(ierr);
866 ierr = VecDuplicate(user[bi].Ucont, &user[bi].pUcont); CHKERRQ(ierr);
867
868 // Initialize Backup (pUcont) with current state
869 ierr = VecCopy(user[bi].Ucont, user[bi].pUcont); CHKERRQ(ierr);
870
871 // --- Calculate Initial Total Residual (Spatial + Temporal) ---
872 ierr = ComputeTotalResidual(&user[bi]); CHKERRQ(ierr);
873
874 // Backup Rhs with current state
875 ierr = VecCopy(user[bi].Rhs, pRhs[bi]); CHKERRQ(ierr);
876
877 // Compute Initial Norms
878 ierr = VecNorm(user[bi].Rhs, NORM_INFINITY, &resid_norm_init[bi]); CHKERRQ(ierr);
879
880 /* Reference scale for the absolute residual test (see its use below). VecNorm is
881 * collective, so this is already global; take the max across blocks. */
882 {
883 PetscReal ucont_inf;
884 ierr = VecNorm(user[bi].Ucont, NORM_INFINITY, &ucont_inf); CHKERRQ(ierr);
885 resid_ref = PetscMax(resid_ref, MomentumBDFCoefficient(simCtx) * ucont_inf / dt);
886 }
887
888 // Initialize history for backtracking logic
889 resid_norm_prev[bi] = resid_norm_init[bi]; /* meaningful ratio on first iteration */
890 delta_sol_norm_prev[bi] = 1000.0;
891 /* pseudo_dtau[bi] is set after ComputeGlobalSpectralRadiusEstimate below. */
892
893 LOG_ALLOW(GLOBAL,LOG_INFO," Block %d | Max RHS = %.6f | initial pseudo-CFL = %.4f .\n", bi, resid_norm_init[bi], cfl);
894 }
895
896 /* --- Spectral Radius Estimate (computed once per physical timestep) ---
897 * lambda_max [1/s] is the global maximum of (face_flux_sum * inverse_cell_volume)
898 * over all owned cells across all MPI ranks. See ComputeGlobalSpectralRadiusEstimate().
899 *
900 * dtau = pseudo_cfl / lambda_max (pseudo_cfl is now a true Courant number)
901 * dtau_min/max are derived from the user-specified min/max_pseudo_cfl bounds.
902 *
903 * The BDF2 lower bound inside the helper ensures lambda_max >= 1.5/dt, so dtau
904 * remains finite even when the velocity field is zero (startup / stagnant ICs). */
905 ierr = ComputeGlobalSpectralRadiusEstimate(user, block_number, dt, &lambda_max); CHKERRQ(ierr);
906 dtau_min = simCtx->min_pseudo_cfl / lambda_max;
907 dtau_max = simCtx->max_pseudo_cfl / lambda_max;
908
909 /* Initialise per-block pseudo-time step from the warm-start CFL (carried from last timestep). */
910 for (PetscInt bi = 0; bi < block_number; bi++) {
911 pseudo_dtau[bi] = cfl / lambda_max; /* dtau [physical time], NOT a fraction of dt */
912 }
913
914 /* --- SHADOW MODE: new operator-scaled stability estimate (diagnostic only) ---
915 * Computes the Workstream-A estimate alongside the legacy one and logs a compact
916 * comparison. The legacy estimate above continues to drive production dtau; the
917 * shadow estimate changes nothing. Enable with -mom_stability_shadow. */
918 {
919 PetscBool shadow = PETSC_FALSE;
920 ierr = PetscOptionsGetBool(NULL, NULL, "-mom_stability_shadow", &shadow, NULL); CHKERRQ(ierr);
921 if (shadow) {
923 ierr = ComputeMomentumStabilityEstimate(user, block_number, dt, MOM_STAB_CAND_C, &rep); CHKERRQ(ierr);
924 const char *lim = (rep.limiter==MOM_STAB_LIMITER_TIME) ? "time"
925 : (rep.limiter==MOM_STAB_LIMITER_CONVECTION) ? "convection" : "viscosity";
926 /* Detailed shadow comparison at DEBUG only (gated by level + function allowlist);
927 never printed unconditionally. Enable in tests via the logging controls. */
929 "Momentum scale [shadow]: legacy_dtau=%.4e new_dtau=%.4e ratio=%.4f limiter=%s | "
930 "lambda_legacy=%.4e lambda_new=%.4e (B=%.4e C=%.4e D=%.4e) | "
931 "lt=%.4e lc=%.4e lv=%.4e | cell=(%d,%d,%d) blk=%d class=%d onesided=%d\n",
932 cfl / lambda_max, cfl / rep.lambda, rep.lambda / lambda_max, lim,
933 lambda_max, rep.lambda, rep.lambda_B, rep.lambda_C, rep.lambda_D,
934 rep.lambda_t, rep.lambda_c, rep.lambda_v,
935 (int)rep.ci, (int)rep.cj, (int)rep.ck, (int)rep.cblock, (int)rep.cclass, (int)rep.one_sided);
936 }
937 }
938
940 "Dual-time solver: lambda_max=%.4e [1/s] resid_ref=%.4e dtau_init=%.4e dtau range [%.4e, %.4e] "
941 "CFL range [%.4f, %.4f] rejection_threshold=%.3f (EMA alpha=%.2f) "
942 "growth=%.3f reduction=%.3f max_accepted=%d.\n",
943 lambda_max, resid_ref, cfl / lambda_max, dtau_min, dtau_max,
944 simCtx->min_pseudo_cfl, simCtx->max_pseudo_cfl,
946 simCtx->mom_ratio_ema_alpha,
948 max_pseudo_steps);
949
950 // --- 2. Main Pseudo-Time Iteration Loop ---
951 pseudo_iter = 0;
952 PetscBool residual_convergence_enabled =
953 (PetscBool)(simCtx->mom_resid_atol > 0.0 || simCtx->mom_resid_rtol > 0.0);
954 PetscBool converged = PETSC_FALSE;
955 PetscBool last_trial_nonfinite = PETSC_FALSE;
956 /* Rejected iterations no longer consume the accepted-iteration budget.
957 A hard cap of 3x max_pseudo_steps guards against infinite rejection loops. */
958 const PetscInt max_total_attempts = max_pseudo_steps * 3;
959 while (!converged && accepted_iter < max_pseudo_steps && pseudo_iter < max_total_attempts)
960 {
961 pseudo_iter++;
962 force_restart = PETSC_FALSE;
963
964 // Every attempt starts from the last globally accepted state.
965 for (PetscInt bi = 0; bi < block_number; bi++) {
966 ierr = VecCopy(user[bi].pUcont, user[bi].Ucont); CHKERRQ(ierr);
967 ierr = VecCopy(pRhs[bi], user[bi].Rhs); CHKERRQ(ierr);
968 const FieldId staggered_fields[] = {FIELD_ID_UCONT};
969 ierr = SynchronizePeriodicStaggeredFields(&user[bi], 1, staggered_fields); CHKERRQ(ierr);
970 ierr = ApplyBoundaryConditions(&user[bi]); CHKERRQ(ierr);
971 }
972
973 for (PetscInt bi = 0; bi < block_number; bi++) {
974
975 // === 4-Stage Jameson RK Smoothing Loop ===
976 for (istage = 0; istage < 4; istage++) {
977
978 LOG_ALLOW(GLOBAL,LOG_TRACE," Pseudo-Iter: %d | RK-Stage: %d\n", pseudo_iter, istage);
979
980 /* RK Update: U_new = U_old + (dtau * alpha) * Residual
981 * pseudo_dtau[bi] IS dtau [physical time] — no further multiplication by dt.
982 * (In the old scheme dtau = pseudo_cfl * dt; here dtau = pseudo_cfl / lambda_max.) */
983 ierr = VecWAXPY(user[bi].Ucont,
984 pseudo_dtau[bi] * alfa[istage],
985 user[bi].Rhs,
986 user[bi].pUcont); CHKERRQ(ierr);
987
988 // Sync Ghosts & Re-apply BCs for intermediate stage
989 const FieldId staggered_fields[] = {FIELD_ID_UCONT};
990 ierr = SynchronizePeriodicStaggeredFields(&user[bi], 1, staggered_fields); CHKERRQ(ierr);
991
992 ierr = ApplyBoundaryConditions(&user[bi]); CHKERRQ(ierr);
993
994 // --- Re-calculate Total Residual for next stage ---
995 ierr = ComputeTotalResidual(&user[bi]); CHKERRQ(ierr);
996
997 } // End RK Stages
998
999 // Immersed boundary interpolation (if enabled)
1000
1001 // === Convergence Metrics Calculation ===
1002
1003 // Calculate dU = U_current - U_backup
1004 ierr = VecWAXPY(user[bi].dUcont, -1.0, user[bi].pUcont, user[bi].Ucont); CHKERRQ(ierr);
1005
1006 // Compute Infinity Norms
1007 ierr = VecNorm(user[bi].dUcont, NORM_INFINITY, &delta_sol_norm_curr[bi]); CHKERRQ(ierr);
1008 ierr = VecNorm(user[bi].Rhs, NORM_INFINITY, &resid_norm_curr[bi]); CHKERRQ(ierr);
1009
1010 // Normalize relative metrics
1011 if (pseudo_iter == 1) {
1012 delta_sol_norm_init[bi] = delta_sol_norm_curr[bi];
1013 delta_sol_rel_curr[bi] = 1.0;
1014 resid_rel_curr[bi] = 1.0;
1015 // resid_norm_init[bi] set correctly before the loop — do not overwrite
1016 } else {
1017 if (delta_sol_norm_init[bi] > 1.0e-10)
1018 delta_sol_rel_curr[bi] = delta_sol_norm_curr[bi] / delta_sol_norm_init[bi];
1019 else
1020 delta_sol_rel_curr[bi] = 0.0;
1021
1022 if(resid_norm_init[bi] > 1.0e-10)
1023 resid_rel_curr[bi] = resid_norm_curr[bi] / resid_norm_init[bi];
1024 else
1025 resid_rel_curr[bi] = 0.0;
1026 }
1027
1028 /* Pre-compute per-block step-to-step trial ratio for later file logging. */
1029 {
1030 const PetscReal resid_floor_log = 1.0e-30;
1031 if (resid_norm_prev[bi] > resid_floor_log)
1032 trial_ratio_log[bi] = resid_norm_curr[bi] / resid_norm_prev[bi];
1033 else if (resid_norm_curr[bi] <= resid_floor_log)
1034 trial_ratio_log[bi] = 0.0;
1035 else
1036 trial_ratio_log[bi] = PETSC_MAX_REAL;
1037 }
1038 } // End loop over blocks
1039
1040 // --- Update Global Convergence Criteria ---
1041 global_norm_delta = -1.0e20;
1042 global_rel_delta = -1.0e20;
1043 global_norm_resid = -1.0e20;
1044 global_rel_resid = -1.0e20;
1045
1046 for (PetscInt bi = 0; bi < block_number; bi++) {
1047 global_norm_delta = PetscMax(delta_sol_norm_curr[bi], global_norm_delta);
1048 global_rel_delta = PetscMax(delta_sol_rel_curr[bi], global_rel_delta);
1049 global_norm_resid = PetscMax(resid_norm_curr[bi], global_norm_resid);
1050 global_rel_resid = PetscMax(resid_rel_curr[bi], global_rel_resid);
1051 }
1052 ierr = PetscTime(&te); CHKERRQ(ierr);
1053 cput = te - ts;
1054 LOG_ALLOW(GLOBAL, LOG_INFO, " Pseudo-Iter(k) %d: |dUk|=%e, |dUk|/|dU0| = %e, |Rk|/|R0| = %e, CPU=%.2fs\n",
1055 pseudo_iter, global_norm_delta, global_rel_delta, global_rel_resid, cput);
1056
1057 // === Adaptive Pseudo-CFL Trial Acceptance and Rollback ===
1058 const PetscReal resid_floor = 1.0e-30;
1059 PetscReal global_trial_ratio = 0.0;
1060 PetscBool global_nonfinite = PETSC_FALSE;
1061 for (PetscInt bi = 0; bi < block_number; bi++) {
1062 PetscReal ratio;
1063 if (resid_norm_prev[bi] > resid_floor) {
1064 ratio = resid_norm_curr[bi] / resid_norm_prev[bi];
1065 } else if (resid_norm_curr[bi] <= resid_floor) {
1066 ratio = 0.0;
1067 } else {
1068 ratio = PETSC_MAX_REAL;
1069 }
1070 global_trial_ratio = PetscMax(global_trial_ratio, ratio);
1071 global_nonfinite = (PetscBool)(global_nonfinite ||
1072 PetscIsInfOrNanReal(delta_sol_norm_curr[bi]) ||
1073 PetscIsInfOrNanReal(resid_norm_curr[bi]) ||
1074 PetscIsInfOrNanReal(ratio));
1075 }
1076
1077 PetscReal reduced_trial_ratio;
1078 PetscMPIInt local_nonfinite = global_nonfinite ? 1 : 0;
1079 PetscMPIInt reduced_nonfinite = 0;
1080 ierr = MPI_Allreduce(&global_trial_ratio, &reduced_trial_ratio, 1, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD); CHKERRQ(ierr);
1081 ierr = MPI_Allreduce(&local_nonfinite, &reduced_nonfinite, 1, MPI_INT, MPI_LOR, PETSC_COMM_WORLD); CHKERRQ(ierr);
1082 global_trial_ratio = reduced_trial_ratio;
1083 global_nonfinite = reduced_nonfinite ? PETSC_TRUE : PETSC_FALSE;
1084 last_trial_nonfinite = global_nonfinite;
1085
1086 /* EMA-smooth the trial ratio to tolerate occasional non-monotonic residual bumps.
1087 Non-finite trials bypass smoothing and always trigger rollback.
1088
1089 The smoothed value is only a CANDIDATE until the trial is accepted. A rejected
1090 trial is rolled back -- its state never happened -- so letting its ratio persist
1091 into the next decision is a state leak, and a costly one: measured on the laminar
1092 channel, a diverging trial at cfl 2.0 (raw ratio 3.97) pushed the EMA to 1.61, and
1093 the *next* trial at cfl 1.5 was rejected too despite a raw ratio of 0.994, i.e.
1094 despite the residual actually decreasing. That false rejection cost four discarded
1095 ComputeRHS calls and drove dtau down to cfl 1.125, below the measured optimum.
1096 The dtau reduction on the rejection path already carries the information; the EMA
1097 does not need to carry it as well. */
1098 PetscReal trial_smoothed = smoothed_trial_ratio;
1099 if (!global_nonfinite) {
1100 trial_smoothed = simCtx->mom_ratio_ema_alpha * global_trial_ratio
1101 + (1.0 - simCtx->mom_ratio_ema_alpha) * smoothed_trial_ratio;
1102 }
1104 " [k=%d] raw_ratio=%.4e smoothed_ratio=%.4e (threshold=%.3f) | "
1105 "|R_prev|=%.6e | |R_curr|=%.6e | CFL=%.6f\n",
1106 pseudo_iter, global_trial_ratio, trial_smoothed,
1108 resid_norm_prev[0], resid_norm_curr[0], pseudo_dtau[0]);
1109
1110 if (simCtx->no_pseudo_cfl_backtrack) {
1111 /* Diagnostic mode: commit every finite trial; only non-finite triggers rollback. */
1112 force_restart = global_nonfinite;
1113 } else {
1114 force_restart = (PetscBool)(global_nonfinite ||
1116 }
1117
1118 if (force_restart) {
1119 /* old_dtau: dtau used for this (rejected) trial [physical time].
1120 * next_dtau: reduced dtau for the retry, clamped to dtau_min.
1121 * Both are physical-time values; the effective CFL = dtau * lambda_max. */
1122 PetscReal old_dtau = pseudo_dtau[0];
1123 PetscReal next_dtau = PetscMax(dtau_min, old_dtau * simCtx->pseudo_cfl_reduction_factor);
1124 /* This CFL failed: never grow back to it. MOM_CFL_CAP_SAFETY keeps the ceiling
1125 * off the edge, where the smoother damps worst even when it is still stable. */
1126 cfl_cap = PetscMax(simCtx->min_pseudo_cfl,
1127 PetscMin(cfl_cap, old_dtau * lambda_max * MOM_CFL_CAP_SAFETY));
1128 rejected_iter++;
1129 recovery_streak = 0;
1130 for (PetscInt bi = 0; bi < block_number; bi++) {
1131 ierr = VecCopy(user[bi].pUcont, user[bi].Ucont); CHKERRQ(ierr);
1132 ierr = VecCopy(pRhs[bi], user[bi].Rhs); CHKERRQ(ierr);
1133 const FieldId staggered_fields[] = {FIELD_ID_UCONT};
1134 ierr = SynchronizePeriodicStaggeredFields(&user[bi], 1, staggered_fields); CHKERRQ(ierr);
1135 ierr = ApplyBoundaryConditions(&user[bi]); CHKERRQ(ierr);
1136 pseudo_dtau[bi] = next_dtau;
1137 }
1139 " Trial %d REJECTED (raw_ratio=%.4e, smoothed=%.4e, nonfinite=%d); "
1140 "dtau %.4e -> %.4e (cfl_eff %.4f -> %.4f)%s\n",
1141 pseudo_iter, global_trial_ratio, trial_smoothed, (int)global_nonfinite,
1142 old_dtau, next_dtau, old_dtau * lambda_max, next_dtau * lambda_max,
1143 (old_dtau == next_dtau) ? " [AT FLOOR — no dtau reduction]" : "");
1144 /* Log rejected trial to file before rolling back. */
1145 if (!rank) {
1146 for (PetscInt bi = 0; bi < block_number; bi++) {
1147 FILE *f;
1148 char filen[PETSC_MAX_PATH_LEN + 128];
1149 ierr = PetscSNPrintf(filen, sizeof(filen),
1150 "%s/Momentum_Solver_DualTime_Picard_Jameson_RK_History_Block_%1d.log",
1151 simCtx->log_dir, bi); CHKERRQ(ierr);
1152 if (simCtx->step == simCtx->StartStep + 1 && pseudo_iter == 1 && !simCtx->continueMode)
1153 f = fopen(filen, "w");
1154 else
1155 f = fopen(filen, "a");
1156 if (simCtx->continueMode && simCtx->step == simCtx->StartStep + 1 && pseudo_iter == 1)
1157 PetscFPrintf(PETSC_COMM_WORLD, f, "# Continuation from step %" PetscInt_FMT "\n", simCtx->StartStep);
1158 /* Log format: dtau [physical time] + cfl_eff [dimensionless Courant number = dtau*lambda_max] */
1159 PetscFPrintf(PETSC_COMM_WORLD, f,
1160 "Step: %d | PseudoIter(k): %d | dtau: %.6e | cfl_eff: %.4f | |dUk|: %le | "
1161 "|dUk|/|dU0|: %le | |Rk|: %le | |Rk|/|R0|: %le | "
1162 "trial_ratio: %le | smoothed_ratio: %le | status: rejected | "
1163 "dtau_after: %.6e | cfl_eff_after: %.4f\n",
1164 (int)ti, (int)pseudo_iter, old_dtau, old_dtau * lambda_max,
1165 delta_sol_norm_curr[bi], delta_sol_rel_curr[bi],
1166 resid_norm_curr[bi], resid_rel_curr[bi],
1167 trial_ratio_log[bi], trial_smoothed,
1168 next_dtau, next_dtau * lambda_max);
1169 fclose(f);
1170 }
1171 }
1172 if (old_dtau <= dtau_min) {
1173 if (global_nonfinite) {
1174 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_CONV_FAILED,
1175 "Momentum solver produced a non-finite trial at minimum pseudo-CFL.");
1176 }
1177 /* Finite rejection at the dtau floor: reducing further is impossible.
1178 Break instead of retrying bit-identically until max_pseudo_steps. */
1180 " Trial %d REJECTED (ratio=%.4e) at minimum dtau (%.4e, cfl_eff=%.4f) "
1181 "with no further reduction possible; breaking retry loop.\n",
1182 pseudo_iter, global_trial_ratio, old_dtau, old_dtau * lambda_max);
1183 break;
1184 }
1185 continue;
1186 }
1187
1188 accepted_iter++;
1189 smoothed_trial_ratio = trial_smoothed; /* commit only for a trial that survives */
1190 last_trial_nonfinite = PETSC_FALSE;
1191 last_accepted_resid = resid_norm_curr[0]; /* track for final summary */
1192 for (PetscInt bi = 0; bi < block_number; bi++) {
1193 ierr = VecCopy(user[bi].Ucont, user[bi].pUcont); CHKERRQ(ierr);
1194 ierr = VecCopy(user[bi].Rhs, pRhs[bi]); CHKERRQ(ierr);
1195 resid_norm_prev[bi] = resid_norm_curr[bi];
1196 delta_sol_norm_prev[bi] = delta_sol_norm_curr[bi];
1197 }
1198
1199 /* old_dtau: dtau used for this (accepted) trial.
1200 * next_dtau: dtau for the next trial, grown/reduced based on convergence rate.
1201 * Bounds (dtau_min, dtau_max) are derived from simCtx->min/max_pseudo_cfl / lambda_max. */
1202 PetscReal old_dtau = pseudo_dtau[0]; /* save before any update */
1203 PetscReal next_dtau = old_dtau;
1204 if (global_trial_ratio < 0.90) {
1205 /* Residual decreasing fast: grow dtau to accelerate convergence. */
1206 next_dtau *= simCtx->pseudo_cfl_growth_factor;
1207 recovery_streak = 0;
1208 } else if (global_trial_ratio <= 1.0) {
1209 /* Residual barely decreasing: grow only after 3 clean consecutive trials. */
1210 recovery_streak++;
1211 if (recovery_streak >= 3) {
1212 next_dtau *= simCtx->pseudo_cfl_growth_factor;
1213 recovery_streak = 0;
1214 }
1215 } else {
1216 /* Accepted but residual slightly grew (within EMA noise allowance): reduce dtau. */
1217 next_dtau *= simCtx->pseudo_cfl_reduction_factor;
1218 recovery_streak = 0;
1219 }
1220 /* Relax the cap slowly on healthy trials: a single rejection caused by a transient
1221 * must not hold the step down for the rest of the run, but recovery has to be slow
1222 * enough that the controller does not simply walk back into the wall. */
1223 cfl_cap = PetscMin(simCtx->max_pseudo_cfl, cfl_cap * MOM_CFL_CAP_RELAX);
1224 next_dtau = PetscMin(next_dtau, dtau_max);
1225 next_dtau = PetscMin(next_dtau, cfl_cap / lambda_max);
1226 next_dtau = PetscMax(next_dtau, dtau_min);
1227 for (PetscInt bi = 0; bi < block_number; bi++) pseudo_dtau[bi] = next_dtau;
1228
1230 " Trial %d ACCEPTED (raw_ratio=%.4e, smoothed=%.4e); |dU|=%.6e | "
1231 "dtau %.4e -> %.4e (cfl_eff %.4f -> %.4f)\n",
1232 pseudo_iter, global_trial_ratio, trial_smoothed, global_norm_delta,
1233 old_dtau, next_dtau, old_dtau * lambda_max, next_dtau * lambda_max);
1234
1235 /* --- Post-decision file logging (one row per accepted trial) --- */
1236 if (!rank) {
1237 for (PetscInt bi = 0; bi < block_number; bi++) {
1238 FILE *f;
1239 char filen[PETSC_MAX_PATH_LEN + 128];
1240 ierr = PetscSNPrintf(filen, sizeof(filen),
1241 "%s/Momentum_Solver_DualTime_Picard_Jameson_RK_History_Block_%1d.log",
1242 simCtx->log_dir, bi); CHKERRQ(ierr);
1243 if (simCtx->step == simCtx->StartStep + 1 && pseudo_iter == 1 && !simCtx->continueMode)
1244 f = fopen(filen, "w");
1245 else
1246 f = fopen(filen, "a");
1247 if (simCtx->continueMode && simCtx->step == simCtx->StartStep + 1 && pseudo_iter == 1)
1248 PetscFPrintf(PETSC_COMM_WORLD, f, "# Continuation from step %" PetscInt_FMT "\n", simCtx->StartStep);
1249 /* Log format: dtau [physical time] + cfl_eff [dimensionless Courant number = dtau*lambda_max] */
1250 PetscFPrintf(PETSC_COMM_WORLD, f,
1251 "Step: %d | PseudoIter(k): %d | dtau: %.6e | cfl_eff: %.4f | |dUk|: %le | "
1252 "|dUk|/|dU0|: %le | |Rk|: %le | |Rk|/|R0|: %le | "
1253 "trial_ratio: %le | smoothed_ratio: %le | status: accepted | "
1254 "dtau_after: %.6e | cfl_eff_after: %.4f\n",
1255 (int)ti, (int)pseudo_iter, old_dtau, old_dtau * lambda_max,
1256 delta_sol_norm_curr[bi], delta_sol_rel_curr[bi],
1257 resid_norm_curr[bi], resid_rel_curr[bi],
1258 trial_ratio_log[bi], trial_smoothed,
1259 next_dtau, next_dtau * lambda_max);
1260 fclose(f);
1261 }
1262 }
1263
1264 /* --- Convergence decision ---
1265 *
1266 * The residual is what states that the momentum equations are satisfied at the
1267 * current state; the update norm measures progress, not correctness. The two
1268 * residual tests therefore carry different weight and are structured differently.
1269 *
1270 * converged = residual_abs_pass || (residual_rel_pass && update_pass)
1271 *
1272 * ABSOLUTE branch, sufficient on its own. |R| <= mom_resid_atol * resid_ref says
1273 * the equations are satisfied to the configured level outright, so it needs no
1274 * corroboration -- this is what an absolute tolerance means, and how PETSc's SNES
1275 * and KSP already behave. Requiring the update guard alongside it made the floor
1276 * unable to fire: measured on the laminar channel at step 793, |R| dropped below
1277 * the floor at pseudo-iteration 8 and the step still ran to 20, because
1278 * |dU|/|dU0| had not yet reached its own tolerance.
1279 *
1280 * RELATIVE branch, weaker evidence, paired with the update guard. A 1000x
1281 * reduction of |R0| says the iteration made progress from wherever it started,
1282 * not that the result is small; |R0| itself collapses as a run approaches steady
1283 * state. The guard stays mandatory here.
1284 *
1285 * The update norm is never sufficient by itself in either branch. |dU| ~ dtau*|R|,
1286 * so it goes small whenever dtau goes small -- observed as |dU| falling only ~10%
1287 * over 100 pseudo-iterations of a completely frozen iteration. An ABSOLUTE bound
1288 * on it (mom_atol) is the disguised, step-size-dependent residual bound
1289 * |R| <= mom_atol/dtau, which tightens as the controller succeeds in growing dtau;
1290 * it duplicates the residual test while pulling against it and takes no part here.
1291 *
1292 * With no residual criterion configured at all -- both residual tolerances set
1293 * non-positive, an explicit opt-out since the defaults now enable them -- the
1294 * update norms are the only information available and the legacy test is retained
1295 * unchanged.
1296 */
1297 if (residual_convergence_enabled) {
1298 const PetscBool residual_abs_pass = (PetscBool)(
1299 simCtx->mom_resid_atol > 0.0 && resid_ref > 0.0 &&
1300 global_norm_resid <= simCtx->mom_resid_atol * resid_ref);
1301 const PetscBool residual_rel_pass = (PetscBool)(
1302 simCtx->mom_resid_rtol > 0.0 && global_rel_resid <= simCtx->mom_resid_rtol);
1303 const PetscBool update_pass = (PetscBool)(
1304 tol_rtol_delta <= 0.0 || global_rel_delta <= tol_rtol_delta);
1305 converged = (PetscBool)(residual_abs_pass || (residual_rel_pass && update_pass));
1306 } else {
1307 converged = (PetscBool)(global_norm_delta <= tol_abs_delta &&
1308 global_rel_delta <= tol_rtol_delta);
1309 }
1310
1311 if (block_number > 1) {
1312 // ierr = Block_Interface_U(user); CHKERRQ(ierr);
1313 }
1314 } // End while loop
1315
1316 if (last_trial_nonfinite) {
1317 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_CONV_FAILED,
1318 "Momentum solver exhausted its attempt limit while recovering from a non-finite trial.");
1319 }
1320
1321 /* Convert the final dtau back to a CFL number for the warm-start of the next physical timestep.
1322 * simCtx->pseudo_cfl stores a dimensionless Courant number (dtau * lambda_max); the next
1323 * timestep's lambda_max may differ as the flow evolves, so storing the CFL number (not dtau)
1324 * ensures the warm-start adapts correctly to the new spectral radius. */
1325 PetscReal next_dtau_start = pseudo_dtau[0];
1326 PetscReal next_cfl_warmstart = next_dtau_start * lambda_max; /* CFL = dtau * lambda_max */
1327
1328 if (!rank) {
1330 " Step %d finished: dtau=%.4e cfl_eff=%.4f lambda_max=%.4e [1/s]. "
1331 "Next step warm-starts at cfl=%.4f.\n",
1332 simCtx->step, next_dtau_start, next_cfl_warmstart, lambda_max, next_cfl_warmstart);
1333 }
1334
1335 /* Store the CFL number (not dtau) so it remains meaningful across physical timesteps. */
1336 simCtx->pseudo_cfl = next_cfl_warmstart;
1337 simCtx->mom_last_lambda_max = lambda_max;
1338
1339 /* Expose the last accepted finite state. On converged or floor-break exits pUcont==Ucont
1340 already (VecCopy would be an identity), but ghosts may have gone stale; always re-sync
1341 and re-apply BCs. Only restore the solution vector itself when nothing was ever accepted. */
1342 if (accepted_iter == 0) {
1343 for (PetscInt bi = 0; bi < block_number; bi++) {
1344 ierr = VecCopy(user[bi].pUcont, user[bi].Ucont); CHKERRQ(ierr);
1345 ierr = VecCopy(pRhs[bi], user[bi].Rhs); CHKERRQ(ierr);
1346 }
1347 }
1348 for (PetscInt bi = 0; bi < block_number; bi++) {
1349 const FieldId staggered_fields[] = {FIELD_ID_UCONT};
1350 ierr = SynchronizePeriodicStaggeredFields(&user[bi], 1, staggered_fields); CHKERRQ(ierr);
1351 ierr = ApplyBoundaryConditions(&user[bi]); CHKERRQ(ierr);
1352 }
1353
1354 simCtx->mom_last_converged = converged;
1355 if (!converged) {
1356 PetscPrintf(PETSC_COMM_WORLD,
1357 "[WARNING] Momentum solver step %d: reached %d total attempts (%d accepted, %d rejected) "
1358 "without convergence; continuing from last accepted finite state.\n",
1359 (int)ti, pseudo_iter, accepted_iter, rejected_iter);
1360 }
1361 if (accepted_iter == 0) {
1362 PetscPrintf(PETSC_COMM_WORLD,
1363 "[WARNING] Momentum solver step %d: no pseudo-time trials were accepted; "
1364 "retaining physical-step entry state.\n", (int)ti);
1365 }
1367 "Momentum solver finished: %d attempts (%d accepted, %d rejected) of %d max accepted / %d hard cap, "
1368 "converged=%s, last accepted |R|=%.6e, last accepted |dU|=%.6e, "
1369 "next_dtau=%.4e (cfl=%.4f).\n",
1370 pseudo_iter, accepted_iter, rejected_iter, max_pseudo_steps, max_total_attempts,
1371 converged ? "yes" : "no",
1372 (accepted_iter > 0) ? last_accepted_resid : resid_norm_init[0],
1373 (accepted_iter > 0) ? delta_sol_norm_prev[0] : 0.0,
1374 next_dtau_start, next_cfl_warmstart);
1375
1376 // --- Final Cleanup ---
1377 for (PetscInt bi = 0; bi < block_number; bi++) {
1378 ierr = VecDestroy(&user[bi].Rhs); CHKERRQ(ierr);
1379 ierr = VecDestroy(&user[bi].dUcont); CHKERRQ(ierr);
1380 ierr = VecDestroy(&user[bi].pUcont); CHKERRQ(ierr);
1381 ierr = VecDestroy(&pRhs[bi]); CHKERRQ(ierr);
1382 }
1383 ierr = PetscFree(pRhs); CHKERRQ(ierr);
1384
1385 ierr = PetscFree2(delta_sol_norm_init, delta_sol_norm_prev);CHKERRQ(ierr);
1386 ierr = PetscFree2(delta_sol_norm_curr, delta_sol_rel_curr);CHKERRQ(ierr);
1387 ierr = PetscFree2(resid_norm_init, resid_norm_prev);CHKERRQ(ierr);
1388 ierr = PetscFree2(resid_norm_curr, pseudo_dtau); CHKERRQ(ierr);
1389 ierr = PetscFree(resid_rel_curr); CHKERRQ(ierr);
1390 ierr = PetscFree(trial_ratio_log); CHKERRQ(ierr);
1391
1393 PetscFunctionReturn(0);
1394}
@ LOG_TRACE
Very fine-grained tracing information for in-depth debugging.
Definition logging.h:33
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:30
#define MOM_CFL_CAP_SAFETY
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).
#define MOM_CFL_CAP_RELAX
Diagnostic report produced by ComputeMomentumStabilityEstimate().
PetscBool continueMode
Definition variables.h:876
PetscReal mom_rtol
Definition variables.h:901
PetscReal mom_last_lambda_max
Definition variables.h:913
PetscReal pseudo_cfl_reduction_factor
Definition variables.h:907
PetscReal min_pseudo_cfl
Definition variables.h:908
PetscBool mom_last_converged
Definition variables.h:912
PetscReal mom_atol
Definition variables.h:901
PetscBool no_pseudo_cfl_backtrack
Definition variables.h:910
PetscReal max_pseudo_cfl
Definition variables.h:908
char log_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:882
PetscReal mom_dt_jameson_residual_norm_noise_allowance_factor
Definition variables.h:909
PetscReal pseudo_cfl_growth_factor
Definition variables.h:907
PetscReal mom_resid_rtol
Definition variables.h:901
PetscInt mom_max_pseudo_steps
Definition variables.h:900
PetscReal mom_ratio_ema_alpha
Definition variables.h:911
PetscReal mom_resid_atol
Definition variables.h:901
PetscReal pseudo_cfl
Definition variables.h:906
Here is the call graph for this function:
Here is the caller graph for this function: