NeoN
A framework for CFD software
Loading...
Searching...
No Matches
solver.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
7#include <memory>
8#include <utility>
9#include <concepts>
10#include <optional>
11
12#include "NeoN/fields/field.hpp"
15#include "NeoN/core/logging.hpp"
16#include "NeoN/core/input.hpp"
21
24
25
26namespace NeoN::dsl
27{
28
29namespace detail
30{
31template<typename VectorType, typename IndexType>
35 VectorType& solution,
36 scalar t,
37 scalar dt,
38 const Dictionary& fvSchemes,
39 const Dictionary& fvSolution,
41)
42{
43 auto optExp = optimize(exp);
44 optExp.read(fvSchemes);
45 optExp.assemble(t, dt, ls, ps);
46
47 // TODO move that to expression explicit operation or
48 // into functor ?
49 // subtract the explicit source term from the rhs
50 auto expTmp = optExp.explicitOperation(solution.mesh().nCells());
51 auto [vol, expSource, rhs] = views(solution.mesh().cellVolumes(), expTmp, ls.rhs());
53 solution.exec(),
54 {0, rhs.size()},
55 NEON_LAMBDA(const localIdx i) { rhs[i] -= expSource[i] * vol[i]; }
56 );
57
58 auto solver = la::Solver(solution.exec(), fvSolution);
59 fence(solution.exec());
60
61 // Do some sanity checks before trying to solve
62 NF_ASSERT(ls.exec() == solution.exec(), "Executors are not the same");
63 return solver.solve(ls, solution.internalVector());
64}
65
66template<typename VectorType, typename IndexType>
69 VectorType& solution,
70 scalar t,
71 scalar dt,
72 const Dictionary& fvSolution,
74)
75{
76 auto ls = exp.assemble(solution.mesh(), t, dt, ps);
77
78 auto solver = la::Solver(solution.exec(), fvSolution);
79 fence(solution.exec());
80 return solver.solve(ls, solution.internalVector());
81}
82}
83
84/* @brief solve an expression
85 *
86 * @param exp - Expression which is to be solved/updated.
87 * @param solution - Solution field, where the solution will be 'written to'.
88 * @param t - the time at the start of the time step.
89 * @param dt - time step for the temporal integration
90 * @param fvSchemes - Dictionary containing spatial operator and time integration properties
91 * @param fvSolution - Dictionary containing linear solver properties
92 * @param p - A chainable functor that performs manipulations on the assembled system
93 */
94template<typename VectorType, typename IndexType>
95std::optional<la::SolverStats> solve(
97 VectorType& solution,
98 scalar t,
99 scalar dt,
100 const Dictionary& fvSchemes,
101 const Dictionary& fvSolution,
103)
104{
105 if (exp.temporalOperators().size() == 0 && exp.spatialOperators().size() == 0)
106 {
107 NF_ERROR_EXIT("No temporal or implicit terms to solve.");
108 }
109 exp.read(fvSchemes);
110 auto integrator = timeIntegration::TimeIntegration<VectorType>(
111 fvSchemes.subDict("timeIntegration"), fvSolution
112 );
113
114 if (exp.temporalOperators().size() > 0 && integrator.explicitIntegration())
115 {
116 // integrate equations in time
117 integrator.solve(exp, solution, t, dt);
118 return std::nullopt; // no linear solve was performed, so no stats to return
119 }
120 else
121 {
122 return detail::iterativeSolveImpl(exp, solution, t, dt, fvSolution, p);
123 }
124}
125
126// ---------------------------------------------------------------------------
127// Matrix under-relaxation (post-assembly fused kernel)
128// ---------------------------------------------------------------------------
129
131KOKKOS_INLINE_FUNCTION scalar copySign(const scalar mag, const scalar s)
132{
133 return (s >= 0) ? mag : -mag;
134}
135
137KOKKOS_INLINE_FUNCTION scalar componentCopySign(const scalar mag, const scalar s)
138{
139 return copySign(mag, s);
140}
141
143KOKKOS_INLINE_FUNCTION Vec3 componentCopySign(const Vec3& mag, const Vec3& s)
144{
145 return Vec3(copySign(mag[0], s[0]), copySign(mag[1], s[1]), copySign(mag[2], s[2]));
146}
147
149KOKKOS_INLINE_FUNCTION scalar componentMag(const scalar value) { return Kokkos::abs(value); }
150
153KOKKOS_INLINE_FUNCTION Vec3 componentMag(const Vec3& value)
154{
155 return Vec3(Kokkos::abs(value[0]), Kokkos::abs(value[1]), Kokkos::abs(value[2]));
156}
157
159KOKKOS_INLINE_FUNCTION scalar componentMax(const scalar lhs, const scalar rhs)
160{
161 return Kokkos::max(lhs, rhs);
162}
163
165KOKKOS_INLINE_FUNCTION Vec3 componentMax(const Vec3& lhs, const Vec3& rhs)
166{
167 return Vec3(
168 Kokkos::max(lhs[0], rhs[0]), Kokkos::max(lhs[1], rhs[1]), Kokkos::max(lhs[2], rhs[2])
169 );
170}
171
172/* @brief Apply equation (matrix) under-relaxation to an assembled LinearSystem.
173 *
174 * NeoN bakes boundary contributions permanently into the CSR diagonal at assembly time,
175 * so the augmented diagonal `D_aug = matrix.values[diagIdx(cell)]` already contains the
176 * boundary diagonal. This kernel relaxes the augmented diagonal DIRECTLY. Per cell:
177 *
178 * D_aug : matrix.values[diagIdx(cell)] (augmented, boundary-baked)
179 * D_dom : max(mag(D_aug), sumMagOffDiag[cell]) (dominance clamp on the augmented diag)
180 * D_relaxed : componentCopySign(D_dom / alpha, D_aug) (scale whole augmented diag by 1/alpha)
181 * write : matrix.values[diagIdx(cell)] = D_relaxed
182 * source : rhs[cell] += (D_relaxed - D_aug) * psi_prev[cell]
183 *
184 * The source correction makes the relaxed system share the fixed point of the
185 * unrelaxed system (the correction cancels at the converged solution). `psi_prev`
186 * is the field value at solve entry (`solution.internalVector()` — no separate snapshot).
187 * The negative-diagonal sign convention is preserved componentwise via `componentCopySign`
188 * so `rAU`/`HbyA` stay correct for alpha < 1.
189 *
190 * @param ls The assembled LinearSystem (mutated in place on the augmented diagonal).
191 * @param solution The solution VolumeField — supplies psi_prev (internalVector).
192 * @param alpha The under-relaxation factor. alpha <= 0 or alpha == 1 is a bitwise no-op.
193 */
194// Overload set is templated on the FULL LinearSystem parameter pack (mirroring
195// removeBoundaryContributions, linearSystem.hpp) rather than LinearSystem<ElementType>.
196// This matters because the real momentum system assembled by
197// the DSL is the SEGREGATED vector-solve form — a SCALAR matrix (MatrixValueType == scalar) with
198// a Vec3 RHS (RHSValueType == Vec3) — not LinearSystem<Vec3>. The diagonal/off-diagonal/boundary
199// algebra therefore runs in MatrixValueType (scalar here), while the source correction and field
200// read run in RHSValueType (Vec3 here); the cross term (dRelaxed - dAug) * field is
201// MatrixValueType * RHSValueType (scalar * Vec3 = Vec3), which the existing operator* already
202// supports. The synthetic NeoN unit tests use createEmptyLinearSystem<Vec3>(mesh) where
203// MatrixValueType == RHSValueType == Vec3, so both forms are now exercised.
204template<
205 typename VectorType,
206 typename MatrixValueType,
207 typename RHSValueType,
208 typename SystemMatrixType,
209 typename BoundaryMatrixType>
212 const VectorType& solution,
213 scalar alpha
214)
215{
216 // alpha<=0 guard: bitwise no-op. MUST be first so neither
217 // matrix.values nor rhs is touched when no relaxation is requested.
218 if (alpha <= 0.0 || alpha == 1.0)
219 {
220 return;
221 }
222
223 const scalar invAlpha = 1.0 / alpha;
224
225 auto lsView = ls.view();
226 auto& matrix = lsView.matrix;
227 auto& rhs = lsView.rhs;
228 const auto ma = ls.faceToMatrixAddress()->view(ls.matrix().sparsity()->rowOffs().view());
229 const auto [rowOffs, colIdxs] = views(ls.matrix().rowOffs(), ls.matrix().colIdxs());
230 const auto field = solution.internalVector().view();
231
232 const localIdx nCells = field.size();
233
234 // FUSED single-pass relaxation: compute the off-diagonal magnitude sum inline and apply
235 // the dominance clamp + diagonal boost + source correction in ONE kernel, eliminating the
236 // per-call O(nCells) `sumOff` scratch allocation. Each cell needs only its OWN row's
237 // off-diagonal sum — no cross-cell dependency — and relaxation only ever writes a cell's own
238 // diagonal (matrix.values[diagIdx]) and rhs[celli]. The row walk skips the diagonal
239 // (colIdxs[idx] != celli), and a cell's diagonal write never aliases another cell's
240 // off-diagonal storage.
241 //
242 // componentCopySign preserves dAug's sign so rAU = V/diag stays correct for alpha < 1.
243 // The source correction rhs += (dRelaxed - dAug)*psi_prev makes the relaxed system share
244 // the unrelaxed fixed point. Off-diagonal sum + diagonal algebra are in MatrixValueType;
245 // the source-correction cross term (dRelaxed - dAug)*field is MatrixValueType * RHSValueType
246 // (scalar * Vec3 in the segregated momentum form).
248 ls.exec(),
249 {0, nCells},
250 NEON_LAMBDA(const localIdx celli) {
251 // Off-diagonal magnitude sum — cell-based CSR row gather, NO atomics (perf lever).
252 auto sumOff = zero<MatrixValueType>();
253 for (localIdx idx = rowOffs[celli]; idx < rowOffs[celli + 1]; ++idx)
254 {
255 if (colIdxs[idx] != celli)
256 {
257 sumOff = sumOff + componentMag(matrix.values[idx]);
258 }
259 }
260 const auto diagIdx = ma.diagIdx(celli);
261 const auto dAug = matrix.values[diagIdx]; // augmented, boundary-baked
262 const auto dDom = componentMax(componentMag(dAug), sumOff); // dominance clamp
263 const auto dRelaxed =
264 componentCopySign(dDom * invAlpha, dAug); // scale whole augmented diag by 1/alpha
265 matrix.values[diagIdx] = dRelaxed;
266 rhs[celli] = rhs[celli] + (dRelaxed - dAug) * field[celli]; // source corr
267 },
268 "applyMatrixRelaxation"
269 );
270}
271
272// ---------------------------------------------------------------------------
273// Field (explicit) under-relaxation
274// ---------------------------------------------------------------------------
275
276/* @brief Apply explicit field under-relaxation to a solution field's internal vector.
277 *
278 * Blends the internal field toward the previous outer-iteration value:
279 *
280 * psi[c] = prev[c] + alpha * (psi[c] - prev[c])
281 *
282 * `prev` is the caller-owned previous-iteration snapshot (e.g. from
283 * `fieldRelaxationSnapshot`, taken at the top of the outer corrector).
284 *
285 * Only the internal vector is blended; the caller is responsible for calling
286 * `solution.correctBoundaryConditions()` afterwards so boundary values are
287 * re-derived from the boundary conditions.
288 *
289 * `alpha <= 0` or `alpha == 1` is a bitwise no-op (the early return touches nothing).
290 * This is REQUIRED so the final outer iteration (`alpha == 1`) and an unset
291 * factor leave the field byte-for-byte unchanged, side-stepping the `alpha == 1` ULP
292 * round-trip trap of `prev + 1*(cur - prev)`.
293 *
294 * @param solution The solution VolumeField — its internal vector is blended in place.
295 * @param previous The previous-iteration snapshot of the internal vector (deep copy).
296 * @param alpha The under-relaxation factor. alpha <= 0 or alpha == 1 is a bitwise no-op.
297 */
298template<typename VectorType>
300 VectorType& solution, const Vector<typename VectorType::ElementType>& previous, scalar alpha
301)
302{
303 // Bitwise no-op guard: MUST be first so the internal vector is not touched when no
304 // relaxation is requested (final iteration / unset factor, alpha==1 ULP trap).
305 if (alpha <= 0.0 || alpha == 1.0)
306 {
307 return;
308 }
309
310 NF_ASSERT(solution.size() == previous.size(), "applyFieldRelaxation: field/prev size mismatch");
311
312 auto [current, prev] = views(solution.internalVector(), previous);
314 solution.exec(),
315 {0, solution.size()},
316 NEON_LAMBDA(const localIdx celli) {
317 current[celli] = prev[celli] + alpha * (current[celli] - prev[celli]);
318 },
319 "applyFieldRelaxation"
320 );
321}
322
323/* @brief Snapshot a field's internal vector for use as the `previous` arg to
324 * `applyFieldRelaxation` (caller-owned prevIter primitive).
325 *
326 * Returns an independent on-executor deep copy of `field.internalVector()` via the
327 * `Vector` copy constructor — subsequent mutation of `field` does not affect the
328 * returned snapshot. Do NOT hand-roll a `parallelFor` copy; the copy ctor already
329 * performs an executor-correct deep copy.
330 */
331template<typename VectorType>
332[[nodiscard]] auto fieldRelaxationSnapshot(const VectorType& field)
333{
334 return Vector<typename VectorType::ElementType>(field.internalVector());
335}
336
337} // namespace dsl
A class representing a dictionary that stores key-value pairs.
Dictionary & subDict(const std::string &key)
Retrieves a sub-dictionary associated with the given key.
A class for the representation of a 3D Vec3.
Definition vec3.hpp:24
A class to contain the data and executors for a field and define some basic operations.
Definition vector.hpp:27
localIdx size() const
Gets the size of the field.
Definition vector.hpp:268
la::LinearSystem< AssemblyType, ValueType > assemble(const UnstructuredMesh &mesh, scalar t, scalar dt, std::vector< const PostAssemblyBase< ValueType, IndexType > * > ps={}) const
construct a linear system and force assembly including explicit source terms
const std::vector< TemporalOperator< ValueType > > & temporalOperators() const
void read(const Dictionary &input)
const std::vector< SpatialOperator< ValueType > > & spatialOperators() const
A class representing a linear system of equations.
Vector< RHSValueType > & rhs()
std::shared_ptr< const FaceToMatrixAddress > faceToMatrixAddress() const
LinearSystemView< RHSValueType, MatrixView< MatrixValueType, SparsityView< typename SystemMatrixType::MatrixSparsityType::SparsityIndexType > > > view() &&=delete
const Executor & exec() const
SystemMatrixType & matrix()
#define NF_ERROR_EXIT(message)
Macro for printing an error message and aborting the program.
Definition error.hpp:90
#define NF_ASSERT(condition, message)
Macro for asserting a condition and printing an error message if the condition is false.
Definition error.hpp:118
la::SolverStats iterativeSolveImpl(Expression< typename VectorType::ElementType > &exp, la::LinearSystem< typename VectorType::ElementType > &ls, VectorType &solution, scalar t, scalar dt, const Dictionary &fvSchemes, const Dictionary &fvSolution, std::vector< const PostAssemblyBase< typename VectorType::ElementType, IndexType > * > ps={})
Definition solver.hpp:32
void applyFieldRelaxation(VectorType &solution, const Vector< typename VectorType::ElementType > &previous, scalar alpha)
Definition solver.hpp:299
KOKKOS_INLINE_FUNCTION scalar componentMag(const scalar value)
Componentwise magnitude (scalar overload).
Definition solver.hpp:149
KOKKOS_INLINE_FUNCTION scalar componentMax(const scalar lhs, const scalar rhs)
Componentwise max (scalar overload).
Definition solver.hpp:159
KOKKOS_INLINE_FUNCTION scalar copySign(const scalar mag, const scalar s)
Returns |mag| carrying the sign of s (scalar overload).
Definition solver.hpp:131
auto fieldRelaxationSnapshot(const VectorType &field)
Definition solver.hpp:332
void applyMatrixRelaxation(la::LinearSystem< MatrixValueType, RHSValueType, SystemMatrixType, BoundaryMatrixType > &ls, const VectorType &solution, scalar alpha)
Definition solver.hpp:210
ExpressionType optimize(const ExpressionType &in)
Apply the default optimizer pipeline to an expression.
std::optional< la::SolverStats > solve(Expression< typename VectorType::ElementType, IndexType > &exp, VectorType &solution, scalar t, scalar dt, const Dictionary &fvSchemes, const Dictionary &fvSolution, std::vector< const PostAssemblyBase< typename VectorType::ElementType, IndexType > * > p={})
Definition solver.hpp:95
KOKKOS_INLINE_FUNCTION scalar componentCopySign(const scalar mag, const scalar s)
Componentwise sign-copy (scalar overload).
Definition solver.hpp:137
void fence(const Executor &exec)
Definition executor.hpp:23
int32_t localIdx
Definition label.hpp:50
KOKKOS_INLINE_FUNCTION scalar mag(const scalar &s)
Definition scalar.hpp:23
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