NeoN
A framework for CFD software
Loading...
Searching...
No Matches
ginkgo.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2024 - 2026 NeoN authors
2//
3// SPDX-License-Identifier: MIT
4
5#pragma once
6
7#if NF_WITH_GINKGO
8
9#include <chrono>
10#include <optional>
11#include <string>
12
13#include <ginkgo/ginkgo.hpp>
14#include <ginkgo/extensions/kokkos.hpp>
15#include <ginkgo/extensions/config/json_config.hpp>
16
17#include "NeoN/fields/field.hpp"
23
24
25namespace NeoN::la::ginkgo
26{
27
28std::shared_ptr<gko::Executor> getGkoExecutor(Executor exec);
29
30gko::config::pnode parse(const Dictionary& dict);
31
33template<typename NeoNMatrixType>
34std::shared_ptr<const gko::LinOp> createGkoMtx(const NeoNMatrixType& mtx);
35
36#ifdef NF_WITH_MPI_SUPPORT
38
39template<typename IndexType>
40std::shared_ptr<const gko::LinOp> createGkoMtxDist(
41 std::shared_ptr<const gko::Executor> exec,
42 const gko::experimental::mpi::communicator& comm,
43 const CSRMatrix<scalar, IndexType>& mtx,
44 const COOMatrix<scalar, IndexType>& bmtx,
45 const CommunicationPattern& commPattern,
46 std::shared_ptr<gko::experimental::distributed::index_map<label, gko::int64>>& imapCache,
47 std::shared_ptr<gko::matrix::Coo<scalar, IndexType>>& nonLocalMtxCache
48);
49#endif // NF_WITH_MPI_SUPPORT
50
51template<typename T>
52gko::array<T> gkoArrayView(std::shared_ptr<const gko::Executor> exec, std::span<T> values)
53{
54 return gko::make_array_view(exec, values.size(), values.data());
55}
56
58template<typename T>
59std::shared_ptr<gko::matrix::Dense<scalar>>
60gkoVecView(std::shared_ptr<const gko::Executor> exec, T* ptr, localIdx s)
61{
62 constexpr std::size_t cols = std::is_same_v<T, Vec3> ? 3 : 1;
63 auto size = static_cast<std::size_t>(s);
64 return gko::share(gko::matrix::Dense<scalar>::create(
65 exec,
66 gko::dim<2> {size, cols},
67 gkoArrayView<scalar>(exec, std::span<scalar> {reinterpret_cast<scalar*>(ptr), cols * size}),
68 cols
69 ));
70}
71
73template<typename T>
74std::shared_ptr<const gko::matrix::Dense<scalar>>
75gkoVecView(std::shared_ptr<const gko::Executor> exec, const T* ptr, localIdx s)
76{
77 constexpr std::size_t cols = std::is_same_v<T, Vec3> ? 3 : 1;
78 auto size = static_cast<std::size_t>(s);
79 return gko::share(gko::matrix::Dense<scalar>::create_const(
80 exec,
81 gko::dim<2> {size, cols},
82 gko::array<scalar>::const_view(exec, cols * size, reinterpret_cast<const scalar*>(ptr)),
83 cols
84 ));
85}
86
88template<typename InType>
89scalar retrieve(const InType& in)
90{
91 using vec = gko::matrix::Dense<scalar>;
92 auto host = vec::create(in->get_executor()->get_master(), gko::dim<2> {1});
93 return host->copy_from(in)->at(0);
94}
95
101struct L1ResidualControl
102{
103 scalar tolerance;
104 scalar relTol;
105 gko::size_type maxIter;
106 gko::size_type minIter;
107 localIdx checkFrequency;
109};
110
117struct L1ResidualResult
118{
119 gko::size_type numIter = 0;
120 scalar initResNorm = 0.0;
121 scalar finalResNorm = 0.0;
122 bool fired = false;
123 // Per-column scaled residuals; populated only for multi-RHS (Vec3) solves (size == ncols).
124 std::vector<scalar> perColInitNorms;
125 std::vector<scalar> perColFinalNorms;
126};
127
133inline constexpr const char* l1CriterionKey = "neon::l1ScaledResidual";
134
142std::shared_ptr<gko::stop::CriterionFactory> makeL1CriterionFactory(
143 std::shared_ptr<const gko::Executor> exec,
144 const L1ResidualControl& control,
145 L1ResidualResult* report
146);
147
154L1ResidualResult solveWithL1Stop(
155 std::shared_ptr<const gko::Executor> exec,
156 std::shared_ptr<const gko::LinOp> mtx,
157 std::shared_ptr<const gko::matrix::Dense<scalar>> b,
158 std::shared_ptr<gko::matrix::Dense<scalar>> x,
159 gko::LinOp* solver,
160 const L1ResidualControl& control
161);
162
163#ifdef NF_WITH_MPI_SUPPORT
170L1ResidualResult solveWithL1StopDist(
171 std::shared_ptr<const gko::Executor> exec,
172 std::shared_ptr<const gko::LinOp> mtx,
173 std::shared_ptr<const gko::experimental::distributed::Vector<scalar>> b,
174 std::shared_ptr<gko::experimental::distributed::Vector<scalar>> x,
175 gko::LinOp* solver,
176 const L1ResidualControl& control
177);
178#endif
179
188inline std::optional<L1ResidualControl> readL1ResidualControl(const Dictionary& cfg)
189{
190 const std::string flag = "l1ScaledResidual";
191 if (!cfg.contains(flag))
192 {
193 return std::nullopt;
194 }
195 // A boolean read from a dictionary file is stored as a word/string, not a bool;
196 // accept the common representations rather than assuming a single type.
197 bool enabled = false;
198 if (cfg.isType<bool>(flag))
199 {
200 enabled = cfg.get<bool>(flag);
201 }
202 else if (cfg.isType<int>(flag))
203 {
204 enabled = cfg.get<int>(flag) != 0;
205 }
206 else if (cfg.isType<std::string>(flag))
207 {
208 const std::string v = cfg.get<std::string>(flag);
209 enabled = (v == "true" || v == "yes" || v == "on" || v == "1");
210 }
211 if (!enabled)
212 {
213 return std::nullopt;
214 }
215
216 L1ResidualControl control {0.0, 0.0, 1000, 0, 1};
217
218 // criteria entries may be stored as int, label or scalar depending on source
219 auto readScalar = [](const Dictionary& d, const std::string& key, scalar fallback)
220 {
221 if (!d.contains(key)) return fallback;
222 if (d.isType<int>(key)) return scalar(d.get<int>(key));
223 if (d.isType<label>(key)) return scalar(d.get<label>(key));
224 return d.get<scalar>(key);
225 };
226 auto readInt = [](const Dictionary& d, const std::string& key, localIdx fallback)
227 {
228 if (!d.contains(key)) return fallback;
229 if (d.isType<int>(key)) return localIdx(d.get<int>(key));
230 if (d.isType<scalar>(key)) return localIdx(d.get<scalar>(key));
231 return d.get<localIdx>(key);
232 };
233
234 // OpenFOAM-style top-level keys (the configFile solver path keeps them: there is no
235 // mapped "criteria" subdict there). Read first so they seed the control...
236 control.tolerance = readScalar(cfg, "tolerance", control.tolerance);
237 control.relTol = readScalar(cfg, "relTol", control.relTol);
238 control.maxIter = readInt(cfg, "maxIter", control.maxIter);
239
240 // ...then let a mapped "criteria" subdict (Ginkgo-style keys) override when present,
241 // preserving the existing behaviour for fvSolution-mapped solvers.
242 if (cfg.contains("criteria"))
243 {
244 const Dictionary& criteria = cfg.subDict("criteria");
245 control.tolerance = readScalar(criteria, "absolute_residual_norm", control.tolerance);
246 control.relTol = readScalar(criteria, "initial_residual_norm", control.relTol);
247 control.maxIter = readInt(criteria, "iteration", control.maxIter);
248 }
249 control.minIter = readInt(cfg, "minIter", control.minIter);
250 control.checkFrequency = readInt(cfg, "checkFrequency", control.checkFrequency);
251
252 return control;
253}
254
259inline bool pnodeReferencesString(const gko::config::pnode& node, const std::string& name)
260{
261 using tag = gko::config::pnode::tag_t;
262 switch (node.get_tag())
263 {
264 case tag::string:
265 return node.get_string() == name;
266 case tag::array:
267 for (const auto& e : node.get_array())
268 if (pnodeReferencesString(e, name)) return true;
269 return false;
270 case tag::map:
271 for (const auto& kv : node.get_map())
272 if (pnodeReferencesString(kv.second, name)) return true;
273 return false;
274 default:
275 return false;
276 }
277}
278
279class GinkgoSolver : public SolverFactory::template Register<GinkgoSolver>
280{
281
282 using Base = SolverFactory::template Register<GinkgoSolver>;
283
284public:
285
286 GinkgoSolver(Executor exec, const Dictionary& solverConfig)
287 : Base(exec), gkoExec_(getGkoExecutor(exec)), coupled_(solverConfig.get("coupled", false)),
288 l1Control_(readL1ResidualControl(solverConfig)), config_(parse(solverConfig))
289 {
290 // Register NeoN's L1-scaled residual criterion in the Ginkgo config registry so a
291 // configFile can name it (l1CriterionKey) in its "criteria" array. Only needed when
292 // the L1 stop is requested (l1ScaledResidual); the factory carries the tolerances and
293 // a report sink. If the parsed config actually references it, the criterion lives
294 // INSIDE the built solver (l1InConfig_) and reports via l1Report_; otherwise the
295 // existing post-hoc attach in solve() handles the flag-only case unchanged.
296 gko::config::registry reg;
297 if (l1Control_)
298 {
299 l1CritFactory_ = makeL1CriterionFactory(gkoExec_, *l1Control_, &l1Report_);
300 reg.emplace(std::string(l1CriterionKey), l1CritFactory_);
301 l1InConfig_ = pnodeReferencesString(config_, l1CriterionKey);
302 }
303 factory_ = gko::config::parse(config_, reg, gko::config::make_type_descriptor<scalar>())
304 .on(gkoExec_);
305 }
306
307 static std::string name() { return "Ginkgo"; }
308
309 static std::string doc() { return "TBD"; }
310
311 static std::string schema() { return "none"; }
312
313 virtual SolverStats solve(
314 const LinearSystem<scalar, scalar, CSRMatrix<scalar, localIdx>>& sys, Vector<scalar>& x
315 ) const final;
316
317 virtual SolverStats solve(
318 const LinearSystem<Vec3, Vec3, CSRMatrix<Vec3, localIdx>>& sys, Vector<Vec3>& x
319 ) const final;
320
321 virtual SolverStats solve(
322 const LinearSystem<scalar, Vec3, CSRMatrix<scalar, localIdx>, COOMatrix<scalar, localIdx>>&
323 sys,
324 Vector<Vec3>& x
325 ) const final;
326
327#ifdef NF_WITH_MPI_SUPPORT
328 virtual SolverStats solveDist(
329 const LinearSystem<scalar, scalar, CSRMatrix<scalar, localIdx>>& sys, Vector<scalar>& x
330 ) const final;
331
332 virtual SolverStats solveDist(
333 const LinearSystem<Vec3, Vec3, CSRMatrix<Vec3, localIdx>>& sys, Vector<Vec3>& x
334 ) const final;
335
336 virtual SolverStats solveDist(
337 const LinearSystem<scalar, Vec3, CSRMatrix<scalar, localIdx>, COOMatrix<scalar, localIdx>>&
338 sys,
339 Vector<Vec3>& x
340 ) const final;
341#endif
342
343 // TODO why use a smart pointer here?
344 virtual std::unique_ptr<SolverFactory> clone() const final
345 {
346 NF_ERROR_EXIT("Not implemented");
347 return {};
348 }
349
350private:
351
352 std::shared_ptr<const gko::Executor> gkoExec_;
353 bool coupled_;
354 std::optional<L1ResidualControl> l1Control_;
355 gko::config::pnode config_;
356 std::shared_ptr<const gko::LinOpFactory> factory_;
357 // L1-scaled residual criterion registered into the config registry (l1Control_ set).
358 std::shared_ptr<gko::stop::CriterionFactory> l1CritFactory_;
359 // True when config_ names the L1 criterion, so it is built into factory_'s solver and
360 // reports through l1Report_ (the in-config path); false keeps the post-hoc attach.
361 bool l1InConfig_ = false;
362 // Report sink the in-config criterion writes its scaled L1 residual / iters into.
363 mutable L1ResidualResult l1Report_;
364#ifdef NF_WITH_MPI_SUPPORT
365 // Both caches are null until the first solve; after that topology is fixed.
366 mutable std::shared_ptr<gko::experimental::distributed::index_map<label, gko::int64>>
367 cachedImap_;
368 mutable std::shared_ptr<gko::matrix::Coo<scalar, localIdx>> cachedNonLocalMtx_;
369#endif
370};
371
372
373}
374
375#endif
A template class for registering derived classes with a base class.
#define NF_ERROR_EXIT(message)
Macro for printing an error message and aborting the program.
Definition error.hpp:90
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
int32_t localIdx
Definition label.hpp:50
float scalar
Definition scalar.hpp:17
const std::string & name(const NeoN::Document &doc)
Retrieves the name of a Document.