PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
Functions
BodyForces.h File Reference

Momentum source terms added to the contravariant RHS. More...

#include "variables.h"
#include "logging.h"
#include "Metric.h"
Include dependency graph for BodyForces.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Functions

PetscErrorCode ComputeDrivenChannelFlowSource (UserCtx *user, Vec Rct)
 Applies a momentum source term to drive flow in a periodic channel or pipe.
 
PetscErrorCode LogDrivenFlowDiagnostics (UserCtx *user)
 Appends one row of driven-flow controller state to driven_flow.csv.
 

Detailed Description

Momentum source terms added to the contravariant RHS.

Contract for a body force

Every body force here is invoked from ComputeBodyForces() (src/rhs.c), which is the single extension point. A new force should:

  1. take (UserCtx *user, Vec Rct),
  2. detect for itself whether it is active and return early if not,
  3. accumulate into Rct with +=, never assign.

RULE: DO NOT ADVANCE PER-TIMESTEP STATE HERE

ComputeBodyForces() is called from ComputeRHS(), which runs once per residual evaluation - that is, once per Jameson RK stage under the Picard solver and once per Newton residual evaluation (including every finite difference probe) under Newton-Krylov. It is emphatically NOT called once per physical timestep.

Any force that carries state across calls - a filter, a ramp, a moving average, an integral controller term - must therefore gate its update on simCtx->step and reuse the resolved value for the rest of that step:

if (simCtx->myForceStep != simCtx->step) {
... advance the state ...
simCtx->myForceStep = simCtx->step;
}

Advancing it unconditionally makes the applied force depend on how many residual evaluations preceded it. That is history dependence, and it breaks two things at once: MomentumNewtonKrylov_FormResidual() requires F(X) to be a deterministic function of the trial vector alone, and the Picard shadow-Jacobian estimate assumes body forces are a constant forcing with zero velocity Jacobian.

This is not hypothetical. The driven-flow smoothing EMA in ComputeDrivenChannelFlowSource() had exactly this defect: the applied force walked 0.5, 0.75, 0.875 ... of the way toward its target across evaluations within a single timestep. tests/smoke/run_driven_periodic_regression.sh asserts the force is piecewise constant per step; extend it when adding a stateful force.

Definition in file BodyForces.h.

Function Documentation

◆ ComputeDrivenChannelFlowSource()

PetscErrorCode ComputeDrivenChannelFlowSource ( UserCtx *  user,
Vec  Rct 
)

Applies a momentum source term to drive flow in a periodic channel or pipe.

This function is the "engine" of the driven flow control system. It operates by:

  1. Introspecting the boundary condition handlers to see if a DRIVEN_ flow handler is active on any face. This determines if a driven flow is enabled and in which direction ('X', 'Y', or 'Z').
  2. If a driven flow is active, it reads the bulkVelocityCorrection value that was computed by the handler's PreStep method and stored in the SimCtx.
  3. It translates this velocity correction into a momentum source term.
  4. It adds this source term to the appropriate component of the contravariant RHS vector (Rct) for all fluid cells in the domain.

If no driven flow handler is found, this function does nothing.

Parameters
userThe UserCtx containing the simulation state for a single block.
RctThe PETSc Vec for the contravariant RHS, which will be modified in-place.
Returns
PetscErrorCode 0 on success.

Applies a momentum source term to drive flow in a periodic channel or pipe.

Local to this translation unit.

Definition at line 37 of file BodyForces.c.

