NeoN
A framework for CFD software
Loading...
Searching...
No Matches
ddtFluxCorr.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2023 - 2026 NeoN authors
2//
3// SPDX-License-Identifier: MIT
4
5#pragma once
6
13
15{
16
19
20namespace detail
21{
22
23KOKKOS_INLINE_FUNCTION
24scalar ddtFluxCorrLimiter(const scalar fluxMag, const scalar corrMag)
25{
26 constexpr scalar small = 1.0e-30;
27 const auto ratio = corrMag / (fluxMag + small);
28 return scalar(1) - Kokkos::min(ratio, scalar(1));
29}
30
31// ------------------------------------------------------------------
32// BDF1 kernel (also used for BDF2 startup)
33// ------------------------------------------------------------------
35 const Executor& exec,
36 const UnstructuredMesh& mesh,
37 const SurfScalarField& flux0,
38 const SurfaceField<Vec3>& uf0,
39 SurfScalarField& fluxCorr,
40 scalar dt
41)
42{
43 const scalar a1 = scalar(1) / dt;
44 const auto nInternalFaces = mesh.nInternalFaces();
45 const auto nBoundaryFaces = mesh.nBoundaryFaces();
46 const auto nProcBoundaryFaces = mesh.nProcBoundaryFaces();
47
48 // Internal faces. mesh.faceNormals() is OF-full but its leading nInternalFaces
49 // entries coincide with the compressed internal-face Sf, so indexing by i is fine.
50 auto [outV, flux0V, uf0V, SfV] = views(
51 fluxCorr.internalVector(), flux0.internalVector(), uf0.internalVector(), mesh.faceNormals()
52 );
54 exec,
55 {size_t(0), static_cast<size_t>(nInternalFaces)},
56 NEON_LAMBDA(const localIdx i) {
57 const auto d = (SfV[i] & uf0V[i]);
58 const auto corr = flux0V[i] - d;
59 const scalar limiter = ddtFluxCorrLimiter(mag(flux0V[i]), mag(corr));
60 outV[i] = limiter * a1 * corr;
61 },
62 "ddtFluxCorr::BDF1::internal"
63 );
64
65 // Boundary + processor faces.
66 // boundaryData().value() stores [physical boundary | processor] faces; the
67 // boundary-mesh Sf (mesh.boundaryMesh().faceNormals()) is the matching COMPRESSED
68 // face-area-normal field spanning the same range, so it is indexed directly by bfi.
69 // The previous code used the OF-full mesh.faceNormals()[nInternalFaces + bfi] and
70 // only looped over nBoundaryFaces: that reads the wrong face when empty/wedge
71 // patches are present, and never touched the processor tail at all -- leaving the
72 // ddt flux correction unset on processor faces and corrupting phiHbyA (hence the
73 // inflated Courant number) on distributed runs. The processor-patch BC is
74 // 'calculated' (no-op correctBoundaryCondition), so this locally computed proc-tail
75 // value survives the fluxCorr.correctBoundaryConditions() call below. See
76 // project_neon_compressed_face_indexing and the matching proc-face loop in
77 // pressureVelocityCoupling::flux.
78 auto [outBV, flux0BV, uf0BV] = views(
79 fluxCorr.boundaryData().value(), flux0.boundaryData().value(), uf0.boundaryData().value()
80 );
81 const auto bFaceNormals = mesh.boundaryMesh().faceNormals().view();
83 exec,
84 {size_t(0), static_cast<size_t>(nBoundaryFaces + nProcBoundaryFaces)},
85 NEON_LAMBDA(const localIdx bfi) {
86 const auto d = (bFaceNormals[bfi] & uf0BV[bfi]);
87 const auto corr = flux0BV[bfi] - d;
88 const scalar limiter = ddtFluxCorrLimiter(mag(flux0BV[bfi]), mag(corr));
89 outBV[bfi] = limiter * a1 * corr;
90 },
91 "ddtFluxCorr::BDF1::boundary"
92 );
93}
94
95// ------------------------------------------------------------------
96// BDF2 kernel
97// ------------------------------------------------------------------
99 const Executor& exec,
100 const UnstructuredMesh& mesh,
101 const SurfScalarField& flux0,
102 const SurfScalarField& flux00,
103 const SurfaceField<Vec3>& uf0,
104 const SurfaceField<Vec3>& uf00,
105 SurfScalarField& fluxCorr,
106 scalar dt
107)
108{
109 const scalar a1 = 2.0 / dt;
110 const scalar a2 = -0.5 / dt;
111 const auto nInternalFaces = mesh.nInternalFaces();
112 const auto nBoundaryFaces = mesh.nBoundaryFaces();
113 const auto nProcBoundaryFaces = mesh.nProcBoundaryFaces();
114
115 // Internal faces
116 {
117 auto [outV, flux0V, flux00V, uf0V, uf00V, SfV] = views(
118 fluxCorr.internalVector(),
119 flux0.internalVector(),
120 flux00.internalVector(),
121 uf0.internalVector(),
122 uf00.internalVector(),
123 mesh.faceNormals()
124 );
126 exec,
127 {size_t(0), static_cast<size_t>(nInternalFaces)},
128 NEON_LAMBDA(const localIdx i) {
129 const auto d1 = (SfV[i] & uf0V[i]);
130 const auto corr1 = flux0V[i] - d1;
131
132 const auto d2 = (SfV[i] & uf00V[i]);
133 const auto corr2 = flux00V[i] - d2;
134
135 const scalar limiter1 = ddtFluxCorrLimiter(mag(flux0V[i]), mag(corr1));
136 const scalar limiter2 = ddtFluxCorrLimiter(mag(flux00V[i]), mag(corr2));
137
138 outV[i] = limiter1 * a1 * corr1 + limiter2 * a2 * corr2;
139 },
140 "ddtFluxCorr::BDF2::internal"
141 );
142 }
143
144 // Boundary + processor faces. Compressed boundary-mesh Sf indexed by bfi over the
145 // [physical | processor] range; mirrors the BDF1 kernel (see its comment for why the
146 // OF-full mesh.faceNormals() must not be used here and why the processor tail must be
147 // included for correct distributed phiHbyA / Courant number).
148 {
149 auto outBV = fluxCorr.boundaryData().value().view();
150 auto flux0BV = flux0.boundaryData().value().view();
151 auto flux00BV = flux00.boundaryData().value().view();
152 auto uf0BV = uf0.boundaryData().value().view();
153 auto uf00BV = uf00.boundaryData().value().view();
154 const auto bFaceNormals = mesh.boundaryMesh().faceNormals().view();
156 exec,
157 {size_t(0), static_cast<size_t>(nBoundaryFaces + nProcBoundaryFaces)},
158 NEON_LAMBDA(const localIdx bfi) {
159 const auto d1 = (bFaceNormals[bfi] & uf0BV[bfi]);
160 const auto corr1 = flux0BV[bfi] - d1;
161
162 const auto d2 = (bFaceNormals[bfi] & uf00BV[bfi]);
163 const auto corr2 = flux00BV[bfi] - d2;
164
165 const scalar limiter1 = ddtFluxCorrLimiter(mag(flux0BV[bfi]), mag(corr1));
166 const scalar limiter2 = ddtFluxCorrLimiter(mag(flux00BV[bfi]), mag(corr2));
167
168 outBV[bfi] = limiter1 * a1 * corr1 + limiter2 * a2 * corr2;
169 },
170 "ddtFluxCorr::BDF2::boundary"
171 );
172 }
173}
174
175} // namespace detail
176
177inline SurfScalarField
179{
180 const auto& mesh = u.mesh();
181 const auto& exec = phi.exec();
182
183 // --- interpolation
184 SurfaceInterpolation<Vec3> interp(exec, mesh, TokenList({std::string("linear")}));
185
186 // --- boundary conditions consistent with U
187 auto surfaceBCs = createFluxCorrBCsFromU(mesh, u);
188
189 SurfScalarField fluxCorr(exec, "ddtFluxCorr", mesh, surfaceBCs);
190
191 const int level = oldTimeLevel(u);
192
193 // --- BDF1 / startup
194 const auto& u0 = oldTime(u);
195 const auto& phi0 = oldTime(phi);
196 auto uf0 = interp.interpolate(u0);
197
198 if (scheme == DdtScheme::BDF2 && level >= 2) // --- BDF2 contribution
199 {
200 const auto& u00 = oldTime(u0);
201 const auto& phi00 = oldTime(phi0);
202 auto uf00 = interp.interpolate(u00);
203
204 detail::ddtFluxCorrBDF2Kernel(exec, mesh, phi0, phi00, uf0, uf00, fluxCorr, dt);
205 }
206 else
207 {
208 detail::ddtFluxCorrBDF1Kernel(exec, mesh, phi0, uf0, fluxCorr, dt);
209 }
210
211 fluxCorr.correctBoundaryConditions();
212 return fluxCorr;
213}
214
215} // namespace NeoN::finiteVolume::cellCentred
const vectorVector & faceNormals() const
Get the field of face areas normals.
A class representing a list of tokens.
Definition tokenList.hpp:31
Represents an unstructured mesh in NeoN.
localIdx nProcBoundaryFaces() const
Get the number of processor-boundary faces (inter-rank faces).
localIdx nBoundaryFaces() const
Get the number of boundary faces in the mesh.
localIdx nInternalFaces() const
Get the number of internal faces in the mesh.
const vectorVector & faceNormals() const
Get the field of face normal vectors.
const BoundaryMesh & boundaryMesh() const
Get the boundary mesh.
View< ValueType > view() &&=delete
const UnstructuredMesh & mesh() const
Returns a const reference to the unstructured mesh object.
Definition domain.hpp:122
const BoundaryData< ValueType > & boundaryData() const
Returns a const reference to the boundary field.
Definition domain.hpp:101
const Executor & exec() const
Returns a const reference to the executor object.
Definition domain.hpp:115
const Vector< ValueType > & internalVector() const
Returns a const reference to the internal field.
Definition domain.hpp:80
void correctBoundaryConditions()
Corrects the boundary conditions of the surface field.
void interpolate(const VolumeField< ValueType > &src, SurfaceField< ValueType > &dst) const
Represents a volume field in a finite volume method.
void ddtFluxCorrBDF1Kernel(const Executor &exec, const UnstructuredMesh &mesh, const SurfScalarField &flux0, const SurfaceField< Vec3 > &uf0, SurfScalarField &fluxCorr, scalar dt)
KOKKOS_INLINE_FUNCTION scalar ddtFluxCorrLimiter(const scalar fluxMag, const scalar corrMag)
void ddtFluxCorrBDF2Kernel(const Executor &exec, const UnstructuredMesh &mesh, const SurfScalarField &flux0, const SurfScalarField &flux00, const SurfaceField< Vec3 > &uf0, const SurfaceField< Vec3 > &uf00, SurfScalarField &fluxCorr, scalar dt)
int oldTimeLevel(const VectorType &field)
Helper function to retrieve the history depth of a field in oldTimeCollection.
std::vector< SurfaceBoundary< scalar > > createFluxCorrBCsFromU(const UnstructuredMesh &mesh, const VolumeField< Vec3 > &u)
Boundary condition to apply to ddtFluxCorr surfaceScalarField. It sets the flux correction to zero fo...
SurfaceField< scalar > SurfScalarField
SurfScalarField ddtFluxCorr(const VolVectorField &u, const SurfScalarField &phi, scalar dt, DdtScheme scheme)
VectorType & oldTime(VectorType &field)
Retrieves the old time field of a given field.
int32_t localIdx
Definition label.hpp:50
KOKKOS_INLINE_FUNCTION scalar mag(const scalar &s)
Definition scalar.hpp:23
std::size_t size_t
Definition label.hpp:56
std::variant< SerialExecutor, CPUExecutor, GPUExecutor > Executor
Definition executor.hpp:20
float scalar
Definition scalar.hpp:17
void parallelFor(const ExecutorType &, std::pair< localIdx, localIdx > range, const Kernel &kernel, std::string name)
auto views(Types &... args)
Unpacks all views of the passed classes.
Definition view.hpp:107
#define NEON_LAMBDA