NeoN
A framework for CFD software
Loading...
Searching...
No Matches
mergedPgm.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 NeoN authors
2//
3// SPDX-License-Identifier: MIT
4
5#pragma once
6
7// Guarded like ginkgo.hpp: the generated NeoN.hpp umbrella header includes every header
8// unconditionally, so this must be a no-op in a build configured without Ginkgo.
9#if NF_WITH_GINKGO
10
11#include <algorithm>
12#include <memory>
13#include <numeric>
14#include <stdexcept>
15#include <string>
16#include <vector>
17
18#include <ginkgo/ginkgo.hpp>
19#include <ginkgo/core/multigrid/pgm.hpp>
20#include <ginkgo/core/multigrid/multigrid_level.hpp>
21#include <ginkgo/core/matrix/csr.hpp>
22#include <ginkgo/core/matrix/row_gatherer.hpp>
23#include <ginkgo/core/solver/multigrid.hpp>
24
27
28#ifdef NF_WITH_MPI_SUPPORT
29#include <ginkgo/core/distributed/base.hpp>
30#include <ginkgo/core/distributed/matrix.hpp>
31#include <ginkgo/core/distributed/vector.hpp>
32#endif
33
34namespace NeoN::la::ginkgo
35{
36
37namespace detail
38{
39
44struct ScaleCorrectionState
45{
46 const gko::LinOp* residual = nullptr;
47};
48
53template<typename Kernel>
54void forEachOn(const gko::Executor& exec, gko::size_type n, const Kernel& kernel)
55{
56 // Host executors are their own master.
57 if (exec.get_master().get() != &exec)
58 {
59 parallelFor(GPUExecutor {}, {0, static_cast<localIdx>(n)}, kernel, "mergedPgm");
60 }
61 else
62 {
63 for (localIdx i = 0; i < static_cast<localIdx>(n); ++i)
64 {
65 kernel(i);
66 }
67 }
68}
69
72template<typename ValueType>
73void rayleighScale(
74 const gko::matrix::Dense<ValueType>* num,
75 const gko::matrix::Dense<ValueType>* den,
76 gko::matrix::Dense<ValueType>* sf
77)
78{
79 const auto* n = num->get_const_values();
80 const auto* d = den->get_const_values();
81 auto* s = sf->get_values();
82 forEachOn(
83 *sf->get_executor(),
84 sf->get_size()[1],
85 NEON_LAMBDA(const localIdx c) { s[c] = d[c] != ValueType {} ? n[c] / d[c] : ValueType {1}; }
86 );
87}
88
90class RecordingRestriction : public gko::LinOp
91{
92public:
93
94 RecordingRestriction(
95 std::shared_ptr<const gko::LinOp> restrictOp, std::shared_ptr<ScaleCorrectionState> state
96 )
97 : gko::LinOp(restrictOp->get_executor(), restrictOp->get_size()),
98 restrict_ {std::move(restrictOp)}, state_ {std::move(state)}
99 {}
100
101protected:
102
103 void apply_impl(const gko::LinOp* b, gko::LinOp* x) const override
104 {
105 state_->residual = b;
106 restrict_->apply(b, x);
107 }
108
109 void apply_impl(
110 const gko::LinOp* alpha, const gko::LinOp* b, const gko::LinOp* beta, gko::LinOp* x
111 ) const override
112 {
113 state_->residual = nullptr;
114 restrict_->apply(alpha, b, beta, x);
115 }
116
117private:
118
119 std::shared_ptr<const gko::LinOp> restrict_;
120 std::shared_ptr<ScaleCorrectionState> state_;
121};
122
134template<typename ValueType, typename IndexType>
135class ScaledProlongation : public gko::LinOp
136{
137 using dense = gko::matrix::Dense<ValueType>;
138 using diagonal = gko::matrix::Diagonal<ValueType>;
139
140public:
141
144 ScaledProlongation(
145 std::shared_ptr<const gko::LinOp> prolongOp,
146 std::shared_ptr<const gko::LinOp> fineOp,
147 std::shared_ptr<const diagonal> invDiag,
148 std::shared_ptr<ScaleCorrectionState> state
149 )
150 : gko::LinOp(prolongOp->get_executor(), prolongOp->get_size()),
151 prolong_ {std::move(prolongOp)}, fine_ {std::move(fineOp)}, invDiag_ {std::move(invDiag)},
152 state_ {std::move(state)}
153 {}
154
155protected:
156
157 void apply_impl(const gko::LinOp* b, gko::LinOp* x) const override { prolong_->apply(b, x); }
158
159 void apply_impl(
160 const gko::LinOp* alpha, const gko::LinOp* b, const gko::LinOp* beta, gko::LinOp* x
161 ) const override
162 {
163 const auto* r = state_->residual;
164 state_->residual = nullptr;
165 if (r == nullptr || r->get_size() != x->get_size())
166 {
167 prolong_->apply(alpha, b, beta, x);
168 return;
169 }
170#ifdef NF_WITH_MPI_SUPPORT
171 using dist_vec = gko::experimental::distributed::Vector<ValueType>;
172 if (auto distX = dynamic_cast<dist_vec*>(x))
173 {
174 scaled(alpha, b, beta, distX, gko::as<const dist_vec>(r));
175 return;
176 }
177#endif
178 scaled(alpha, b, beta, gko::as<dense>(x), gko::as<const dense>(r));
179 }
180
181private:
182
183 static dense* local(dense* v) { return v; }
184 static const dense* local(const dense* v) { return v; }
185#ifdef NF_WITH_MPI_SUPPORT
186 static const dense* local(const gko::experimental::distributed::Vector<ValueType>* v)
187 {
188 return v->get_local_vector();
189 }
190 // mutable view of the rank-local part (distributed::Vector only exposes it read-only)
191 static std::unique_ptr<dense> local(gko::experimental::distributed::Vector<ValueType>* v)
192 {
193 const auto* l = v->get_local_vector();
194 return dense::create(
195 l->get_executor(),
196 l->get_size(),
197 gko::make_array_view(
198 l->get_executor(), l->get_size()[0] * l->get_stride(), v->get_local_values()
199 ),
200 l->get_stride()
201 );
202 }
203#endif
204
205 template<typename VecType>
206 VecType* workspace(std::unique_ptr<gko::LinOp>& slot, const VecType* like) const
207 {
208 auto ws = dynamic_cast<VecType*>(slot.get());
209 if (ws == nullptr || ws->get_size() != like->get_size())
210 {
211 slot = VecType::create_with_config_of(like);
212 ws = gko::as<VecType>(slot.get());
213 }
214 return ws;
215 }
216
217 template<typename VecType>
218 void scaled(
219 const gko::LinOp* alpha,
220 const gko::LinOp* e,
221 const gko::LinOp* beta,
222 VecType* x,
223 const VecType* r
224 ) const
225 {
226 auto exec = this->get_executor();
227 const auto ncols = x->get_size()[1];
228 auto* d = workspace(delta_, x);
229 auto* ad = workspace(aDelta_, x);
230 auto* t = workspace(tmp_, x);
231
232 prolong_->apply(e, d); // d = P e
233 fine_->apply(d, ad); // ad = A d
234
235 auto num = dense::create(exec, gko::dim<2> {1, ncols});
236 auto den = dense::create(exec, gko::dim<2> {1, ncols});
237 d->compute_dot(r, num);
238 d->compute_dot(ad, den);
239 // on the executor: a host round trip here (two copies to the host, one back, each
240 // synchronising the stream) ran on every scaled prolongation of every V-cycle
241 auto sf = dense::create(exec, gko::dim<2> {1, ncols});
242 rayleighScale(num.get(), den.get(), sf.get());
243
244 auto one = gko::initialize<dense>({gko::one<ValueType>()}, exec);
245 ad->scale(sf); // ad = sf A d
246 t->copy_from(r);
247 t->sub_scaled(one, ad); // t = r - sf A d
248 d->scale(sf); // d = sf d
249 invDiag_->apply(one, local(static_cast<const VecType*>(t)), one, local(d)); // d += D^-1 t
250
251 x->scale(beta);
252 x->add_scaled(alpha, d);
253 }
254
255 std::shared_ptr<const gko::LinOp> prolong_;
256 std::shared_ptr<const gko::LinOp> fine_;
257 std::shared_ptr<const diagonal> invDiag_;
258 std::shared_ptr<ScaleCorrectionState> state_;
259 mutable std::unique_ptr<gko::LinOp> delta_;
260 mutable std::unique_ptr<gko::LinOp> aDelta_;
261 mutable std::unique_ptr<gko::LinOp> tmp_;
262};
263
265template<typename ValueType, typename IndexType>
266std::shared_ptr<const gko::matrix::Diagonal<ValueType>>
267invertedDiagonal(const gko::matrix::Csr<ValueType, IndexType>* mtx)
268{
269 // invert in place on mtx's executor (no copy of the diagonal to the host and back)
270 auto diag = gko::share(mtx->extract_diagonal());
271 auto* v = diag->get_values();
272 forEachOn(
273 *diag->get_executor(),
274 diag->get_size()[0],
275 NEON_LAMBDA(const localIdx i) {
276 v[i] = v[i] != ValueType {} ? ValueType {1} / v[i] : ValueType {};
277 }
278 );
279 return diag;
280}
281
282} // namespace detail
283
314template<typename ValueType = gko::default_precision, typename IndexType = gko::int32>
315class MergedPgm : public gko::LinOp, public gko::multigrid::EnableMultigridLevel<ValueType>
316{
317public:
318
319 using value_type = ValueType;
320 using index_type = IndexType;
321 using csr = gko::matrix::Csr<ValueType, IndexType>;
322 using pgm = gko::multigrid::Pgm<ValueType, IndexType>;
323 using row_gatherer = gko::matrix::RowGatherer<IndexType>;
324#ifdef NF_WITH_MPI_SUPPORT
325 // NeoN always builds its distributed system matrix with a 64-bit global index type
326 // (@see createGkoMtxDist in ginkgoDistributed.cpp); Pgm keeps the same global index type for
327 // every coarser level, so all distributed operators seen here are Matrix<Value, Index, int64>.
328 using dist_mtx = gko::experimental::distributed::Matrix<ValueType, IndexType, gko::int64>;
329#endif
330
331 std::shared_ptr<const gko::LinOp> get_system_matrix() const { return system_matrix_; }
332
333 GKO_CREATE_FACTORY_PARAMETERS(parameters, Factory)
334 {
336 unsigned GKO_FACTORY_PARAMETER_SCALAR(merge_levels, 2u);
338 unsigned GKO_FACTORY_PARAMETER_SCALAR(max_iterations, 15u);
339 double GKO_FACTORY_PARAMETER_SCALAR(max_unassigned_ratio, 0.05);
340 bool GKO_FACTORY_PARAMETER_SCALAR(deterministic, false);
341 bool GKO_FACTORY_PARAMETER_SCALAR(skip_sorting, false);
343 bool GKO_FACTORY_PARAMETER_SCALAR(scale_correction, false);
344 };
345 class reuse_data_type;
346 GKO_ENABLE_LIN_OP_FACTORY_WITH_REUSE(MergedPgm, parameters, Factory, reuse_data_type);
347 GKO_ENABLE_BUILD_METHOD(Factory);
348
355 class reuse_data_type : public gko::LinOpFactory::ReuseData
356 {
357 friend class MergedPgm;
358
359 bool initialized_ = false;
360 bool distributed_ = false;
361 gko::dim<2> size_ {};
362 std::vector<std::unique_ptr<gko::LinOpFactory::ReuseData>> levels_;
363 std::shared_ptr<const gko::LinOp> prolong_;
364 std::shared_ptr<const gko::LinOp> restrict_;
365 };
366
367protected:
368
369 void apply_impl(const gko::LinOp* b, gko::LinOp* x) const override
370 {
371 this->get_composition()->apply(b, x);
372 }
373 void apply_impl(
374 const gko::LinOp* alpha, const gko::LinOp* b, const gko::LinOp* beta, gko::LinOp* x
375 ) const override
376 {
377 this->get_composition()->apply(alpha, b, beta, x);
378 }
379
380 explicit MergedPgm(std::shared_ptr<const gko::Executor> exec) : gko::LinOp(std::move(exec)) {}
381
382 MergedPgm(const Factory* factory, std::shared_ptr<const gko::LinOp> system_matrix)
383 : MergedPgm(factory, std::move(system_matrix), nullptr)
384 {}
385
386 MergedPgm(
387 const Factory* factory,
388 std::shared_ptr<const gko::LinOp> system_matrix,
389 reuse_data_type& reuse_data
390 )
391 : MergedPgm(factory, std::move(system_matrix), &reuse_data)
392 {}
393
394 MergedPgm(
395 const Factory* factory,
396 std::shared_ptr<const gko::LinOp> system_matrix,
397 reuse_data_type* reuse_data
398 )
399 : gko::LinOp(factory->get_executor(), system_matrix->get_size()),
400 gko::multigrid::EnableMultigridLevel<ValueType>(system_matrix),
401 parameters_ {factory->get_parameters()}, system_matrix_ {system_matrix}
402 {
403 GKO_ASSERT(parameters_.merge_levels >= 1u);
404 if (system_matrix_->get_size()[0] != 0)
405 {
406 if (is_distributed(system_matrix_.get()))
407 {
408 generateDistributed(reuse_data);
409 }
410 else
411 {
412 generateLocal(reuse_data);
413 }
414 }
415 }
416
418 static void check_reuse_consistent(
419 const Factory*, const gko::LinOp* input, const reuse_data_type& reuse_data
420 )
421 {
422 if (!reuse_data.initialized_) return;
423 GKO_ASSERT_EQUAL_DIMENSIONS(input, reuse_data.size_);
424 if (is_distributed(input) != reuse_data.distributed_)
425 {
426 GKO_INVALID_STATE(
427 "generate_reuse needs a distributed matrix exactly if the reuse data was "
428 "initialized with one"
429 );
430 }
431 }
432
433 // ---- localized / Schwarz path: fine op is a plain Csr on this rank ----
434 void generateLocal(reuse_data_type* reuseData)
435 {
436 auto A = as_csr(system_matrix_);
437 const auto fineRows = A->get_size()[0];
438 const bool reuseMerged = reuseData && reuseData->initialized_;
439
440 // Run Pgm `merge_levels` times, composing the per-level aggregations BY INDEX. Each inner
441 // Pgm coarsens `coarse` and yields a piecewise-constant prolong (a RowGatherer: fine row ->
442 // aggregate) plus the next coarse operator. Because the prolongs are injections, the merged
443 // prolongation is the injection of the composed aggregate map `mergedAgg[i] =
444 // aggThis[mergedAgg[i]]`
445 // -- no Csr*Csr SpGEMM (cuSPARSE aborts with INSUFFICIENT_RESOURCES on large operands).
446 // When reusing, the inner aggregates are frozen, so the recorded merged injection is reused
447 // and only the coarse operator is refreshed.
448 auto pgmFactory = makePgm();
449 std::vector<std::unique_ptr<gko::LinOpFactory::ReuseData>> newSlots;
450 std::shared_ptr<const csr> coarse = A;
451 std::vector<IndexType> mergedAgg; // fine row -> current coarse index (host)
452 gko::size_type coarseRows = 0;
453 for (unsigned i = 0; i < parameters_.merge_levels; ++i)
454 {
455 auto level = generateLevel(pgmFactory.get(), coarse, i, reuseData, newSlots);
456 if (!reuseMerged)
457 {
458 auto aggThis = gather_agg(level.get()); // host, length coarse->rows
459 coarseRows = level->get_prolong_op()->get_size()[1];
460 composeAgg(mergedAgg, aggThis, fineRows, i);
461 }
462 coarse = as_csr(level->get_coarse_op());
463 }
464
465 if (reuseMerged)
466 {
467 prolong_ = reuseData->prolong_;
468 restrict_ = reuseData->restrict_;
469 }
470 else
471 {
472 auto pCsr = make_injection(mergedAgg, fineRows, coarseRows); // Csr, fine x coarse
473 prolong_ = pCsr;
474 restrict_ = gko::share(gko::as<csr>(pCsr->transpose())); // Csr, coarse x fine
475 }
476 // coarse == P_merged^T A P_merged already (last Pgm level's coarse op); use it directly.
477 setLevel(A, A.get(), coarse);
478 recordReuse(reuseData, std::move(newSlots), false);
479 }
480
481#ifdef NF_WITH_MPI_SUPPORT
482 // ---- global path: fine op is a distributed::Matrix ----
483 void generateDistributed(reuse_data_type* reuseData)
484 {
485 auto exec = this->get_executor();
486 auto distFine = gko::as<const dist_mtx>(system_matrix_);
487 auto comm = gko::as<const gko::experimental::distributed::DistributedBase>(system_matrix_)
488 ->get_communicator();
489 const auto fineGlobalRows = system_matrix_->get_size()[0];
490 // The prolong composition is rank-local because the distributed Pgm prolong is
491 // block-diagonal; all sizes below are LOCAL (this rank's diag block).
492 const auto localFineRows = distFine->get_diag_matrix()->get_size()[0];
493 const bool reuseMerged = reuseData && reuseData->initialized_;
494
495 auto pgmFactory = makePgm();
496 std::vector<std::unique_ptr<gko::LinOpFactory::ReuseData>> newSlots;
497 std::shared_ptr<const gko::LinOp> coarse = system_matrix_; // stays distributed
498 std::vector<IndexType> mergedAgg; // LOCAL fine row -> LOCAL coarse idx
499 gko::size_type localCoarseRows = 0;
500 for (unsigned i = 0; i < parameters_.merge_levels; ++i)
501 {
502 auto level = generateLevel(pgmFactory.get(), coarse, i, reuseData, newSlots);
503 if (!reuseMerged)
504 {
505 // Aggregation lives in the block-diagonal prolong's DIAG block (a RowGatherer).
506 auto prolongDiag =
507 gko::as<const dist_mtx>(level->get_prolong_op())->get_diag_matrix();
508 auto aggThis =
509 agg_from_rowgatherer(prolongDiag.get()); // host, length = local fine rows
510 // local coarse rows == this level's coarse DIAG block rows
511 localCoarseRows = gko::as<const dist_mtx>(level->get_coarse_op())
512 ->get_diag_matrix()
513 ->get_size()[0];
514 composeAgg(mergedAgg, aggThis, localFineRows, i);
515 }
516 coarse = level->get_coarse_op(); // distributed coarse op -> next Pgm fine op
517 }
518
519 if (reuseMerged)
520 {
521 prolong_ = reuseData->prolong_;
522 restrict_ = reuseData->restrict_;
523 }
524 else
525 {
526 const auto coarseGlobalRows = coarse->get_size()[0];
527 // Build the merged local injection Csr, then re-wrap prolong/restrict as
528 // block-diagonal distributed matrices (diag block only, empty off-diagonal) -- mirrors
529 // pgm.cpp's distributed_setup, which builds prolong/restrict from the local block
530 // alone.
531 std::shared_ptr<csr> pLocal = make_injection(mergedAgg, localFineRows, localCoarseRows);
532 std::shared_ptr<gko::LinOp> rLocal = gko::share(gko::as<csr>(pLocal->transpose()));
533 prolong_ = gko::share(
534 dist_mtx::create(exec, comm, gko::dim<2> {fineGlobalRows, coarseGlobalRows}, pLocal)
535 );
536 restrict_ = gko::share(
537 dist_mtx::create(exec, comm, gko::dim<2> {coarseGlobalRows, fineGlobalRows}, rLocal)
538 );
539 }
540 // coarse == the last inner Pgm's distributed coarse op == A_merged; use it directly.
541 setLevel(system_matrix_, gko::as<csr>(distFine->get_diag_matrix().get()), coarse);
542 recordReuse(reuseData, std::move(newSlots), true);
543 }
544#else
545 void generateDistributed(reuse_data_type*) { GKO_NOT_IMPLEMENTED; }
546#endif
547
548private:
549
550 // Set the level's operators; with scale_correction the transfer operators are wrapped (the
551 // plain prolong_/restrict_ stay what the reuse data records). `diagBlock` is the rank-local
552 // diagonal block of `fineOp` (the matrix itself in the local path).
553 void setLevel(
554 std::shared_ptr<const gko::LinOp> fineOp,
555 const csr* diagBlock,
556 std::shared_ptr<const gko::LinOp> coarse
557 )
558 {
559 if (!parameters_.scale_correction)
560 {
561 this->set_multigrid_level(prolong_, std::move(coarse), restrict_);
562 return;
563 }
564 auto state = std::make_shared<detail::ScaleCorrectionState>();
565 this->set_multigrid_level(
566 std::make_shared<detail::ScaledProlongation<ValueType, IndexType>>(
567 prolong_, std::move(fineOp), detail::invertedDiagonal(diagBlock), state
568 ),
569 std::move(coarse),
570 std::make_shared<detail::RecordingRestriction>(restrict_, state)
571 );
572 }
573
574 // Generate inner Pgm level `i`: plainly without reuse data, through the recorded slot once the
575 // reuse data is initialized, and otherwise through a fresh slot that recordReuse() commits.
576 static std::unique_ptr<pgm> generateLevel(
577 const typename pgm::Factory* pgmFactory,
578 std::shared_ptr<const gko::LinOp> fine,
579 unsigned i,
580 reuse_data_type* reuseData,
581 std::vector<std::unique_ptr<gko::LinOpFactory::ReuseData>>& newSlots
582 )
583 {
584 if (!reuseData) return pgmFactory->generate(std::move(fine));
585 if (reuseData->initialized_)
586 {
587 return pgmFactory->generate_reuse(std::move(fine), *reuseData->levels_.at(i));
588 }
589 newSlots.push_back(pgmFactory->create_empty_reuse_data());
590 return pgmFactory->generate_reuse(std::move(fine), *newSlots.back());
591 }
592
593 // Fill an empty reuse data only once the whole level was generated, so that a throw leaves it
594 // empty (LinOpFactory::generate_reuse_impl guarantee).
595 void recordReuse(
596 reuse_data_type* reuseData,
597 std::vector<std::unique_ptr<gko::LinOpFactory::ReuseData>> newSlots,
598 bool distributed
599 ) const
600 {
601 if (!reuseData || reuseData->initialized_) return;
602 reuseData->levels_ = std::move(newSlots);
603 reuseData->prolong_ = prolong_;
604 reuseData->restrict_ = restrict_;
605 reuseData->size_ = system_matrix_->get_size();
606 reuseData->distributed_ = distributed;
607 reuseData->initialized_ = true;
608 }
609
610 static bool is_distributed(const gko::LinOp* op)
611 {
612#ifdef NF_WITH_MPI_SUPPORT
613 return dynamic_cast<const gko::experimental::distributed::DistributedBase*>(op) != nullptr;
614#else
615 static_cast<void>(op);
616 return false;
617#endif
618 }
619
620 std::unique_ptr<typename pgm::Factory> makePgm() const
621 {
622 return pgm::build()
623 .with_max_iterations(parameters_.max_iterations)
624 .with_max_unassigned_ratio(parameters_.max_unassigned_ratio)
625 .with_deterministic(parameters_.deterministic)
626 .with_skip_sorting(parameters_.skip_sorting)
627 .on(this->get_executor());
628 }
629
630 // mergedAgg[k] <- aggThis[mergedAgg[k]] (or aggThis directly on the first level). Identical for
631 // the local and distributed paths -- the distributed prolong is block-diagonal so its
632 // diag-block aggregation composes rank-locally, exactly like a local Csr aggregation.
633 static void composeAgg(
634 std::vector<IndexType>& mergedAgg,
635 const gko::array<IndexType>& aggThis,
636 gko::size_type fineRows,
637 unsigned i
638 )
639 {
640 if (i == 0)
641 {
642 mergedAgg.assign(aggThis.get_const_data(), aggThis.get_const_data() + fineRows);
643 }
644 else
645 {
646 for (gko::size_type k = 0; k < fineRows; ++k)
647 {
648 mergedAgg[k] = aggThis.get_const_data()[mergedAgg[k]];
649 }
650 }
651 }
652
653 static std::shared_ptr<const csr> as_csr(std::shared_ptr<const gko::LinOp> op)
654 {
655 if (auto c = std::dynamic_pointer_cast<const csr>(op)) return c;
656 auto exec = op->get_executor();
657 auto out = csr::create(exec);
658 gko::as<const gko::ConvertibleTo<csr>>(op.get())->convert_to(out);
659 return std::move(out);
660 }
661
662 // Pull a RowGatherer's per-row aggregate map (fine row i -> its aggregate) to the host. Length
663 // == the gatherer's row count; values in [0, coarseRows). One entry per row.
664 gko::array<IndexType> agg_from_rowgatherer(const gko::LinOp* rgOp) const
665 {
666 auto exec = this->get_executor();
667 auto host = exec->get_master();
668 auto rg = gko::as<const row_gatherer>(rgOp);
669 auto fineRows = rg->get_size()[0];
670 gko::array<IndexType> agg(host, fineRows);
671 host->copy_from(exec, fineRows, rg->get_const_row_idxs(), agg.get_data());
672 return agg;
673 }
674
675 // Localized-path convenience: the level's prolong IS a RowGatherer.
676 gko::array<IndexType> gather_agg(const gko::multigrid::MultigridLevel* level) const
677 {
678 return agg_from_rowgatherer(level->get_prolong_op().get());
679 }
680
681 // Build the merged injection Csr P (P[i, mergedAgg[i]] = 1) from the composed aggregate map on
682 // the host, then clone to the device executor. row_ptrs = iota (one entry per row); values =
683 // ones. One entry per row => naturally sorted. Returned non-const so it can serve as the
684 // (mutable) diag block of a distributed::Matrix.
685 std::shared_ptr<csr> make_injection(
686 const std::vector<IndexType>& mergedAgg, gko::size_type fineRows, gko::size_type coarseRows
687 ) const
688 {
689 auto exec = this->get_executor();
690 auto host = exec->get_master();
691 gko::array<IndexType> cols(host, fineRows);
692 std::copy(mergedAgg.begin(), mergedAgg.end(), cols.get_data());
693 gko::array<IndexType> rowPtrs(host, fineRows + 1);
694 std::iota(rowPtrs.get_data(), rowPtrs.get_data() + fineRows + 1, IndexType {0});
695 gko::array<ValueType> vals(host, fineRows);
696 std::fill_n(vals.get_data(), fineRows, gko::one<ValueType>());
697
698 auto pHost = csr::create(
699 host,
700 gko::dim<2> {fineRows, coarseRows},
701 std::move(vals),
702 std::move(cols),
703 std::move(rowPtrs)
704 );
705 return gko::share(gko::clone(exec, pHost));
706 }
707
708 // NB: `parameters_` is provided by GKO_ENABLE_LIN_OP_FACTORY_WITH_REUSE; do not redeclare it.
709 std::shared_ptr<const gko::LinOp> system_matrix_;
710 // Merged prolong/restrict: a Csr in the local path, a block-diagonal distributed::Matrix in the
711 // global path. Structural (composed once); shared with the reuse data.
712 std::shared_ptr<const gko::LinOp> prolong_;
713 std::shared_ptr<const gko::LinOp> restrict_;
714};
715
721// NB: return the CONCRETE factory type, not the abstract gko::LinOpFactory. gko::config::registry
722// keys entries by base_type<T>::type, which is defined for concrete factories (-> LinOpFactory) but
723// not for the abstract base — emplacing a shared_ptr<const LinOpFactory> fails to compile.
724template<typename ValueType = gko::default_precision, typename IndexType = gko::int32>
725inline std::shared_ptr<typename MergedPgm<ValueType, IndexType>::Factory> makeMergedPgmFactory(
726 std::shared_ptr<const gko::Executor> exec,
727 unsigned mergeLevels,
728 bool deterministic = true,
729 bool scaleCorrection = false
730)
731{
732 return MergedPgm<ValueType, IndexType>::build()
733 .with_merge_levels(mergeLevels)
734 .with_deterministic(deterministic)
735 .with_scale_correction(scaleCorrection)
736 .on(std::move(exec));
737}
738
744template<typename ValueType = gko::default_precision, typename IndexType = gko::int32>
745inline gko::config::configuration_map mergedPgmConfigMap()
746{
747 return {
748 {"neon::MergedPgm",
749 [](const gko::config::pnode& config,
750 const gko::config::registry&,
751 gko::config::type_descriptor) -> gko::deferred_factory_parameter<gko::LinOpFactory>
752 {
753 auto params = MergedPgm<ValueType, IndexType>::build();
754 auto toUnsigned = [](const gko::config::pnode& node, const std::string& key)
755 {
756 const auto v = node.get_integer();
757 if (v < 0)
758 {
759 throw std::invalid_argument("neon::MergedPgm: " + key + " must be >= 0");
760 }
761 return static_cast<unsigned>(v);
762 };
763 for (const auto& [key, value] : config.get_map())
764 {
765 if (key == "type" || key == "value_type") continue;
766 if (key == "merge_levels") params.with_merge_levels(toUnsigned(value, key));
767 else if (key == "max_iterations")
768 params.with_max_iterations(toUnsigned(value, key));
769 else if (key == "max_unassigned_ratio")
770 params.with_max_unassigned_ratio(value.get_real());
771 else if (key == "deterministic")
772 params.with_deterministic(value.get_boolean());
773 else if (key == "skip_sorting")
774 params.with_skip_sorting(value.get_boolean());
775 else if (key == "scale_correction")
776 params.with_scale_correction(value.get_boolean());
777 else
778 throw std::invalid_argument("neon::MergedPgm: unknown key '" + key + "'");
779 }
780 return params;
781 }}
782 };
783}
784
785} // namespace NeoN::la::ginkgo
786
787#endif // NF_WITH_GINKGO
KOKKOS_INLINE_FUNCTION T one()
void parallelFor(const ExecutorType &, std::pair< localIdx, localIdx > range, const Kernel &kernel, std::string name)
int32_t localIdx
Definition label.hpp:50
#define NEON_LAMBDA