38{
39 PetscErrorCode ierr;
40 SimCtx *simCtx = user->simCtx;
41 PetscFunctionBeginUser;
42
43 // --- Step 1: Discover if and where a driven flow is active ---
44 const char drivenDirection = DrivenFlowDirection(user); // ' ' when none is configured
45
46 // --- Step 2: Early exit if no driven flow is configured ---
47 if (drivenDirection == ' ') {
48 PetscFunctionReturn(0);
49 }
50
51 // --- Step 3: Get the control signal and exit if no correction is needed ---
52 PetscReal bulkVelocityCorrection = simCtx->bulkVelocityCorrection;
53 if (PetscAbsReal(bulkVelocityCorrection) < 1.0e-12) {
54 PetscFunctionReturn(0);
55 }
56
57 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d, Block %d: Applying driven flow momentum source in '%c' direction.\n",
58 simCtx->rank, user->_this, drivenDirection);
59 LOG_ALLOW(LOCAL, LOG_DEBUG, " - Received Bulk Velocity Correction: %le\n", bulkVelocityCorrection);
60
61 // --- Step 4: Setup for calculation ---
62 DMDALocalInfo info = user->info;
63 PetscInt i, j, k;
64 PetscInt lxs = (info.xs == 0) ? 1 : info.xs;
65 PetscInt lys = (info.ys == 0) ? 1 : info.ys;
66 PetscInt lzs = (info.zs == 0) ? 1 : info.zs;
67 PetscInt lxe = (info.xs + info.xm == info.mx) ? info.mx - 1 : info.xs + info.xm;
68 PetscInt lye = (info.ys + info.ym == info.my) ? info.my - 1 : info.ys + info.ym;
69 PetscInt lze = (info.zs + info.zm == info.mz) ? info.mz - 1 : info.zs + info.zm;
70
71 Cmpnts ***rct, ***csi, ***eta, ***zet;
72 PetscReal ***nvert;
73 ierr = DMDAVecGetArray(user->fda, Rct, &rct); CHKERRQ(ierr);
74 ierr = DMDAVecGetArrayRead(user->fda, user->lCsi, (const Cmpnts***)&csi); CHKERRQ(ierr);
75 ierr = DMDAVecGetArrayRead(user->fda, user->lEta, (const Cmpnts***)&eta); CHKERRQ(ierr);
76 ierr = DMDAVecGetArrayRead(user->fda, user->lZet, (const Cmpnts***)&zet); CHKERRQ(ierr);
77 ierr = DMDAVecGetArrayRead(user->da, user->lNvert, (const PetscReal***)&nvert); CHKERRQ(ierr);
78
79 // Calculate the driving force magnitude for the current timestep, smoothed
80 // with the value from the previous step for stability.
81 //
82 // ONCE PER PHYSICAL STEP, NOT ONCE PER CALL. This function runs from
83 // ComputeRHS, which executes once per Jameson RK stage under the Picard
84 // solver and once per residual evaluation under Newton-Krylov. The smoothing
85 // below carries state in simCtx across calls, so advancing it every call
86 // would walk the applied force toward its target within a single timestep
87 // (0.5, then 0.75, then 0.875 ... of the way there). The force would then
88 // depend on how many residual evaluations preceded it - history dependence
89 // that MomentumNewtonKrylov_FormResidual() explicitly forbids, and that
90 // breaks the constant-forcing assumption behind the Picard shadow-Jacobian
91 // estimate. Resolve it once for the step and reuse it thereafter.
92 const PetscReal forceScalingFactor = simCtx->forceScalingFactor;
93 PetscReal drivingForceMagnitude;
94
95 if (simCtx->drivingForceStep != simCtx->step) {
96 const PetscReal targetForce = (bulkVelocityCorrection / simCtx->dt / 1.0 * COEF_TIME_ACCURACY); // replaced simCtx->st with 1.0.
97 drivingForceMagnitude = (simCtx->drivingForceMagnitude * 0.5) + (targetForce * 0.5);
98 simCtx->drivingForceMagnitude = drivingForceMagnitude;
99 simCtx->drivingForceStep = simCtx->step;
100 } else {
101 drivingForceMagnitude = simCtx->drivingForceMagnitude;
102 }
103
104 LOG_ALLOW(GLOBAL, LOG_DEBUG, " - Previous driving force: %le\n", simCtx->drivingForceMagnitude);
105 LOG_ALLOW(GLOBAL, LOG_DEBUG, " - New smoothed driving force: %le\n", drivingForceMagnitude);
106 LOG_ALLOW(GLOBAL, LOG_DEBUG, " - Force scaling factor: %f\n", simCtx->forceScalingFactor);
107
108 PetscBool hasLoggedApplication = PETSC_FALSE; // Flag to log details only once per rank.
109 // --- Step 5: Apply the momentum source to the correct RHS component ---
110 for (k = lzs; k < lze; k++) {
111 for (j = lys; j < lye; j++) {
112 for (i = lxs; i < lxe; i++) {
113 if (nvert[k][j][i] < 0.1) { // Apply only to fluid cells
114 PetscReal faceArea = 0.0;
115 PetscReal momentumSource = 0.0;
116
117 switch (drivenDirection) {
118 case 'X':
119 faceArea = sqrt(csi[k][j][i].x * csi[k][j][i].x + csi[k][j][i].y * csi[k][j][i].y + csi[k][j][i].z * csi[k][j][i].z);
120 momentumSource = drivingForceMagnitude * forceScalingFactor * faceArea;
121 rct[k][j][i].x += momentumSource;
122
123 // Log details for the very first point where force is applied on this rank.
124 if (!hasLoggedApplication) {
125 LOG_ALLOW(LOCAL, LOG_DEBUG,"Body Force %le added at (%d,%d,%d)\n",momentumSource, k, j, i);
126 hasLoggedApplication = PETSC_TRUE;
127 }
128 break;
129 case 'Y':
130 faceArea = sqrt(eta[k][j][i].x * eta[k][j][i].x + eta[k][j][i].y * eta[k][j][i].y + eta[k][j][i].z * eta[k][j][i].z);
131 momentumSource = drivingForceMagnitude * forceScalingFactor * faceArea;
132 rct[k][j][i].y += momentumSource;
133
134 // Log details for the very first point where force is applied on this rank.
135 if (!hasLoggedApplication) {
136 LOG_ALLOW(LOCAL, LOG_DEBUG,"Body Force %le added at (%d,%d,%d)\n",momentumSource, k, j, i);
137 hasLoggedApplication = PETSC_TRUE;
138 }
139 break;
140 case 'Z':
141 faceArea = sqrt(zet[k][j][i].x * zet[k][j][i].x + zet[k][j][i].y * zet[k][j][i].y + zet[k][j][i].z * zet[k][j][i].z);
142 momentumSource = drivingForceMagnitude * forceScalingFactor * faceArea;
143 rct[k][j][i].z += momentumSource;
144
145 // Log details for the very first point where force is applied on this rank.
146 if (!hasLoggedApplication) {
147 LOG_ALLOW(LOCAL, LOG_DEBUG,"Body Force %le added at (%d,%d,%d)\n",momentumSource, k, j, i);
148 hasLoggedApplication = PETSC_TRUE;
149 }
150 break;
151 }
152 }
153 }
154 }
155 }
156
157 // --- Step 6: Restore arrays ---
158 ierr = DMDAVecRestoreArray(user->fda, Rct, &rct); CHKERRQ(ierr);
159 ierr = DMDAVecRestoreArrayRead(user->fda, user->lCsi, (const Cmpnts***)&csi); CHKERRQ(ierr);
160 ierr = DMDAVecRestoreArrayRead(user->fda, user->lEta, (const Cmpnts***)&eta); CHKERRQ(ierr);
161 ierr = DMDAVecRestoreArrayRead(user->fda, user->lZet, (const Cmpnts***)&zet); CHKERRQ(ierr);
162 ierr = DMDAVecRestoreArrayRead(user->da, user->lNvert, (const PetscReal***)&nvert); CHKERRQ(ierr);
163
164 PetscFunctionReturn(0);
165}
static char DrivenFlowDirection(const UserCtx *user)
The axis a driven periodic handler drives, or ' ' when none is configured.
Definition BodyForces.c:14
#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_DEBUG
Detailed debugging information.
Definition logging.h:32
PetscMPIInt rank
Definition variables.h:862
Vec lNvert
Definition variables.h:1113
PetscReal forceScalingFactor
Definition variables.h:961
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1077
PetscInt _this
Definition variables.h:1092
PetscReal dt
Definition variables.h:874
PetscReal bulkVelocityCorrection
Definition variables.h:973
PetscScalar x
Definition variables.h:122
PetscScalar z
Definition variables.h:122
PetscInt drivingForceStep
Definition variables.h:966
PetscInt step
Definition variables.h:867
DMDALocalInfo info
Definition variables.h:1086
PetscScalar y
Definition variables.h:122
#define COEF_TIME_ACCURACY
Coefficient controlling the temporal accuracy scheme (e.g., 1.5 for 2nd Order Backward Difference).
Definition variables.h:75
PetscReal drivingForceMagnitude
Definition variables.h:961
A 3D point or vector with PetscScalar components.
Definition variables.h:121
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:

