core 1.5.0
Shared MPI block decomposition, asynchronous ghost-layer exchange, SDF geometry, load balancing (header-only C++20)
Loading...
Searching...
No Matches
anderson.hpp
Go to the documentation of this file.
1// core — Anderson acceleration of a steady march: the grid-agnostic data path.
2//
3// Design: flow doc/steady_acceleration.md (D13: AndersonCore lives in core from the start; §3 the
4// state and the metric; §4 the algorithm; §6 MPI and GPU). The caller owns a fixed-point map
5// x ↦ g(x) that reads and writes a set of padded device buffers in place (flow: one
6// `Solver::step()`); this header owns the history, the reductions, the least squares, the
7// safeguards and the lazy mix of those buffers.
8//
9// MPI-free: a distributed run passes its collectives as two callables in `AndersonComm` (the
10// pattern of csr_bicgstab.hpp's setDistributed), built from an MPI_Comm by
11// solver/anderson_mpi.hpp, which is the MPI side. An empty `AndersonComm` is one rank, and an
12// MPI_Allreduce / MPI_Bcast on one rank is the identity, so np = 1 is bit-identical to serial.
13#ifndef PECLET_CORE_SOLVER_ANDERSON_HPP
14#define PECLET_CORE_SOLVER_ANDERSON_HPP
15
16#include <algorithm>
17#include <cmath>
18#include <cstddef>
19#include <cstdio>
20#include <exception>
21#include <functional>
22#include <limits>
23#include <stdexcept>
24#include <string>
25#include <utility>
26#include <vector>
27
30
32
35enum class AndersonRole : int {
36 Velocity = 0,
37 Carried = 2,
38};
39
45 std::function<void(double* data, int count)> sumAll;
46 std::function<void(void* data, std::size_t bytes)> broadcast;
47 int rank = 0;
48};
49
56 std::vector<View<double>> fields;
58 std::vector<AndersonRole> roles;
62 int ghost = 2;
65};
66
67// ---------------------------------------------------------------------------------------------
68// Device kernels and host linear algebra. Free functions, because an extended __host__ __device__
69// lambda may not live in a private member function.
70// ---------------------------------------------------------------------------------------------
71namespace detail::anderson {
72
73inline constexpr int kMaxWindow = 8;
74
78 Kokkos::MDRangePolicy<ExecSpace, Kokkos::IndexType<Index>,
79 Kokkos::Rank<3, Kokkos::Iterate::Left, Kokkos::Iterate::Left>>;
80
81inline InnerPolicy innerPolicy(const IVec<3>& e, int g) {
82 return InnerPolicy({Index(g), Index(g), Index(g)}, {e[0] - g, e[1] - g, e[2] - g});
83}
84
86template <int N>
87struct SumN {
88 double v[N];
90 for (int i = 0; i < N; ++i)
91 v[i] = 0.0;
92 }
94 for (int i = 0; i < N; ++i)
95 v[i] += o.v[i];
96 return *this;
97 }
98};
99
100} // namespace detail::anderson
101} // namespace peclet::core::solver
102
103namespace Kokkos {
104template <int N>
105struct reduction_identity<peclet::core::solver::detail::anderson::SumN<N>> {
109};
110} // namespace Kokkos
111
112namespace peclet::core::solver {
113namespace detail::anderson {
114
117template <class T>
118using DeviceScalar = Kokkos::View<T, MemSpace>;
119
122 const DeviceScalar<Index>& out) {
123 const Index ex = e[0], exy = e[0] * e[1];
124 Kokkos::parallel_reduce(
125 "anderson::count", innerPolicy(e, g),
126 KOKKOS_LAMBDA(const Index x, const Index y, const Index z, Index& acc) {
127 const Index i = x + y * ex + z * exy;
128 if (buf(i) != prev(i))
129 ++acc;
130 },
131 Kokkos::Sum<Index, MemSpace>(out));
132}
133
141
144 const MixColumns& cols, double beta) {
145 const double damp = 1.0 - beta;
146 Kokkos::parallel_for(
147 "anderson::mix", Kokkos::RangePolicy<ExecSpace, Kokkos::IndexType<Index>>(0, buf.extent(0)),
148 KOKKOS_LAMBDA(const Index i) {
149 double c = 0.0, d = 0.0;
150 for (int j = 0; j < cols.count; ++j) {
151 c += cols.gamma[j] * cols.dG[j](i);
152 d += cols.gamma[j] * cols.dR[j](i);
153 }
154 const double x = buf(i) - c - damp * (rPrev(i) - d);
155 buf(i) = x;
156 xOut(i) = x;
157 });
158}
159
163 Kokkos::parallel_for(
164 "anderson::diff", Kokkos::RangePolicy<ExecSpace, Kokkos::IndexType<Index>>(0, buf.extent(0)),
165 KOKKOS_LAMBDA(const Index i) {
166 const double r = buf(i) - x(i);
167 dRs(i) = r - rPrev(i);
168 dGs(i) = buf(i) - gPrev(i);
169 x(i) = r;
170 });
171}
172
175 Kokkos::parallel_for(
176 "anderson::residual",
177 Kokkos::RangePolicy<ExecSpace, Kokkos::IndexType<Index>>(0, buf.extent(0)),
178 KOKKOS_LAMBDA(const Index i) { x(i) = buf(i) - x(i); });
179}
180
184 const DeviceScalar<SumN<2>>& out) {
185 const Index ex = e[0], exy = e[0] * e[1];
186 Kokkos::parallel_reduce(
187 "anderson::self_sums", innerPolicy(e, g),
188 KOKKOS_LAMBDA(const Index xi, const Index y, const Index z, SumN<2>& acc) {
189 const Index i = xi + y * ex + z * exy;
190 acc.v[0] += x(i) * x(i);
191 acc.v[1] += buf(i) * buf(i);
192 },
193 Kokkos::Sum<SumN<2>, MemSpace>(out));
194}
195
200 const IVec<3>& e, int g, const DeviceScalar<SumN<2>>& out) {
201 const Index ex = e[0], exy = e[0] * e[1];
202 Kokkos::parallel_reduce(
203 "anderson::column_sums", innerPolicy(e, g),
204 KOKKOS_LAMBDA(const Index xi, const Index y, const Index z, SumN<2>& acc) {
205 const Index i = xi + y * ex + z * exy;
206 const double rs = dRs(i), rj = dRj(i);
207 acc.v[0] += rs * rj;
208 acc.v[1] += rj * x(i);
209 },
210 Kokkos::Sum<SumN<2>, MemSpace>(out));
211}
212
213// ---- host linear algebra (deterministic, n ≤ kMaxWindow) ----------------------------------------
214
215using Mat = double[kMaxWindow][kMaxWindow];
216
220inline void jacobiEigen(int n, Mat& a, Mat& v, double (&lam)[kMaxWindow]) {
221 for (int i = 0; i < n; ++i)
222 for (int j = 0; j < n; ++j)
223 v[i][j] = (i == j) ? 1.0 : 0.0;
224 for (int sweep = 0; sweep < 50; ++sweep) {
225 double off = 0.0, all = 0.0;
226 for (int i = 0; i < n; ++i)
227 for (int j = 0; j < n; ++j) {
228 all += a[i][j] * a[i][j];
229 if (i != j)
230 off += a[i][j] * a[i][j];
231 }
232 if (std::sqrt(off) <= 1e-15 * std::sqrt(all))
233 break;
234 for (int p = 0; p < n; ++p)
235 for (int q = p + 1; q < n; ++q) {
236 if (a[p][q] == 0.0)
237 continue;
238 const double theta = (a[q][q] - a[p][p]) / (2.0 * a[p][q]);
239 const double t =
240 (theta >= 0.0 ? 1.0 : -1.0) / (std::abs(theta) + std::sqrt(theta * theta + 1.0));
241 const double c = 1.0 / std::sqrt(t * t + 1.0), s = t * c;
242 for (int k = 0; k < n; ++k) { // A ← A·J
243 const double akp = a[k][p], akq = a[k][q];
244 a[k][p] = c * akp - s * akq;
245 a[k][q] = s * akp + c * akq;
246 }
247 for (int k = 0; k < n; ++k) { // A ← Jᵀ·A
248 const double apk = a[p][k], aqk = a[q][k];
249 a[p][k] = c * apk - s * aqk;
250 a[q][k] = s * apk + c * aqk;
251 }
252 for (int k = 0; k < n; ++k) { // V ← V·J
253 const double vkp = v[k][p], vkq = v[k][q];
254 v[k][p] = c * vkp - s * vkq;
255 v[k][q] = s * vkp + c * vkq;
256 }
257 }
258 }
259 for (int i = 0; i < n; ++i)
260 lam[i] = a[i][i];
261}
262
266struct ScaledEig {
267 int n = 0;
268 double dinv[kMaxWindow] = {};
269 double lam[kMaxWindow] = {};
270 Mat v = {};
271 double lamMin = 0.0, lamMax = 0.0;
272};
273
274inline ScaledEig scaledEigen(int n, const Mat& gram) {
275 ScaledEig r;
276 r.n = n;
277 for (int j = 0; j < n; ++j) {
278 const double d = std::sqrt(gram[j][j]);
279 r.dinv[j] = d > 0.0 ? 1.0 / d : 0.0;
280 }
281 Mat a = {};
282 for (int i = 0; i < n; ++i)
283 for (int j = 0; j < n; ++j)
284 a[i][j] = r.dinv[i] * gram[i][j] * r.dinv[j];
285 jacobiEigen(n, a, r.v, r.lam);
286 r.lamMin = r.lamMax = n > 0 ? r.lam[0] : 0.0;
287 for (int i = 1; i < n; ++i) {
288 r.lamMin = std::min(r.lamMin, r.lam[i]);
289 r.lamMax = std::max(r.lamMax, r.lam[i]);
290 }
291 return r;
292}
293
296inline void truncatedSolve(const ScaledEig& se, const double* b, double condMin, double* x) {
297 const int n = se.n;
298 double bt[kMaxWindow], y[kMaxWindow] = {};
299 for (int j = 0; j < n; ++j)
300 bt[j] = se.dinv[j] * b[j];
301 for (int i = 0; i < n; ++i) {
302 if (!(se.lam[i] > 0.0) || se.lam[i] < condMin * se.lamMax)
303 continue;
304 double vb = 0.0;
305 for (int l = 0; l < n; ++l)
306 vb += se.v[l][i] * bt[l];
307 const double w = vb / se.lam[i];
308 for (int k = 0; k < n; ++k)
309 y[k] += se.v[k][i] * w;
310 }
311 for (int k = 0; k < n; ++k)
312 x[k] = se.dinv[k] * y[k];
313}
314
315} // namespace detail::anderson
316
317// ---------------------------------------------------------------------------------------------
318// AndersonCore — type-II Anderson acceleration of the caller's map (design §4).
319//
320// One map evaluation is
321// core.prepare(accelerate); // §4.3 steps 0–1: state check, lazy mix
322// <the caller's step> // §4.3 step 2: reads and writes state.fields in place
323// core.complete(failed); // §4.3 steps 3–7: column, reductions, decisions, coefficients
324// and when the caller's step throws, `if (!core.stepFailed(e.what())) throw;` restores the last
325// map output and disables acceleration if the failing input was a mixed one (else the failure is
326// the plain march's own and the caller rethrows). A caller-side parameter change (flow: Δt, ρ, μ,
327// F) is reported with invalidate() before prepare().
328// ---------------------------------------------------------------------------------------------
330 public:
331 // ---- fixed constants (design §4.1) ----
333 static constexpr int kEngageDecreases = 2;
334 static constexpr double kRestartGrowth = 4.0;
335 static constexpr int kMaxRestarts = 5;
336 static constexpr double kNoiseFloor = 1e-10;
337 static constexpr double kCondMin = 1e-12;
338 // Rev 2: no instability guard — no Ritz estimate, no status "unstable". A Ritz value of the
339 // caller's non-normal map in the velocity-only metric is no stability test; evidence that the
340 // plain map is unstable comes from the caller's plain steps (design "Revision 2", D5).
341
342 enum class Status : int { Active = 0, Disabled = 1 };
343
349 explicit AndersonCore(AndersonState state, int window = 5, double mixing = 1.0)
350 : st_(std::move(state)), m_(window), beta_(mixing) {
351 double failed[2] = {0.0, 0.0}; // {invalid descriptor, failed allocation}, summed over ranks
352 std::string invalid;
353 try {
354 validate();
355 } catch (const std::invalid_argument& e) {
356 invalid = e.what();
357 failed[0] = 1.0;
358 }
359 const std::size_t ns = st_.fields.size();
360 const Index nPad = invalid.empty() ? numPadded() : 0;
361 if (invalid.empty()) {
362 try {
363 auto alloc = [&](std::vector<View<double>>& set, const char* label) {
364 set.resize(ns);
365 for (std::size_t f = 0; f < ns; ++f)
366 set[f] = View<double>(
367 Kokkos::view_alloc(std::string("anderson::") + label, Kokkos::WithoutInitializing),
368 nPad);
369 };
370 alloc(x_, "X");
371 alloc(rPrev_, "Rprev");
372 alloc(gPrev_, "Gprev");
373 for (int s = 0; s < m_; ++s) {
374 alloc(dR_[s], "dR");
375 alloc(dG_[s], "dG");
376 }
377 allocReductionScratch();
378 } catch (const std::exception&) {
379 failed[1] = 1.0;
380 }
381 }
382 sumAll(failed, 2);
383 if (failed[0] > 0.0)
384 throw std::invalid_argument(invalid.empty()
385 ? std::string("AndersonCore: the state descriptor is "
386 "inconsistent on another rank")
387 : invalid);
388 if (failed[1] > 0.0) {
389 char msg[512];
390 std::snprintf(msg, sizeof msg,
391 "AndersonCore: window %d needs %zu bytes ((2m+3)*n_s*8*n_pad, n_s = %zu, "
392 "n_pad = %lld per rank); window 2 needs %zu; run on more ranks or without "
393 "acceleration",
394 m_, memoryBytesFor(m_, ns, nPad), ns, static_cast<long long>(nPad),
395 memoryBytesFor(2, ns, nPad));
396 throw std::runtime_error(msg);
397 }
398 }
399
400 AndersonCore(const AndersonCore&) = delete;
404
406 static std::size_t memoryBytesFor(int window, std::size_t numFields, Index nPad) {
407 return static_cast<std::size_t>(2 * window + 3) * numFields * sizeof(double) *
408 static_cast<std::size_t>(nPad);
409 }
410
411 // ---- one map evaluation -------------------------------------------------------------------
412
417 bool prepare(bool accelerate) {
418 if (prepared_)
419 throw std::logic_error("AndersonCore::prepare: called twice without complete()");
420 prepared_ = true;
421 mixed_ = false;
422 if (status_ != Status::Active)
423 return false;
424 if (havePrev_) {
425 const std::size_t ns = st_.fields.size();
426 for (std::size_t f = 0; f < ns; ++f)
427 detail::anderson::countChanged(st_.fields[f], gPrev_[f], st_.extent, st_.ghost,
428 Kokkos::subview(countDev_, f));
429 Kokkos::deep_copy(ExecSpace(), countHost_, countDev_);
430 ExecSpace().fence("anderson::count"); // the one host synchronisation of prepare()
431 double changed = 0.0;
432 for (std::size_t f = 0; f < ns; ++f)
433 changed += static_cast<double>(countHost_(f));
434 sumAll(&changed, 1);
435 if (changed > 0.0)
436 invalidate();
437 }
438 mixed_ = accelerate && pending_ && status_ == Status::Active;
439 if (mixed_) {
440 for (std::size_t f = 0; f < st_.fields.size(); ++f) {
442 cols.count = mk_;
443 for (int k = 0; k < mk_; ++k) {
444 cols.dG[k] = dG_[order_[k]][f];
445 cols.dR[k] = dR_[order_[k]][f];
446 cols.gamma[k] = gamma_[k];
447 }
448 detail::anderson::mix(st_.fields[f], x_[f], rPrev_[f], cols, beta_);
449 }
450 } else {
451 for (std::size_t f = 0; f < st_.fields.size(); ++f)
452 Kokkos::deep_copy(ExecSpace(), x_[f], st_.fields[f]); // stream-ordered, no fence
453 }
454 pending_ = false;
455 return mixed_;
456 }
457
461 bool stepFailed(const std::string& what) {
462 prepared_ = false;
463 if (!mixed_ || status_ != Status::Active)
464 return false;
465 restoreLastOutput();
466 status_ = Status::Disabled;
467 reasonCode_ = Reason::StepFailed;
468 reason_ = "step failed at an accelerated iterate: " + what;
469 reset();
470 if (st_.comm.rank == 0 && !noticed_) {
471 std::fprintf(stderr, "AndersonCore: %s; acceleration disabled\n", reason_.c_str());
472 noticed_ = true;
473 }
474 return true;
475 }
476
480 void complete(bool pressureSolveFailed = false) {
481 if (!prepared_)
482 throw std::logic_error("AndersonCore::complete: called without prepare()");
483 prepared_ = false;
484 if (status_ != Status::Active)
485 return;
486 namespace da = detail::anderson;
487 const std::size_t ns = st_.fields.size();
488 const IVec<3>& e = st_.extent;
489 const int g = st_.ghost;
490
491 // --- 3. provisional new column (Rprev / Gprev untouched) ---
492 int s = -1;
493 int cols[kMaxWindow]; // the window after acceptance, oldest first, s last
494 int nCols = 0;
495 if (havePrev_) {
496 if (mk_ < m_) {
497 for (int c = 0; c < m_ && s < 0; ++c)
498 if (std::find(order_, order_ + mk_, c) == order_ + mk_)
499 s = c;
500 for (int k = 0; k < mk_; ++k)
501 cols[nCols++] = order_[k];
502 } else {
503 s = order_[0];
504 for (int k = 1; k < mk_; ++k)
505 cols[nCols++] = order_[k];
506 }
507 cols[nCols++] = s;
508 for (std::size_t f = 0; f < ns; ++f)
509 da::diff(st_.fields[f], x_[f], rPrev_[f], gPrev_[f], dR_[s][f], dG_[s][f]);
510 } else {
511 for (std::size_t f = 0; f < ns; ++f)
512 da::residual(st_.fields[f], x_[f]);
513 }
514
515 // --- 4. reductions (§6.1; rev 1: Velocity fields only, no pass 1; rev 2: RR and b only) ---
516 // Every reduction lands in its own device slot (velocity field v, window position k), so the
517 // (nCols + 1)·n_v reductions queue without a host synchronisation and are read back behind ONE
518 // fence. The per-field partials are added on the host in field order.
519 for (std::size_t f = 0, v = 0; f < ns; ++f) {
520 if (st_.roles[f] != AndersonRole::Velocity)
521 continue;
522 da::selfSums(x_[f], st_.fields[f], e, g, Kokkos::subview(selfDev_, v));
523 for (int k = 0; k < nCols; ++k) {
524 const int j = cols[k];
525 da::columnSums(dR_[s][f], dR_[j][f], x_[f], e, g,
526 Kokkos::subview(colDev_, v * kMaxWindow + k));
527 }
528 ++v;
529 }
530 Kokkos::deep_copy(ExecSpace(), selfHost_, selfDev_);
531 if (nCols > 0)
532 Kokkos::deep_copy(ExecSpace(), colHost_, colDev_);
533 ExecSpace().fence("anderson::pass2"); // the one host synchronisation of complete()
534 std::vector<double> packet(static_cast<std::size_t>(2 * nCols + 2), 0.0);
535 for (std::size_t v = 0; v < selfHost_.extent(0); ++v) {
536 const da::SumN<2>& ss = selfHost_(v);
537 packet[0] += ss.v[0];
538 packet[1] += ss.v[1];
539 for (int k = 0; k < nCols; ++k) {
540 const da::SumN<2>& cs = colHost_(v * kMaxWindow + k);
541 for (int q = 0; q < 2; ++q)
542 packet[static_cast<std::size_t>(2 + 2 * k + q)] += cs.v[q];
543 }
544 }
545 sumAll(packet.data(), static_cast<int>(packet.size()));
546
547 // --- 5. host decisions (every rank; rank 0's are broadcast and win) ---
548 const double xx = packet[0], uu = packet[1];
549 const double rho = std::sqrt(xx);
550 const double residualBefore = residual_, rhoMinBefore = rhoMin_;
551 if (xx == 0.0)
552 residual_ = 0.0;
553 else if (uu == 0.0)
554 residual_ = std::numeric_limits<double>::infinity();
555 else
556 residual_ = std::sqrt(xx / uu);
557 if (!std::isfinite(rho) || (mixed_ && pressureSolveFailed)) {
558 if (mixed_) {
559 restoreLastOutput();
560 reasonCode_ = Reason::NonFiniteMixed;
561 } else {
562 reasonCode_ = Reason::NonFinitePlain;
563 }
564 reason_ = reasonText(reasonCode_);
565 status_ = Status::Disabled;
566 reset();
567 havePrev_ = false;
568 return;
569 }
570 if (havePrev_) {
571 for (int k = 0; k < nCols; ++k) {
572 const int j = cols[k];
573 const double* v = &packet[static_cast<std::size_t>(2 + 2 * k)];
574 rr_[s][j] = rr_[j][s] = v[0];
575 b_[j] = v[1];
576 }
577 for (int k = 0; k < nCols; ++k)
578 order_[k] = cols[k];
579 mk_ = nCols;
580 }
581 // Restart (review R2): the output of the rejected mixed iterate is not kept. After the
582 // broadcast the state is restored to the last map output (Gprev), Rprev / Gprev are not
583 // committed, and ρ_min and the reported residual keep their values from before this call, so
584 // the solver never holds a rejected output (and "too many restarts" leaves the last good one).
585 restarted_ = false;
586 if (mixed_ && residual_ >= kNoiseFloor && rho > kRestartGrowth * rhoMin_) {
587 restarted_ = true;
588 reset();
589 ++numRestarts_;
590 residual_ = residualBefore;
591 if (numRestarts_ >= kMaxRestarts) {
592 status_ = Status::Disabled;
593 reasonCode_ = Reason::TooManyRestarts;
594 reason_ = reasonText(reasonCode_);
595 }
596 }
597 decCount_ = (rho < rhoPrev_) ? decCount_ + 1 : 0;
598 if (decCount_ >= kEngageDecreases)
599 engaged_ = true;
600 rhoPrev_ = rho;
601 rhoMin_ = restarted_ ? rhoMinBefore : std::min(rhoMin_, rho);
602
603 // --- 7. next coefficients (rev 2: no Ritz block) ---
604 if (status_ == Status::Active && engaged_ && mk_ >= 1) {
605 // A column with a zero norm (D_j = 0, §4.4 step 1) carries nothing: drop it first. Under the
606 // velocity metric that is a column in which only Carried fields moved.
607 for (int k = 0; k < mk_;) {
608 if (rr_[order_[k]][order_[k]] > 0.0) {
609 ++k;
610 continue;
611 }
612 dropColumn(k);
613 }
614 while (mk_ > 1 && condScaled() < kCondMin)
615 dropColumn(0);
616 if (mk_ >= 1) {
617 solveTruncated();
618 pending_ = true;
619 }
620 }
621 broadcastDecisions();
622
623 // --- 6. commit on the device, keyed on the broadcast restart flag (every rank acts alike) ---
624 if (restarted_) {
625 restoreLastOutput(); // Rprev / Gprev stay those of the last map output
626 } else {
627 std::swap(rPrev_, x_);
628 for (std::size_t f = 0; f < ns; ++f)
629 Kokkos::deep_copy(ExecSpace(), gPrev_[f], st_.fields[f]); // stream-ordered, no fence
630 }
631 havePrev_ = true;
632 }
633
634 // ---- control ------------------------------------------------------------------------------
635
638 void reset() {
639 mk_ = 0;
640 pending_ = engaged_ = false;
641 decCount_ = 0;
642 rhoMin_ = rhoPrev_ = std::numeric_limits<double>::infinity();
643 }
644
647 void invalidate() {
648 reset();
649 havePrev_ = false;
650 ++numResets_;
651 }
652
654 void disable() {
655 if (status_ == Status::Active) {
656 status_ = Status::Disabled;
657 reasonCode_ = Reason::DisabledByCaller;
658 reason_ = reasonText(reasonCode_);
659 }
660 pending_ = false;
661 }
662
663 // ---- status ---------------------------------------------------------------------------------
664
665 Status status() const { return status_; }
667 const char* statusName() const { return status_ == Status::Active ? "active" : "disabled"; }
668 const std::string& reason() const { return reason_; }
671 double residual() const { return residual_; }
672 int numRestarts() const { return numRestarts_; }
673 int numResets() const { return numResets_; }
675 int numColumns() const { return mk_; }
676 int window() const { return m_; }
677 double mixing() const { return beta_; }
678 bool engaged() const { return engaged_; }
680 bool pending() const { return pending_; }
682 std::vector<double> gamma() const { return std::vector<double>(gamma_, gamma_ + mk_); }
684 std::size_t memoryBytes() const {
685 std::size_t b = 0;
686 auto add = [&b](const std::vector<View<double>>& set) {
687 for (const auto& v : set)
688 b += v.span() * sizeof(double);
689 };
690 add(x_);
691 add(rPrev_);
692 add(gPrev_);
693 for (int s = 0; s < m_; ++s) {
694 add(dR_[s]);
695 add(dG_[s]);
696 }
697 return b;
698 }
699 const AndersonState& state() const { return st_; }
700
701 private:
702 enum class Reason : int {
703 None = 0,
704 StepFailed,
705 NonFiniteMixed,
706 NonFinitePlain,
707 TooManyRestarts,
708 DisabledByCaller,
709 };
710
713 struct DecisionPacket {
714 int status;
715 int reason;
716 int mk;
717 int pending;
718 int restarted;
719 int numRestarts;
720 int order[kMaxWindow];
721 double gamma[kMaxWindow];
722 double residual;
723 };
724
725 static std::string reasonText(Reason r) {
726 switch (r) {
727 case Reason::None:
728 return "";
729 case Reason::StepFailed:
730 return "step failed at an accelerated iterate";
731 case Reason::NonFiniteMixed:
732 return "non-finite residual / failed pressure solve at an accelerated iterate";
733 case Reason::NonFinitePlain:
734 return "non-finite residual on a plain step";
735 case Reason::TooManyRestarts:
736 return "too many restarts";
737 case Reason::DisabledByCaller:
738 return "disabled by the caller";
739 }
740 return "";
741 }
742
743 void validate() {
744 const std::size_t ns = st_.fields.size();
745 if (ns == 0)
746 throw std::invalid_argument("AndersonCore: the state has no fields");
747 if (st_.roles.size() != ns)
748 throw std::invalid_argument("AndersonCore: one role per state field is required");
750 throw std::invalid_argument("AndersonCore: window must be in [1, 8]");
751 if (!(beta_ > 0.0 && beta_ <= 1.0))
752 throw std::invalid_argument("AndersonCore: mixing must be in (0, 1]");
753 if (st_.ghost < 0 || st_.extent[0] <= 2 * st_.ghost || st_.extent[1] <= 2 * st_.ghost ||
754 st_.extent[2] <= 2 * st_.ghost)
755 throw std::invalid_argument("AndersonCore: the padded extent has no inner entries");
756 const Index nPad = numPadded();
757 for (std::size_t f = 0; f < ns; ++f) {
758 if (static_cast<Index>(st_.fields[f].extent(0)) != nPad)
759 throw std::invalid_argument("AndersonCore: a state field is not the full padded box");
761 throw std::invalid_argument("AndersonCore: a role must be Velocity or Carried");
762 }
763 }
764
765 Index numPadded() const { return st_.extent[0] * st_.extent[1] * st_.extent[2]; }
766
767 void sumAll(double* data, int count) const {
768 if (st_.comm.sumAll)
769 st_.comm.sumAll(data, count);
770 }
771
772 void restoreLastOutput() {
773 for (std::size_t f = 0; f < st_.fields.size(); ++f)
774 Kokkos::deep_copy(ExecSpace(), st_.fields[f], gPrev_[f]); // stream-ordered, no fence
775 }
776
779 void allocReductionScratch() {
780 namespace da = detail::anderson;
781 std::size_t nv = 0;
782 for (const AndersonRole r : st_.roles)
783 nv += r == AndersonRole::Velocity ? 1 : 0;
784 countDev_ = Kokkos::View<Index*, MemSpace>("anderson::count", st_.fields.size());
785 selfDev_ = Kokkos::View<da::SumN<2>*, MemSpace>("anderson::self_sums", nv);
786 colDev_ = Kokkos::View<da::SumN<2>*, MemSpace>("anderson::column_sums", nv * kMaxWindow);
787 countHost_ = Kokkos::create_mirror_view(countDev_);
788 selfHost_ = Kokkos::create_mirror_view(selfDev_);
789 colHost_ = Kokkos::create_mirror_view(colDev_);
790 }
791
792 void dropColumn(int k) {
793 for (int q = k; q + 1 < mk_; ++q)
794 order_[q] = order_[q + 1];
795 --mk_;
796 }
797
798 void windowBlock(const double (&full)[kMaxWindow][kMaxWindow], detail::anderson::Mat& out) const {
799 for (int a = 0; a < mk_; ++a)
800 for (int c = 0; c < mk_; ++c)
801 out[a][c] = full[order_[a]][order_[c]];
802 }
803
805 double condScaled() const {
807 windowBlock(rr_, a);
808 const auto se = detail::anderson::scaledEigen(mk_, a);
809 return se.lamMin / se.lamMax;
810 }
811
813 void solveTruncated() {
815 windowBlock(rr_, a);
816 double b[kMaxWindow];
817 for (int k = 0; k < mk_; ++k)
818 b[k] = b_[order_[k]];
819 const auto se = detail::anderson::scaledEigen(mk_, a);
821 }
822
823 void broadcastDecisions() {
824 if (!st_.comm.broadcast)
825 return;
826 DecisionPacket p{};
827 p.status = static_cast<int>(status_);
828 p.reason = static_cast<int>(reasonCode_);
829 p.mk = mk_;
830 p.pending = pending_ ? 1 : 0;
831 p.restarted = restarted_ ? 1 : 0;
832 p.numRestarts = numRestarts_;
833 for (int k = 0; k < kMaxWindow; ++k) {
834 p.order[k] = k < mk_ ? order_[k] : -1;
835 p.gamma[k] = k < mk_ ? gamma_[k] : 0.0;
836 }
837 p.residual = residual_;
838 st_.comm.broadcast(&p, sizeof p);
839 if (st_.comm.rank == 0)
840 return;
841 const Reason oldReason = reasonCode_;
842 status_ = static_cast<Status>(p.status);
843 reasonCode_ = static_cast<Reason>(p.reason);
844 mk_ = p.mk;
845 pending_ = p.pending != 0;
846 restarted_ = p.restarted != 0;
847 numRestarts_ = p.numRestarts;
848 for (int k = 0; k < kMaxWindow; ++k) {
849 order_[k] = p.order[k] >= 0 ? p.order[k] : 0;
850 gamma_[k] = p.gamma[k];
851 }
852 residual_ = p.residual;
853 if (reasonCode_ != oldReason)
854 reason_ = reasonText(reasonCode_);
855 }
856
857 AndersonState st_;
858 int m_;
859 double beta_;
860
861 // device (design §4.2): one view per state field per vector
862 std::vector<View<double>> x_, rPrev_, gPrev_;
863 std::vector<View<double>> dR_[kMaxWindow], dG_[kMaxWindow];
864 // per-step reduction slots: COUNT by field; pass 2 by velocity field (· kMaxWindow + position)
865 Kokkos::View<Index*, MemSpace> countDev_;
866 Kokkos::View<detail::anderson::SumN<2>*, MemSpace> selfDev_;
867 Kokkos::View<detail::anderson::SumN<2>*, MemSpace> colDev_;
868 typename Kokkos::View<Index*, MemSpace>::host_mirror_type countHost_;
869 typename Kokkos::View<detail::anderson::SumN<2>*, MemSpace>::host_mirror_type selfHost_;
870 typename Kokkos::View<detail::anderson::SumN<2>*, MemSpace>::host_mirror_type colHost_;
871
872 // host
873 int mk_ = 0;
874 int order_[kMaxWindow] = {}; // slot indices, oldest → newest
875 double rr_[kMaxWindow][kMaxWindow] = {}; // ⟨Δr_i, Δr_j⟩ by slot
876 double b_[kMaxWindow] = {}; // ⟨Δr_j, r_k⟩ by slot
877 double gamma_[kMaxWindow] = {}; // by window position, oldest first
878 bool havePrev_ = false, pending_ = false, engaged_ = false;
879 bool prepared_ = false, mixed_ = false, noticed_ = false;
880 bool restarted_ = false; // this call restarted at a mixed iterate (broadcast with the decisions)
881 int decCount_ = 0, numRestarts_ = 0, numResets_ = 0;
882 double rhoPrev_ = std::numeric_limits<double>::infinity();
883 double rhoMin_ = std::numeric_limits<double>::infinity();
884 Status status_ = Status::Active;
885 Reason reasonCode_ = Reason::None;
886 std::string reason_;
887 double residual_ = std::numeric_limits<double>::infinity();
888};
889
890} // namespace peclet::core::solver
891
892#endif // PECLET_CORE_SOLVER_ANDERSON_HPP
bool stepFailed(const std::string &what)
§4.3 step 2, the caller's step threw.
Definition anderson.hpp:461
static constexpr int kMaxWindow
Definition anderson.hpp:332
void invalidate()
The caller's map changed (flow: its parameter signature) or the state was written: reset() and forget...
Definition anderson.hpp:647
static std::size_t memoryBytesFor(int window, std::size_t numFields, Index nPad)
The §6.3 memory formula: (2m + 3)·n_s·8·n_pad bytes.
Definition anderson.hpp:406
static constexpr double kCondMin
Definition anderson.hpp:337
AndersonCore(const AndersonCore &)=delete
AndersonCore(AndersonCore &&)=default
std::size_t memoryBytes() const
Bytes held by the history, X, Rprev and Gprev: equals memoryBytesFor(window(), n_s,...
Definition anderson.hpp:684
AndersonCore(AndersonState state, int window=5, double mixing=1.0)
Allocates the history (design §6.3: (2m+3)·n_s·8·n_pad bytes).
Definition anderson.hpp:349
void complete(bool pressureSolveFailed=false)
§4.3 steps 3–7, after the caller's step.
Definition anderson.hpp:480
const AndersonState & state() const
Definition anderson.hpp:699
AndersonCore & operator=(const AndersonCore &)=delete
bool prepare(bool accelerate)
§4.3 steps 0–1.
Definition anderson.hpp:417
double residual() const
Relative velocity residual of the last evaluation whose output the state holds (design §3....
Definition anderson.hpp:671
const std::string & reason() const
Definition anderson.hpp:668
void disable()
Stops acceleration for good: prepare/complete become no-ops around a plain step.
Definition anderson.hpp:654
static constexpr double kRestartGrowth
Definition anderson.hpp:334
const char * statusName() const
"active" | "disabled".
Definition anderson.hpp:667
bool pending() const
A mix is pending for the next accelerated call.
Definition anderson.hpp:680
int numColumns() const
Window columns in use (mk).
Definition anderson.hpp:675
static constexpr double kNoiseFloor
Definition anderson.hpp:336
static constexpr int kMaxRestarts
Definition anderson.hpp:335
void reset()
Clears the history and the counters (design §4.2); keeps the previous evaluation, so the next call fo...
Definition anderson.hpp:638
static constexpr int kEngageDecreases
Definition anderson.hpp:333
AndersonCore & operator=(AndersonCore &&)=default
std::vector< double > gamma() const
The pending coefficients γ, oldest column first (numColumns() of them).
Definition anderson.hpp:682
double[kMaxWindow][kMaxWindow] Mat
Definition anderson.hpp:215
Kokkos::View< T, MemSpace > DeviceScalar
A reduction result held on the device: a reduction into it does not synchronise the host,...
Definition anderson.hpp:118
void truncatedSolve(const ScaledEig &se, const double *b, double condMin, double *x)
x = D⁻¹ Σ_i v_i (v_iᵀ D⁻¹ b)/λ_i over the eigenvalues λ_i ≥ condMin·λ_max (and > 0): the truncated so...
Definition anderson.hpp:296
ScaledEig scaledEigen(int n, const Mat &gram)
Definition anderson.hpp:274
void columnSums(View< const double > dRs, View< const double > dRj, View< const double > x, const IVec< 3 > &e, int g, const DeviceScalar< SumN< 2 > > &out)
Pass 2, one window column j against the new column s, over the inner entries of one Velocity field: {...
Definition anderson.hpp:199
Kokkos::MDRangePolicy< ExecSpace, Kokkos::IndexType< Index >, Kokkos::Rank< 3, Kokkos::Iterate::Left, Kokkos::Iterate::Left > > InnerPolicy
Inner-region policy, x (the first index) fastest in tiles and within a tile on every backend (suite C...
Definition anderson.hpp:79
void selfSums(View< const double > x, View< const double > buf, const IVec< 3 > &e, int g, const DeviceScalar< SumN< 2 > > &out)
Pass 2, residual part: {Σ X², Σ buf²} over the inner entries of one Velocity field,...
Definition anderson.hpp:183
void countChanged(View< const double > buf, View< const double > prev, const IVec< 3 > &e, int g, const DeviceScalar< Index > &out)
COUNT: inner entries where buf != prev, into out (no host synchronisation).
Definition anderson.hpp:121
void jacobiEigen(int n, Mat &a, Mat &v, double(&lam)[kMaxWindow])
Cyclic Jacobi eigendecomposition of the symmetric n×n a (overwritten), fixed sweep order p < q row-ma...
Definition anderson.hpp:220
void mix(View< double > buf, View< double > xOut, View< const double > rPrev, const MixColumns &cols, double beta)
MIX (design §4.3 step 1, §4.5): x = buf − ΔG γ − (1−β)(Rprev − ΔR γ); buf = X = x....
Definition anderson.hpp:143
InnerPolicy innerPolicy(const IVec< 3 > &e, int g)
Definition anderson.hpp:81
void residual(View< const double > buf, View< double > x)
R (step 3, first evaluation): X = buf − X. Padded.
Definition anderson.hpp:174
void diff(View< const double > buf, View< double > x, View< const double > rPrev, View< const double > gPrev, View< double > dRs, View< double > dGs)
DIFF (step 3): r = buf − X; dR_s = r − Rprev; dG_s = buf − Gprev; X = r. Padded.
Definition anderson.hpp:161
AndersonRole
What a state field is to the metric (design §3.1–3.2, rev 1: the metric is velocity only).
Definition anderson.hpp:35
@ Carried
mixed, stored and differenced like the others, but not measured (e.g. P)
@ Velocity
measured at unit weight, every inner entry
ExecSpace::memory_space MemSpace
Definition view.hpp:21
PECLET_HD Vec3< Real > add(Vec3< Real > a, Vec3< Real > b)
Definition portable.hpp:58
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.hpp:26
Kokkos::DefaultExecutionSpace ExecSpace
Definition view.hpp:20
std::int64_t Index
Signed index type for grids and particles (supersedes block_decomposer's long int IndxT).
Definition types.hpp:15
static KOKKOS_FORCEINLINE_FUNCTION peclet::core::solver::detail::anderson::SumN< N > sum()
Definition anderson.hpp:106
The collectives of a distributed run.
Definition anderson.hpp:44
std::function< void(void *data, std::size_t bytes)> broadcast
Definition anderson.hpp:46
int rank
this rank in the communicator; rank 0's decisions win (design §6.1)
Definition anderson.hpp:47
std::function< void(double *data, int count)> sumAll
Definition anderson.hpp:45
The state descriptor (design §5.1): exactly what the caller's step reads across steps,...
Definition anderson.hpp:53
std::vector< View< double > > fields
The state buffers, each over the full padded box (extent(0) == extent[0]·extent[1]·extent[2]),...
Definition anderson.hpp:56
AndersonComm comm
Collectives; empty = a single rank.
Definition anderson.hpp:64
IVec< 3 > extent
Padded extents e (inner + 2·ghost per axis); inner entries are ghost ≤ i < e − ghost.
Definition anderson.hpp:60
std::vector< AndersonRole > roles
One role per field.
Definition anderson.hpp:58
The window columns the mix reads, oldest first.
Definition anderson.hpp:135
The Jacobi-scaled eigendecomposition of a Gram block G: A = D⁻¹ G D⁻¹, D = diag(G)^{1/2} (design §4....
Definition anderson.hpp:266
N independent double sums in one reduction.
Definition anderson.hpp:87