Green-Gauss gradient contracts

Upstream of the slope limiter on the residual path. Face values, normals, volume scaling, and boundary ghost copies are pure; only the face/cell iteration still touches mesh globals (ug).

Pipeline

zero cell gradients
     |
     v
for each face:
     q_f  <- ArithmeticFaceAverage  OR  InverseDistance + WeightedFaceAverage
     S    <- AreaWeightedFaceNormal (n * A)
     left  AccumulateFaceGradient(+S * q_f)
     if interior:
       right AccumulateFaceGradient(-S * q_f)
     |
     v
for each physical cell:
     ScaleGradientByVolume (grad /= V)
     |
     v
for each boundary face:
     CopyGradient(owner -> ghost)

Formulas

Arithmetic face value:

\[q_f = \frac{1}{2}(q_L + q_R)\]

Inverse-distance weights:

\[d_L = |\mathbf{x}_f - \mathbf{x}_{cL}|,\quad d_R = |\mathbf{x}_f - \mathbf{x}_{cR}|\]
\[c_L = \frac{d_R}{d_L + d_R + \varepsilon},\quad c_R = \frac{d_L}{d_L + d_R + \varepsilon},\quad q_f = c_L q_L + c_R q_R\]

Green-Gauss accumulation and volume scale:

\[\nabla q_c \leftarrow \nabla q_c + \sigma\,\mathbf{S}\,q_f, \quad \sigma = +1\ \mathrm{(left)},\ -1\ \mathrm{(right)}\]
\[\nabla q_c \leftarrow \frac{1}{V_c}\nabla q_c\]

Boundary ghosts copy the owner (left) gradient; they are not independent unknowns in this scheme.

Pure entry points (UGradPolicy.h)

  • ArithmeticFaceAverage

  • InverseDistanceFaceWeights / WeightedFaceAverage

  • AreaWeightedFaceNormal

  • AccumulateFaceGradient

  • ScaleGradientByVolume

  • CopyGradient

Tags: @doc-algorithm green-gauss-gradient with @doc-stage / @doc-formula on each pure API so an extractor can rebuild this page from headers without a second algorithm write-up. Narrative, diagrams, and math remain in doc/source (not only in // comments).

Tests: grad_policy_test.