◆ LogDrivenFlowDiagnostics()

PetscErrorCode LogDrivenFlowDiagnostics ( UserCtx *  user)

Appends one row of driven-flow controller state to driven_flow.csv.

Active whenever a constant_flux or initial_flux handler drives the flow, in any geometry. Call once per physical step, after the momentum solve, so the row holds the force that step actually applied. Rank 0 writes; block 0 only, since the controller state lives in SimCtx.

Columns: target_flux and measured_flux (the planar-average volumetric flux the correction was computed from this step), cross_section_area, bulk_velocity (their ratio), bulk_velocity_correction, and driving_acceleration, the momentum source per unit mass along the driven axis in solver units: the smoothed magnitude times -driven_flow_scaling_factor, or zero on a step where the correction was negligible and no source was added. In a statistically stationary driven channel its time average is the mean wall stress over the half-height, u_tau^2 / h; in a duct, the mean wall stress times the wetted perimeter over the cross-section area.

Parameters
[in]userBlock context; only block 0 reports.
Returns
PetscErrorCode 0 on success.

Appends one row of driven-flow controller state to driven_flow.csv.

Full API contract is documented with the header declaration in include/BodyForces.h.

Definition at line 174 of file BodyForces.c.

175{
176 SimCtx *simCtx = user->simCtx;
177 const char direction = DrivenFlowDirection(user);
178 FILE *file = NULL;
179
180 PetscFunctionBeginUser;
181
182 /* The controller state is global: every rank holds the same values, set from the
183 controller's collective reductions, so rank 0 reports it once per step. */
184 if (direction == ' ' || user->_this != 0 || simCtx->rank != 0) PetscFunctionReturn(0);
185
186 /* What the momentum equation actually received this step. The source is resolved
187 once per step and skipped entirely when the correction is negligible, in which
188 case the smoothed magnitude is stale and nothing was applied. */
189 const PetscBool applied = (PetscBool)(simCtx->drivingForceStep == simCtx->step &&
190 PetscAbsReal(simCtx->bulkVelocityCorrection) >= 1.0e-12);
191 const PetscReal acceleration = applied ? simCtx->drivingForceMagnitude * simCtx->forceScalingFactor : 0.0;
192 const PetscReal area = simCtx->drivenFluxArea;
193 const PetscReal bulk_velocity = (area > 0.0) ? simCtx->drivenFluxMeasured / area : 0.0;
194 PetscReal physical_time = 0.0;
195
196 PetscCall(PicurvPhysicalTime(simCtx, simCtx->ti, &physical_time));
197 PetscCall(PicurvOpenDiagnosticsCsv(simCtx, "driven_flow.csv",
198 "step,time,direction,target_flux,measured_flux,cross_section_area,"
199 "bulk_velocity,bulk_velocity_correction,driving_acceleration,physical_time",
200 &file));
201 fprintf(file, "%d,%.6e,%c,%.10e,%.10e,%.10e,%.10e,%.6e,%.10e,%.6e\n",
202 (int)simCtx->step, (double)simCtx->ti, direction,
203 (double)simCtx->targetVolumetricFlux, (double)simCtx->drivenFluxMeasured, (double)area,
204 (double)bulk_velocity, (double)simCtx->bulkVelocityCorrection, (double)acceleration,
205 (double)physical_time);
206 PetscCheck(fclose(file) == 0, PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
207 "Unable to close the driven-flow diagnostics file.");
208 PetscFunctionReturn(0);
209}
PetscErrorCode PicurvPhysicalTime(const SimCtx *simCtx, PetscReal solver_time, PetscReal *physical)
Convert a solver time to physical seconds, t * L_ref / U_ref.
Definition io.c:3177
PetscErrorCode PicurvOpenDiagnosticsCsv(const SimCtx *simCtx, const char *filename, const char *header, FILE **file)
Opens a per-run diagnostics CSV in the run's analysis directory for appending.
Definition logging.c:3503
PetscReal targetVolumetricFlux
Definition variables.h:967
PetscReal drivenFluxMeasured
Definition variables.h:978
PetscReal drivenFluxArea
Definition variables.h:978
PetscReal ti
Definition variables.h:868
Here is the call graph for this function:
Here is the caller graph for this function: