IPPL API Reference
Independent Parallel Particle Layer C++ API
Loading...
Searching...
No Matches
Preconditioner.h
1//
2// General pre-conditioners for the pre-conditioned Conjugate Gradient Solver
3// such as Jacobi, Polynomial Newton, Polynomial Chebyshev, Richardson, Gauss-Seidel
4// provides a function operator() that returns the preconditioned field
5//
6
7#ifndef IPPL_PRECONDITIONER_H
8#define IPPL_PRECONDITIONER_H
9
10#include <vector>
11
12#include "Expression/IpplOperations.h" // get the function apply()
13
14// Expands to a lambda that acts as a wrapper for a differential operator
15// fun: the function for which to create the wrapper, such as ippl::laplace
16// type: the argument type, which should match the LHS type for the solver
17//
18// IMPORTANT: takes the field by *reference*, not by value. A by-value
19// signature would copy the Field on every call -- BareField has shared-
20// ownership Kokkos::View members, so the data isn't actually copied, but
21// the embedded HaloCells::haloData_m buffer that the halo exchange grows
22// via Kokkos::realloc only grows on the copy and never propagates back to
23// the original. The result was a fresh cudaMalloc per op_m() call in
24// multi-rank runs (see nsys profile in ~/prof/pif-pr-verify).
25#define IPPL_SOLVER_OPERATOR_WRAPPER(fun, type) \
26 [](type& arg) { \
27 return fun(arg); \
28 }
29
30namespace ippl {
31 template <typename Field>
33 constexpr static unsigned Dim = Field::dim;
34 using mesh_type = typename Field::Mesh_t;
35 using layout_type = typename Field::Layout_t;
36
38 : type_m("Identity") {}
39
40 preconditioner(std::string name)
41 : type_m(name) {}
42
43 virtual ~preconditioner() = default;
44
45 // Apply the preconditioner: result = M^{-1} u. Concrete preconditioners
46 // override this to write into the caller-provided result buffer; this
47 // avoids per-call Field allocations and per-call deep copies in PCG.
48 // The default (identity) preconditioner copies u into result.
49 virtual void operator()(Field& u, Field& result) {
50 Kokkos::deep_copy(result.getView(), u.getView());
51 }
52
53 // Allocate any scratch fields the preconditioner needs. Called once
54 // by the owning solver after the layout is known. Concrete
55 // preconditioners that need scratch override this.
56 virtual void init_fields(Field& /*b*/) {}
57
58 std::string get_type() { return type_m; };
59
60 protected:
61 std::string type_m;
62 };
63
68 template <typename Field, typename InvDiagF>
69 struct jacobi_preconditioner : public preconditioner<Field> {
70 constexpr static unsigned Dim = Field::dim;
71 using mesh_type = typename Field::Mesh_t;
72 using layout_type = typename Field::Layout_t;
73
74 jacobi_preconditioner(InvDiagF&& inverse_diagonal, double w = 1.0)
75 : preconditioner<Field>("jacobi")
76 , w_m(w) {
77 inverse_diagonal_m = std::move(inverse_diagonal);
78 }
79
80 void operator()(Field& u, Field& result) override {
81 // result = w * D^{-1} * u, written element-wise into the caller-
82 // provided result buffer. Two forms of inverse_diagonal_m are
83 // supported, matching the convention used by the other
84 // preconditioners in this file:
85 // - returns a scalar (e.g. uniform-mesh Poisson Laplacian):
86 // result = w * scalar * u
87 // - returns a Field / expression equal to D^{-1} * u
88 // (e.g. matrix-free FEM operators):
89 // result = w * inverse_diagonal_m(u)
90 if constexpr (std::is_same_v<InvDiagF, std::function<double(Field&)>>) {
91 const double scale = w_m * inverse_diagonal_m(u);
92 result = scale * u;
93 } else {
94 result = inverse_diagonal_m(u);
95 result = w_m * result;
96 }
97 }
98
99 protected:
100 InvDiagF inverse_diagonal_m;
101 double w_m; // Damping factor
102 };
103
108 template <typename Field, typename OperatorF>
110 constexpr static unsigned Dim = Field::dim;
111 using mesh_type = typename Field::Mesh_t;
112 using layout_type = typename Field::Layout_t;
113
114 polynomial_newton_preconditioner(OperatorF&& op, double alpha, double beta,
115 unsigned int max_level = 6, double zeta = 1e-3,
116 double* eta = nullptr)
117 : preconditioner<Field>("polynomial_newton")
118 , alpha_m(alpha)
119 , beta_m(beta)
120 , level_m(max_level)
121 , zeta_m(zeta)
122 , eta_m(eta) {
123 op_m = std::move(op);
124 }
125
126 // Memory management is needed because of runtime defined eta_m
128 if (eta_m != nullptr) {
129 delete[] eta_m;
130 eta_m = nullptr;
131 }
132 }
133
135 : preconditioner<Field>("polynomial_newton")
136 , level_m(other.level_m)
137 , alpha_m(other.alpha_m)
138 , beta_m(other.beta_m)
139 , zeta_m(other.zeta_m)
140 , eta_m(other.eta_m) {
141 op_m = std::move(other.op_m);
142 }
143
145 return *this = polynomial_newton_preconditioner(other);
146 }
147
148 // Recursive Newton expansion P_k(u) where:
149 // P_0(u) = eta_0 * u
150 // P_k(u) = eta_k * (2 P_{k-1}(u) - P_{k-1}(A P_{k-1}(u)))
151 // Writes the result of level `level` into `out`. Uses one scratch
152 // field per recursion depth (Pr, PA, PAPr) so that no Field is
153 // allocated per call. The two recursive calls at each level execute
154 // sequentially and may reuse scratch at lower depths because the
155 // first call's lower-depth values have already been folded into
156 // Pr_scratch[level] before the second call begins.
157 void recursive_preconditioner(Field& u, unsigned int level, Field& out) {
158 // Timers accumulated across all levels of the recursion: useful
159 // for "how much of pcond is laplace vs combine vs leaf-assign".
160 // Counts include every call at every level (so the call-count
161 // column equals 2^(level_m+1)-1, not the number of operator()
162 // invocations).
163 if (level == 0) {
164 out = eta_m[0] * u;
165 return;
166 }
167 Field& Pr = Pr_scratch_m[level];
168 Field& PA = PA_scratch_m[level];
169 Field& PAPr = PAPr_scratch_m[level];
170
171 // Inner recursive call owns its own timing; we don't double-count
172 // it here.
173 recursive_preconditioner(u, level - 1, Pr);
174 PA = op_m(Pr);
175 recursive_preconditioner(PA, level - 1, PAPr);
176 out = eta_m[level] * (2.0 * Pr - PAPr);
177 }
178
179 void operator()(Field& u, Field& result) override {
180 // One outer timer per polynomial_newton apply, complementing the
181 // per-call recursion timers. Lets us see how many outer pcond
182 // calls a solve does vs. the time inside each.
183 recursive_preconditioner(u, level_m, result);
184 }
185
186 void init_fields(Field& b) override {
187 // One-shot precomputation of the eta coefficients.
188 if (eta_m == nullptr) {
189 eta_m = new double[level_m + 1];
190 eta_m[0] = 2.0 / ((alpha_m + beta_m) * (1.0 + zeta_m));
191 if (level_m > 0) {
192 eta_m[1] =
193 2.0
194 / (1.0 + 2 * alpha_m * eta_m[0] - alpha_m * eta_m[0] * alpha_m * eta_m[0]);
195 }
196 for (unsigned int i = 2; i < level_m + 1; ++i) {
197 eta_m[i] = 2.0 / (1.0 + 2 * eta_m[i - 1] - eta_m[i - 1] * eta_m[i - 1]);
198 }
199 }
200
201 // First call: allocate one scratch field per recursion depth.
202 // Subsequent calls: refresh layout in place so a load-balance
203 // repartition tracks correctly without freeing/reallocating
204 // when the local extents are unchanged.
205 mesh_type& mesh = b.get_mesh();
206 layout_type& layout = b.getLayout();
207 if (!fields_initialized_m) {
208 Pr_scratch_m.resize(level_m + 1);
209 PA_scratch_m.resize(level_m + 1);
210 PAPr_scratch_m.resize(level_m + 1);
211 for (unsigned int i = 1; i <= level_m; ++i) {
212 Pr_scratch_m[i] = Field(mesh, layout);
213 PA_scratch_m[i] = Field(mesh, layout);
214 PAPr_scratch_m[i] = Field(mesh, layout);
215 }
216 fields_initialized_m = true;
217 } else {
218 for (unsigned int i = 1; i <= level_m; ++i) {
219 Pr_scratch_m[i].updateLayout(layout);
220 PA_scratch_m[i].updateLayout(layout);
221 PAPr_scratch_m[i].updateLayout(layout);
222 }
223 }
224 }
225
226 protected:
227 OperatorF op_m; // Operator to be preconditioned
228 double alpha_m; // Smallest Eigenvalue
229 double beta_m; // Largest Eigenvalue
230 unsigned int level_m; // Number of recursive calls
231 double zeta_m; // smallest (alpha + beta) is multiplied by (1+zeta) to avoid clustering of
232 // Eigenvalues
233 double* eta_m = nullptr; // Size is determined at runtime
234 std::vector<Field> Pr_scratch_m;
235 std::vector<Field> PA_scratch_m;
236 std::vector<Field> PAPr_scratch_m;
237 bool fields_initialized_m = false;
238 };
239
244 template <typename Field, typename OperatorF>
246 constexpr static unsigned Dim = Field::dim;
247 using mesh_type = typename Field::Mesh_t;
248 using layout_type = typename Field::Layout_t;
249
250 polynomial_chebyshev_preconditioner(OperatorF&& op, double alpha, double beta,
251 unsigned int degree = 63, double zeta = 1e-3)
252 : preconditioner<Field>("polynomial_chebyshev")
253 , alpha_m(alpha)
254 , beta_m(beta)
255 , degree_m(degree)
256 , zeta_m(zeta)
257 , rho_m(nullptr) {
258 op_m = std::move(op);
259 }
260
261 // Memory management is needed because of runtime defined rho_m
263 if (rho_m != nullptr) {
264 delete[] rho_m;
265 rho_m = nullptr;
266 }
267 }
268
270 : preconditioner<Field>("polynomial_chebyshev")
271 , degree_m(other.degree_m)
272 , theta_m(other.theta_m)
273 , sigma_m(other.sigma_m)
274 , delta_m(other.delta_m)
275 , alpha_m(other.delta_m)
276 , beta_m(other.delta_m)
277 , zeta_m(other.zeta_m)
278 , rho_m(other.rho_m) {
279 op_m = std::move(other.op_m);
280 }
281
284 return *this = polynomial_chebyshev_preconditioner(other);
285 }
286
287 void operator()(Field& r, Field& result) override {
288 // x_m, x_old_m, A_m, z_m are pre-allocated scratch (init_fields).
289 // Coefficients in rho_m are also computed once.
290 x_old_m = r / theta_m;
291 A_m = op_m(r);
292 x_m = 2.0 * rho_m[1] / delta_m * (2.0 * r - A_m / theta_m);
293 if (degree_m == 0) {
294 // result = x_old
295 Kokkos::deep_copy(result.getView(), x_old_m.getView());
296 return;
297 }
298
299 if (degree_m == 1) {
300 // result = x
301 Kokkos::deep_copy(result.getView(), x_m.getView());
302 return;
303 }
304 for (unsigned int i = 2; i < degree_m + 1; ++i) {
305 A_m = op_m(x_m);
306 z_m = 2.0 / delta_m * (r - A_m);
307 // Write the new x value into result (the caller's buffer);
308 // x_old gets a deep copy of the previous x.
309 result = rho_m[i] * (2 * sigma_m * x_m - rho_m[i - 1] * x_old_m + z_m);
310 Kokkos::deep_copy(x_old_m.getView(), x_m.getView());
311 Kokkos::deep_copy(x_m.getView(), result.getView());
312 }
313 }
314
315 void init_fields(Field& b) override {
316 // One-shot precomputation of the rho coefficients.
317 if (rho_m == nullptr) {
318 theta_m = (beta_m + alpha_m) / 2.0 * (1.0 + zeta_m);
319 delta_m = (beta_m - alpha_m) / 2.0;
320 sigma_m = theta_m / delta_m;
321
322 rho_m = new double[degree_m + 1];
323 rho_m[0] = 1.0 / sigma_m;
324 for (unsigned int i = 1; i < degree_m + 1; ++i) {
325 rho_m[i] = 1.0 / (2.0 * sigma_m - rho_m[i - 1]);
326 }
327 }
328 mesh_type& mesh = b.get_mesh();
329 layout_type& layout = b.getLayout();
330 // First call allocates; subsequent calls refresh the layout so a
331 // repartition is tracked without throwing the storage away.
332 if (!fields_initialized_m) {
333 x_m = Field(mesh, layout);
334 x_old_m = Field(mesh, layout);
335 A_m = Field(mesh, layout);
336 z_m = Field(mesh, layout);
337 fields_initialized_m = true;
338 } else {
339 x_m.updateLayout(layout);
340 x_old_m.updateLayout(layout);
341 A_m.updateLayout(layout);
342 z_m.updateLayout(layout);
343 }
344 }
345
346 protected:
347 OperatorF op_m;
348 double alpha_m;
349 double beta_m;
350 double delta_m;
351 double theta_m;
352 double sigma_m;
353 unsigned degree_m;
354 double zeta_m;
355 double* rho_m = nullptr; // Size is determined at runtime
356 Field x_m;
357 Field x_old_m;
358 Field A_m;
359 Field z_m;
360 bool fields_initialized_m = false;
361 };
362
366 template <typename Field, typename UpperAndLowerF, typename InvDiagF>
368 constexpr static unsigned Dim = Field::dim;
369 using mesh_type = typename Field::Mesh_t;
370 using layout_type = typename Field::Layout_t;
371
372 richardson_preconditioner(UpperAndLowerF&& upper_and_lower, InvDiagF&& inverse_diagonal,
373 unsigned innerloops = 5)
374 : preconditioner<Field>("Richardson")
375 , innerloops_m(innerloops) {
376 upper_and_lower_m = std::move(upper_and_lower);
377 inverse_diagonal_m = std::move(inverse_diagonal);
378 }
379
380 void operator()(Field& r, Field& result) override {
381 // Richardson iteration in-place on the caller-provided result
382 // buffer. ULg_m stays as a member scratch; the inner deep copies
383 // remain because the (upper, diag, inverse, lower) operators may
384 // return Field-valued expressions that alias their input.
385
386 result = 0;
387 for (unsigned int j = 0; j < innerloops_m; ++j) {
388 ULg_m = upper_and_lower_m(result);
389 ULg_m = ULg_m.deepCopy();
390 result = r - ULg_m;
391
392 // The inverse diagonal is applied to the
393 // vector itself to return the result usually.
394 // However, the operator for FEM already
395 // returns the result of inv_diag * itself
396 // due to the matrix-free evaluation.
397 // Therefore, we need this if to differentiate
398 // the two cases.
399 if constexpr (std::is_same_v<InvDiagF, std::function<double(Field&)>>) {
400 result = inverse_diagonal_m(result) * result;
401 } else {
402 result = inverse_diagonal_m(result).deepCopy();
403 }
404 }
405 }
406
407 void init_fields(Field& b) override {
408 layout_type& layout = b.getLayout();
409 mesh_type& mesh = b.get_mesh();
410 if (!fields_initialized_m) {
411 ULg_m = Field(mesh, layout);
412 fields_initialized_m = true;
413 } else {
414 ULg_m.updateLayout(layout);
415 }
416 }
417
418 protected:
419 UpperAndLowerF upper_and_lower_m;
420 InvDiagF inverse_diagonal_m;
421 unsigned innerloops_m;
422 Field ULg_m;
423 bool fields_initialized_m = false;
424 };
425
433 template <typename Field, typename OperatorF, typename InvDiagF>
435 constexpr static unsigned Dim = Field::dim;
436 using mesh_type = typename Field::Mesh_t;
437 using layout_type = typename Field::Layout_t;
438
439 richardson_preconditioner_alt(OperatorF&& op, InvDiagF&& inverse_diagonal,
440 unsigned innerloops = 5)
441 : preconditioner<Field>("Richardson_alt")
442 , innerloops_m(innerloops) {
443 op_m = std::move(op);
444 inverse_diagonal_m = std::move(inverse_diagonal);
445 }
446
447 void operator()(Field& r, Field& result) override {
448 // result holds the running iterate; Ag_m and g_old_m are scratch
449 // members. The inner deep copies remain because the operators
450 // (op_m, inverse_diagonal_m) may return Field-valued expressions
451 // that alias their input.
452
453 result = 0;
454 g_old_m = 0;
455
456 for (unsigned int j = 0; j < innerloops_m; ++j) {
457 Ag_m = op_m(result);
458 Ag_m = Ag_m.deepCopy();
459 result = r - Ag_m;
460
461 // The inverse diagonal is applied to the
462 // vector itself to return the result usually.
463 // However, the operator for FEM already
464 // returns the result of inv_diag * itself
465 // due to the matrix-free evaluation.
466 // Therefore, we need this if to differentiate
467 // the two cases.
468 if constexpr (std::is_same_v<InvDiagF, std::function<double(Field&)>>) {
469 result = g_old_m + inverse_diagonal_m(result) * result;
470 } else {
471 result = g_old_m + inverse_diagonal_m(result);
472 }
473 Kokkos::deep_copy(g_old_m.getView(), result.getView());
474 }
475 }
476
477 void init_fields(Field& b) override {
478 layout_type& layout = b.getLayout();
479 mesh_type& mesh = b.get_mesh();
480 if (!fields_initialized_m) {
481 Ag_m = Field(mesh, layout);
482 g_old_m = Field(mesh, layout);
483 fields_initialized_m = true;
484 } else {
485 Ag_m.updateLayout(layout);
486 g_old_m.updateLayout(layout);
487 }
488 }
489
490 protected:
491 OperatorF op_m;
492 InvDiagF inverse_diagonal_m;
493 unsigned innerloops_m;
494 Field Ag_m;
495 Field g_old_m;
496 bool fields_initialized_m = false;
497 };
498
502 template <typename Field, typename LowerF, typename UpperF, typename InvDiagF>
503 struct gs_preconditioner : public preconditioner<Field> {
504 constexpr static unsigned Dim = Field::dim;
505 using mesh_type = typename Field::Mesh_t;
506 using layout_type = typename Field::Layout_t;
507
508 gs_preconditioner(LowerF&& lower, UpperF&& upper, InvDiagF&& inverse_diagonal,
509 unsigned innerloops, unsigned outerloops)
510 : preconditioner<Field>("Gauss-Seidel")
511 , innerloops_m(innerloops)
512 , outerloops_m(outerloops) {
513 lower_m = std::move(lower);
514 upper_m = std::move(upper);
515 inverse_diagonal_m = std::move(inverse_diagonal);
516 }
517
518 void operator()(Field& b, Field& result) override {
519 // The running iterate lives in result; UL_m and r_m are scratch
520 // members. The inner deep copies remain because the (upper, lower,
521 // inverse) operators may return Field-valued expressions that
522 // alias their input.
523
524 result = 0; // Initial guess
525
526 for (unsigned int k = 0; k < outerloops_m; ++k) {
527 UL_m = upper_m(result);
528 UL_m = UL_m.deepCopy();
529 r_m = b - UL_m;
530 for (unsigned int j = 0; j < innerloops_m; ++j) {
531 UL_m = lower_m(result);
532 UL_m = UL_m.deepCopy();
533 result = r_m - UL_m;
534 if constexpr (std::is_same_v<InvDiagF, std::function<double(Field&)>>) {
535 result = inverse_diagonal_m(result) * result;
536 } else {
537 result = inverse_diagonal_m(result).deepCopy();
538 }
539 // The inverse diagonal is applied to the
540 // vector itself to return the result usually.
541 // However, the operator for FEM already
542 // returns the result of inv_diag * itself
543 // due to the matrix-free evaluation.
544 // Therefore, we need this if to differentiate
545 // the two cases.
546 }
547 UL_m = lower_m(result);
548 UL_m = UL_m.deepCopy();
549 r_m = b - UL_m;
550 for (unsigned int j = 0; j < innerloops_m; ++j) {
551 UL_m = upper_m(result);
552 UL_m = UL_m.deepCopy();
553 result = r_m - UL_m;
554 if constexpr (std::is_same_v<InvDiagF, std::function<double(Field&)>>) {
555 result = inverse_diagonal_m(result) * result;
556 } else {
557 result = inverse_diagonal_m(result).deepCopy();
558 }
559 // The inverse diagonal is applied to the
560 // vector itself to return the result usually.
561 // However, the operator for FEM already
562 // returns the result of inv_diag * itself
563 // due to the matrix-free evaluation.
564 // Therefore, we need this if to differentiate
565 // the two cases.
566 }
567 }
568 }
569
570 void init_fields(Field& b) override {
571 layout_type& layout = b.getLayout();
572 mesh_type& mesh = b.get_mesh();
573 if (!fields_initialized_m) {
574 UL_m = Field(mesh, layout);
575 r_m = Field(mesh, layout);
576 fields_initialized_m = true;
577 } else {
578 UL_m.updateLayout(layout);
579 r_m.updateLayout(layout);
580 }
581 }
582
583 protected:
584 LowerF lower_m;
585 UpperF upper_m;
586 InvDiagF inverse_diagonal_m;
587 unsigned innerloops_m;
588 unsigned outerloops_m;
589 Field UL_m;
590 Field r_m;
591 bool fields_initialized_m = false;
592 };
593
597 template <typename Field, typename LowerF, typename UpperF, typename InvDiagF, typename DiagF>
598 struct ssor_preconditioner : public preconditioner<Field> {
599 constexpr static unsigned Dim = Field::dim;
600 using mesh_type = typename Field::Mesh_t;
601 using layout_type = typename Field::Layout_t;
602
603 ssor_preconditioner(LowerF&& lower, UpperF&& upper, InvDiagF&& inverse_diagonal,
604 DiagF&& diagonal, unsigned innerloops, unsigned outerloops,
605 double omega)
606 : preconditioner<Field>("ssor")
607 , innerloops_m(innerloops)
608 , outerloops_m(outerloops)
609 , omega_m(omega) {
610 lower_m = std::move(lower);
611 upper_m = std::move(upper);
612 inverse_diagonal_m = std::move(inverse_diagonal);
613 diagonal_m = std::move(diagonal);
614 }
615
616 void operator()(Field& b, Field& result) override {
617 // In the FEM solver, which uses the preconditioner,
618 // we re-use a resultField to avoid allocating new
619 // memory at every iteration.
620 // In order for the operator calls to not rewrite
621 // on this same field over and over when calling
622 // the operators (upper, diag, inverse, lower, etc)
623 // we need deep copies to the preconditioner fields.
624
625 // The inverse diagonal is applied to the
626 // vector itself to return the result usually.
627 // However, the operator for FEM already
628 // returns the result of inv_diag * itself
629 // due to the matrix-free evaluation.
630 // Therefore, we need this if to differentiate
631 // the two cases.
632
633 double D;
634
635 // In order for the operator calls to not rewrite on this same field
636 // over and over when calling the operators (upper, diag, inverse,
637 // lower, etc) we need deep copies to the preconditioner fields."
638
639 result = 0; // Initial guess
640
641 for (unsigned int k = 0; k < outerloops_m; ++k) {
642 if constexpr (std::is_same_v<InvDiagF, std::function<double(Field&)>>) {
643 UL_m = upper_m(result);
644 D = diagonal_m(result);
645 r_m = omega_m * (b - UL_m) + (1.0 - omega_m) * D * result;
646
647 for (unsigned int j = 0; j < innerloops_m; ++j) {
648 UL_m = lower_m(result);
649 result = r_m - omega_m * UL_m;
650 result = inverse_diagonal_m(result) * result;
651 }
652 UL_m = lower_m(result);
653 D = diagonal_m(result);
654 r_m = omega_m * (b - UL_m) + (1.0 - omega_m) * D * result;
655 for (unsigned int j = 0; j < innerloops_m; ++j) {
656 UL_m = upper_m(result);
657 result = r_m - omega_m * UL_m;
658 result = inverse_diagonal_m(result) * result;
659 }
660 } else {
661 UL_m = upper_m(result).deepCopy();
662 r_m = omega_m * (b - UL_m) + (1.0 - omega_m) * diagonal_m(result);
663
664 for (unsigned int j = 0; j < innerloops_m; ++j) {
665 UL_m = lower_m(result).deepCopy();
666 result = r_m - omega_m * UL_m;
667 result = inverse_diagonal_m(result).deepCopy();
668 }
669 UL_m = lower_m(result).deepCopy();
670 r_m = omega_m * (b - UL_m) + (1.0 - omega_m) * diagonal_m(result);
671 for (unsigned int j = 0; j < innerloops_m; ++j) {
672 UL_m = upper_m(result).deepCopy();
673 result = r_m - omega_m * UL_m;
674 result = inverse_diagonal_m(result).deepCopy();
675 }
676 }
677 }
678 }
679
680 void init_fields(Field& b) override {
681 layout_type& layout = b.getLayout();
682 mesh_type& mesh = b.get_mesh();
683 if (!fields_initialized_m) {
684 UL_m = Field(mesh, layout);
685 r_m = Field(mesh, layout);
686 fields_initialized_m = true;
687 } else {
688 UL_m.updateLayout(layout);
689 r_m.updateLayout(layout);
690 }
691 }
692
693 protected:
694 LowerF lower_m;
695 UpperF upper_m;
696 InvDiagF inverse_diagonal_m;
697 DiagF diagonal_m;
698 unsigned innerloops_m;
699 unsigned outerloops_m;
700 double omega_m;
701 Field UL_m;
702 Field r_m;
703 bool fields_initialized_m = false;
704 };
705
713 template <typename Field, typename Functor>
714 double powermethod(Functor&& f, Field& x_0, unsigned int max_iter = 5000, double tol = 1e-3) {
715 unsigned int i = 0;
716 using mesh_type = typename Field::Mesh_t;
717 using layout_type = typename Field::Layout_t;
718 mesh_type& mesh = x_0.get_mesh();
719 layout_type& layout = x_0.getLayout();
720 Field x_new(mesh, layout);
721 Field x_diff(mesh, layout);
722 double error = 1.0;
723 double lambda = 1.0;
724 while (error > tol && i < max_iter) {
725 x_new = f(x_0);
726 lambda = norm(x_new);
727 x_diff = x_new - lambda * x_0;
728 error = norm(x_diff);
729 x_new = x_new / lambda;
730 x_0 = x_new.deepCopy();
731 ++i;
732 }
733 if (i == max_iter) {
734 std::cerr << "Powermethod did not converge, lambda_max : " << lambda
735 << ", error : " << error << std::endl;
736 }
737 return lambda;
738 }
739
748 template <typename Field, typename Functor>
749 double adapted_powermethod(Functor&& f, Field& x_0, double lambda_max,
750 unsigned int max_iter = 5000, double tol = 1e-3) {
751 unsigned int i = 0;
752 using mesh_type = typename Field::Mesh_t;
753 using layout_type = typename Field::Layout_t;
754 mesh_type& mesh = x_0.get_mesh();
755 layout_type& layout = x_0.getLayout();
756 Field x_new(mesh, layout);
757 Field x_diff(mesh, layout);
758 double error = 1.0;
759 double lambda = 1.0;
760 while (error > tol && i < max_iter) {
761 x_new = f(x_0);
762 x_new = x_new - lambda_max * x_0;
763 lambda = -norm(x_new); // We know that lambda < 0;
764 x_diff = x_new - lambda * x_0;
765 error = norm(x_diff);
766 x_new = x_new / -lambda;
767 x_0 = x_new.deepCopy();
768 ++i;
769 }
770 lambda = lambda + lambda_max;
771 if (i == max_iter) {
772 std::cerr << "Powermethod did not converge, lambda_min : " << lambda
773 << ", error : " << error << std::endl;
774 }
775 return lambda;
776 }
777
778 /*
779 // Use the powermethod to compute the eigenvalues if no analytical solution is known
780 beta = powermethod(std::move(op_m), x_0);
781 // Trick for computing the smallest Eigenvalue of SPD Matrix
782 alpha = adapted_powermethod(std::move(op_m), x_0, beta);
783 */
784
785} // namespace ippl
786
787#endif // IPPL_PRECONDITIONER_H
Definition FieldLayout.h:166
Definition Field.h:18
Field deepCopy() const
Definition Field.hpp:26
Definition Archive.h:20
double powermethod(Functor &&f, Field &x_0, unsigned int max_iter=5000, double tol=1e-3)
Definition Preconditioner.h:714
double adapted_powermethod(Functor &&f, Field &x_0, double lambda_max, unsigned int max_iter=5000, double tol=1e-3)
Definition Preconditioner.h:749
Definition Preconditioner.h:503
Definition Preconditioner.h:69
Definition Preconditioner.h:245
Definition Preconditioner.h:109
Definition Preconditioner.h:32
Definition Preconditioner.h:434
Definition Preconditioner.h:367
Definition Preconditioner.h:598