Line data Source code
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 "Expression/IpplOperations.h" // get the function apply()
11 :
12 : // Expands to a lambda that acts as a wrapper for a differential operator
13 : // fun: the function for which to create the wrapper, such as ippl::laplace
14 : // type: the argument type, which should match the LHS type for the solver
15 : #define IPPL_SOLVER_OPERATOR_WRAPPER(fun, type) \
16 : [](type arg) { \
17 : return fun(arg); \
18 : }
19 :
20 : namespace ippl {
21 : template <typename Field>
22 : struct preconditioner {
23 : constexpr static unsigned Dim = Field::dim;
24 : using mesh_type = typename Field::Mesh_t;
25 : using layout_type = typename Field::Layout_t;
26 :
27 0 : preconditioner()
28 0 : : type_m("Identity") {}
29 :
30 0 : preconditioner(std::string name)
31 0 : : type_m(name) {}
32 :
33 0 : virtual ~preconditioner() = default;
34 :
35 : // Placeholder for the function operator, actually implemented in the derived classes
36 0 : virtual Field operator()(Field& u) {
37 0 : Field res = u.deepCopy();
38 0 : return res;
39 : }
40 :
41 : // Placeholder for setting additional fields, actually implemented in the derived classes
42 0 : virtual void init_fields(Field& b) {
43 0 : Field res = b.deepCopy();
44 0 : return;
45 0 : }
46 :
47 : std::string get_type() { return type_m; };
48 :
49 : protected:
50 : std::string type_m;
51 : };
52 :
53 : /*!
54 : * Jacobi preconditioner
55 : * M = w*diag{A} // w is a damping factor
56 : */
57 : template <typename Field, typename InvDiagF>
58 : struct jacobi_preconditioner : public preconditioner<Field> {
59 : constexpr static unsigned Dim = Field::dim;
60 : using mesh_type = typename Field::Mesh_t;
61 : using layout_type = typename Field::Layout_t;
62 :
63 0 : jacobi_preconditioner(InvDiagF&& inverse_diagonal, double w = 1.0)
64 : : preconditioner<Field>("jacobi")
65 0 : , w_m(w) {
66 0 : inverse_diagonal_m = std::move(inverse_diagonal);
67 0 : }
68 :
69 0 : Field operator()(Field& u) override {
70 0 : mesh_type& mesh = u.get_mesh();
71 0 : layout_type& layout = u.getLayout();
72 0 : Field res(mesh, layout);
73 :
74 0 : res = inverse_diagonal_m(u);
75 0 : res = w_m * res;
76 0 : return res;
77 0 : }
78 :
79 : protected:
80 : InvDiagF inverse_diagonal_m;
81 : double w_m; // Damping factor
82 : };
83 :
84 : /*!
85 : * Polynomial Newton Preconditioner
86 : * Computes iteratively approximations for A^-1
87 : */
88 : template <typename Field, typename OperatorF>
89 : struct polynomial_newton_preconditioner : public preconditioner<Field> {
90 : constexpr static unsigned Dim = Field::dim;
91 : using mesh_type = typename Field::Mesh_t;
92 : using layout_type = typename Field::Layout_t;
93 :
94 0 : polynomial_newton_preconditioner(OperatorF&& op, double alpha, double beta,
95 : unsigned int max_level = 6, double zeta = 1e-3,
96 : double* eta = nullptr)
97 : : preconditioner<Field>("polynomial_newton")
98 0 : , alpha_m(alpha)
99 0 : , beta_m(beta)
100 0 : , level_m(max_level)
101 0 : , zeta_m(zeta)
102 0 : , eta_m(eta) {
103 0 : op_m = std::move(op);
104 0 : }
105 :
106 : // Memory management is needed because of runtime defined eta_m
107 0 : ~polynomial_newton_preconditioner() {
108 0 : if (eta_m != nullptr) {
109 0 : delete[] eta_m;
110 0 : eta_m = nullptr;
111 : }
112 0 : }
113 :
114 : polynomial_newton_preconditioner(const polynomial_newton_preconditioner& other)
115 : : preconditioner<Field>("polynomial_newton")
116 : , level_m(other.level_m)
117 : , alpha_m(other.alpha_m)
118 : , beta_m(other.beta_m)
119 : , zeta_m(other.zeta_m)
120 : , eta_m(other.eta_m) {
121 : op_m = std::move(other.op_m);
122 : }
123 :
124 : polynomial_newton_preconditioner& operator=(const polynomial_newton_preconditioner& other) {
125 : return *this = polynomial_newton_preconditioner(other);
126 : }
127 :
128 0 : Field recursive_preconditioner(Field& u, unsigned int level) {
129 0 : mesh_type& mesh = u.get_mesh();
130 0 : layout_type& layout = u.getLayout();
131 : // Define etas if not defined yet
132 0 : if (eta_m == nullptr) {
133 : // Precompute the etas for later use
134 0 : eta_m = new double[level_m + 1];
135 0 : eta_m[0] = 2.0 / ((alpha_m + beta_m) * (1.0 + zeta_m));
136 0 : if (level_m > 0) {
137 0 : eta_m[1] =
138 : 2.0
139 0 : / (1.0 + 2 * alpha_m * eta_m[0] - alpha_m * eta_m[0] * alpha_m * eta_m[0]);
140 : }
141 0 : for (unsigned int i = 2; i < level_m + 1; ++i) {
142 0 : eta_m[i] = 2.0 / (1.0 + 2 * eta_m[i - 1] - eta_m[i - 1] * eta_m[i - 1]);
143 : }
144 : }
145 :
146 0 : Field res(mesh, layout);
147 : // Base case
148 0 : if (level == 0) {
149 0 : res = eta_m[0] * u;
150 0 : return res;
151 : }
152 : // Recursive case
153 0 : Field PAPr(mesh, layout);
154 0 : Field Pr(mesh, layout);
155 :
156 0 : Pr = recursive_preconditioner(u, level - 1);
157 0 : PAPr = op_m(Pr);
158 0 : PAPr = recursive_preconditioner(PAPr, level - 1);
159 0 : res = eta_m[level] * (2.0 * Pr - PAPr);
160 0 : return res;
161 0 : }
162 :
163 0 : Field operator()(Field& u) override { return recursive_preconditioner(u, level_m); }
164 :
165 : protected:
166 : OperatorF op_m; // Operator to be preconditioned
167 : double alpha_m; // Smallest Eigenvalue
168 : double beta_m; // Largest Eigenvalue
169 : unsigned int level_m; // Number of recursive calls
170 : double zeta_m; // smallest (alpha + beta) is multiplied by (1+zeta) to avoid clustering of
171 : // Eigenvalues
172 : double* eta_m = nullptr; // Size is determined at runtime
173 : };
174 :
175 : /*!
176 : * Polynomial Chebyshev Preconditioner
177 : * Computes iteratively approximations for A^-1
178 : */
179 : template <typename Field, typename OperatorF>
180 : struct polynomial_chebyshev_preconditioner : public preconditioner<Field> {
181 : constexpr static unsigned Dim = Field::dim;
182 : using mesh_type = typename Field::Mesh_t;
183 : using layout_type = typename Field::Layout_t;
184 :
185 0 : polynomial_chebyshev_preconditioner(OperatorF&& op, double alpha, double beta,
186 : unsigned int degree = 63, double zeta = 1e-3)
187 : : preconditioner<Field>("polynomial_chebyshev")
188 0 : , alpha_m(alpha)
189 0 : , beta_m(beta)
190 0 : , degree_m(degree)
191 0 : , zeta_m(zeta)
192 0 : , rho_m(nullptr) {
193 0 : op_m = std::move(op);
194 0 : }
195 :
196 : // Memory management is needed because of runtime defined rho_m
197 0 : ~polynomial_chebyshev_preconditioner() {
198 0 : if (rho_m != nullptr) {
199 0 : delete[] rho_m;
200 0 : rho_m = nullptr;
201 : }
202 0 : }
203 :
204 : polynomial_chebyshev_preconditioner(const polynomial_chebyshev_preconditioner& other)
205 : : preconditioner<Field>("polynomial_chebyshev")
206 : , degree_m(other.degree_m)
207 : , theta_m(other.theta_m)
208 : , sigma_m(other.sigma_m)
209 : , delta_m(other.delta_m)
210 : , alpha_m(other.delta_m)
211 : , beta_m(other.delta_m)
212 : , zeta_m(other.zeta_m)
213 : , rho_m(other.rho_m) {
214 : op_m = std::move(other.op_m);
215 : }
216 :
217 : polynomial_chebyshev_preconditioner& operator=(
218 : const polynomial_chebyshev_preconditioner& other) {
219 : return *this = polynomial_chebyshev_preconditioner(other);
220 : }
221 :
222 0 : Field operator()(Field& r) override {
223 0 : mesh_type& mesh = r.get_mesh();
224 0 : layout_type& layout = r.getLayout();
225 :
226 0 : Field res(mesh, layout);
227 0 : Field x(mesh, layout);
228 0 : Field x_old(mesh, layout);
229 0 : Field A(mesh, layout);
230 0 : Field z(mesh, layout);
231 :
232 : // Precompute the coefficients if not done yet
233 0 : if (rho_m == nullptr) {
234 : // Start precomputing the coefficients
235 0 : theta_m = (beta_m + alpha_m) / 2.0 * (1.0 + zeta_m);
236 0 : delta_m = (beta_m - alpha_m) / 2.0;
237 0 : sigma_m = theta_m / delta_m;
238 :
239 0 : rho_m = new double[degree_m + 1];
240 0 : rho_m[0] = 1.0 / sigma_m;
241 0 : for (unsigned int i = 1; i < degree_m + 1; ++i) {
242 0 : rho_m[i] = 1.0 / (2.0 * sigma_m - rho_m[i - 1]);
243 : }
244 : } // End of precomputing the coefficients
245 :
246 0 : res = r.deepCopy();
247 :
248 0 : x_old = r / theta_m;
249 0 : A = op_m(r);
250 0 : x = 2.0 * rho_m[1] / delta_m * (2.0 * r - A / theta_m);
251 :
252 0 : if (degree_m == 0) {
253 0 : return x_old;
254 : }
255 :
256 0 : if (degree_m == 1) {
257 0 : return x;
258 : }
259 0 : for (unsigned int i = 2; i < degree_m + 1; ++i) {
260 0 : A = op_m(x);
261 0 : z = 2.0 / delta_m * (r - A);
262 0 : res = rho_m[i] * (2 * sigma_m * x - rho_m[i - 1] * x_old + z);
263 0 : x_old = x.deepCopy();
264 0 : x = res.deepCopy();
265 : }
266 0 : return res;
267 0 : }
268 :
269 : protected:
270 : OperatorF op_m;
271 : double alpha_m;
272 : double beta_m;
273 : double delta_m;
274 : double theta_m;
275 : double sigma_m;
276 : unsigned degree_m;
277 : double zeta_m;
278 : double* rho_m = nullptr; // Size is determined at runtime
279 : };
280 :
281 : /*!
282 : * Richardson preconditioner
283 : */
284 : template <typename Field, typename UpperAndLowerF, typename InvDiagF>
285 : struct richardson_preconditioner : public preconditioner<Field> {
286 : constexpr static unsigned Dim = Field::dim;
287 : using mesh_type = typename Field::Mesh_t;
288 : using layout_type = typename Field::Layout_t;
289 :
290 0 : richardson_preconditioner(UpperAndLowerF&& upper_and_lower, InvDiagF&& inverse_diagonal,
291 : unsigned innerloops = 5)
292 : : preconditioner<Field>("Richardson")
293 0 : , innerloops_m(innerloops) {
294 0 : upper_and_lower_m = std::move(upper_and_lower);
295 0 : inverse_diagonal_m = std::move(inverse_diagonal);
296 0 : }
297 :
298 0 : Field operator()(Field& r) override {
299 0 : mesh_type& mesh = r.get_mesh();
300 0 : layout_type& layout = r.getLayout();
301 0 : Field g(mesh, layout);
302 :
303 0 : g = 0;
304 0 : for (unsigned int j = 0; j < innerloops_m; ++j) {
305 0 : ULg_m = upper_and_lower_m(g);
306 0 : g = r - ULg_m;
307 0 : g = inverse_diagonal_m(g) * g;
308 : }
309 0 : return g;
310 0 : }
311 :
312 0 : void init_fields(Field& b) override {
313 0 : layout_type& layout = b.getLayout();
314 0 : mesh_type& mesh = b.get_mesh();
315 :
316 0 : ULg_m = Field(mesh, layout);
317 0 : }
318 :
319 : protected:
320 : UpperAndLowerF upper_and_lower_m;
321 : InvDiagF inverse_diagonal_m;
322 : unsigned innerloops_m;
323 : Field ULg_m;
324 : };
325 :
326 : /*!
327 : * 2-step Gauss-Seidel preconditioner
328 : */
329 : template <typename Field, typename LowerF, typename UpperF, typename InvDiagF>
330 : struct gs_preconditioner : public preconditioner<Field> {
331 : constexpr static unsigned Dim = Field::dim;
332 : using mesh_type = typename Field::Mesh_t;
333 : using layout_type = typename Field::Layout_t;
334 :
335 0 : gs_preconditioner(LowerF&& lower, UpperF&& upper, InvDiagF&& inverse_diagonal,
336 : unsigned innerloops, unsigned outerloops)
337 : : preconditioner<Field>("Gauss-Seidel")
338 0 : , innerloops_m(innerloops)
339 0 : , outerloops_m(outerloops) {
340 0 : lower_m = std::move(lower);
341 0 : upper_m = std::move(upper);
342 0 : inverse_diagonal_m = std::move(inverse_diagonal);
343 0 : }
344 :
345 0 : Field operator()(Field& b) override {
346 0 : layout_type& layout = b.getLayout();
347 0 : mesh_type& mesh = b.get_mesh();
348 :
349 0 : Field x(mesh, layout);
350 :
351 0 : x = 0; // Initial guess
352 :
353 0 : for (unsigned int k = 0; k < outerloops_m; ++k) {
354 0 : UL_m = upper_m(x);
355 0 : r_m = b - UL_m;
356 0 : for (unsigned int j = 0; j < innerloops_m; ++j) {
357 0 : UL_m = lower_m(x);
358 0 : x = r_m - UL_m;
359 0 : x = inverse_diagonal_m(x) * x;
360 : }
361 0 : UL_m = lower_m(x);
362 0 : r_m = b - UL_m;
363 0 : for (unsigned int j = 0; j < innerloops_m; ++j) {
364 0 : UL_m = upper_m(x);
365 0 : x = r_m - UL_m;
366 0 : x = inverse_diagonal_m(x) * x;
367 : }
368 : }
369 0 : return x;
370 0 : }
371 :
372 0 : void init_fields(Field& b) override {
373 0 : layout_type& layout = b.getLayout();
374 0 : mesh_type& mesh = b.get_mesh();
375 :
376 0 : UL_m = Field(mesh, layout);
377 0 : r_m = Field(mesh, layout);
378 0 : }
379 :
380 : protected:
381 : LowerF lower_m;
382 : UpperF upper_m;
383 : InvDiagF inverse_diagonal_m;
384 : unsigned innerloops_m;
385 : unsigned outerloops_m;
386 : Field UL_m;
387 : Field r_m;
388 : };
389 :
390 : /*!
391 : * Symmetric successive over-relaxation
392 : */
393 : template <typename Field, typename LowerF, typename UpperF, typename InvDiagF, typename DiagF>
394 : struct ssor_preconditioner : public preconditioner<Field> {
395 : constexpr static unsigned Dim = Field::dim;
396 : using mesh_type = typename Field::Mesh_t;
397 : using layout_type = typename Field::Layout_t;
398 :
399 0 : ssor_preconditioner(LowerF&& lower, UpperF&& upper, InvDiagF&& inverse_diagonal,
400 : DiagF&& diagonal, unsigned innerloops, unsigned outerloops,
401 : double omega)
402 : : preconditioner<Field>("ssor")
403 0 : , innerloops_m(innerloops)
404 0 : , outerloops_m(outerloops)
405 0 : , omega_m(omega) {
406 0 : lower_m = std::move(lower);
407 0 : upper_m = std::move(upper);
408 0 : inverse_diagonal_m = std::move(inverse_diagonal);
409 0 : diagonal_m = std::move(diagonal);
410 0 : }
411 :
412 0 : Field operator()(Field& b) override {
413 0 : static IpplTimings::TimerRef initTimer = IpplTimings::getTimer("SSOR Init");
414 0 : IpplTimings::startTimer(initTimer);
415 :
416 : double D;
417 :
418 0 : layout_type& layout = b.getLayout();
419 0 : mesh_type& mesh = b.get_mesh();
420 :
421 0 : Field x(mesh, layout);
422 :
423 0 : x = 0; // Initial guess
424 :
425 0 : IpplTimings::stopTimer(initTimer);
426 :
427 0 : static IpplTimings::TimerRef loopTimer = IpplTimings::getTimer("SSOR loop");
428 0 : IpplTimings::startTimer(loopTimer);
429 :
430 0 : for (unsigned int k = 0; k < outerloops_m; ++k) {
431 0 : UL_m = upper_m(x);
432 0 : D = diagonal_m(x);
433 0 : r_m = omega_m * (b - UL_m) + (1.0 - omega_m) * D * x;
434 :
435 0 : for (unsigned int j = 0; j < innerloops_m; ++j) {
436 0 : UL_m = lower_m(x);
437 0 : x = r_m - omega_m * UL_m;
438 0 : x = inverse_diagonal_m(x) * x;
439 : }
440 0 : UL_m = lower_m(x);
441 0 : D = diagonal_m(x);
442 0 : r_m = omega_m * (b - UL_m) + (1.0 - omega_m) * D * x;
443 0 : for (unsigned int j = 0; j < innerloops_m; ++j) {
444 0 : UL_m = upper_m(x);
445 0 : x = r_m - omega_m * UL_m;
446 0 : x = inverse_diagonal_m(x) * x;
447 : }
448 : }
449 0 : IpplTimings::stopTimer(loopTimer);
450 0 : return x;
451 0 : }
452 :
453 0 : void init_fields(Field& b) override {
454 0 : layout_type& layout = b.getLayout();
455 0 : mesh_type& mesh = b.get_mesh();
456 :
457 0 : UL_m = Field(mesh, layout);
458 0 : r_m = Field(mesh, layout);
459 0 : }
460 :
461 : protected:
462 : LowerF lower_m;
463 : UpperF upper_m;
464 : InvDiagF inverse_diagonal_m;
465 : DiagF diagonal_m;
466 : unsigned innerloops_m;
467 : unsigned outerloops_m;
468 : double omega_m;
469 : Field UL_m;
470 : Field r_m;
471 : };
472 :
473 : /*!
474 : * Computes the largest Eigenvalue of the Functor f
475 : * @param f Functor
476 : * @param x_0 initial guess
477 : * @param max_iter maximum number of iterations
478 : * @param tol tolerance
479 : */
480 : template <typename Field, typename Functor>
481 : double powermethod(Functor&& f, Field& x_0, unsigned int max_iter = 5000, double tol = 1e-3) {
482 : unsigned int i = 0;
483 : using mesh_type = typename Field::Mesh_t;
484 : using layout_type = typename Field::Layout_t;
485 : mesh_type& mesh = x_0.get_mesh();
486 : layout_type& layout = x_0.getLayout();
487 : Field x_new(mesh, layout);
488 : Field x_diff(mesh, layout);
489 : double error = 1.0;
490 : double lambda = 1.0;
491 : while (error > tol && i < max_iter) {
492 : x_new = f(x_0);
493 : lambda = norm(x_new);
494 : x_diff = x_new - lambda * x_0;
495 : error = norm(x_diff);
496 : x_new = x_new / lambda;
497 : x_0 = x_new.deepCopy();
498 : ++i;
499 : }
500 : if (i == max_iter) {
501 : std::cerr << "Powermethod did not converge, lambda_max : " << lambda
502 : << ", error : " << error << std::endl;
503 : }
504 : return lambda;
505 : }
506 :
507 : /*!
508 : * Computes the smallest Eigenvalue of the Functor f (f must be symmetric positive definite)
509 : * @param f Functor
510 : * @param x_0 initial guess
511 : * @param lambda_max largest Eigenvalue
512 : * @param max_iter maximum number of iterations
513 : * @param tol tolerance
514 : */
515 : template <typename Field, typename Functor>
516 : double adapted_powermethod(Functor&& f, Field& x_0, double lambda_max,
517 : unsigned int max_iter = 5000, double tol = 1e-3) {
518 : unsigned int i = 0;
519 : using mesh_type = typename Field::Mesh_t;
520 : using layout_type = typename Field::Layout_t;
521 : mesh_type& mesh = x_0.get_mesh();
522 : layout_type& layout = x_0.getLayout();
523 : Field x_new(mesh, layout);
524 : Field x_diff(mesh, layout);
525 : double error = 1.0;
526 : double lambda = 1.0;
527 : while (error > tol && i < max_iter) {
528 : x_new = f(x_0);
529 : x_new = x_new - lambda_max * x_0;
530 : lambda = -norm(x_new); // We know that lambda < 0;
531 : x_diff = x_new - lambda * x_0;
532 : error = norm(x_diff);
533 : x_new = x_new / -lambda;
534 : x_0 = x_new.deepCopy();
535 : ++i;
536 : }
537 : lambda = lambda + lambda_max;
538 : if (i == max_iter) {
539 : std::cerr << "Powermethod did not converge, lambda_min : " << lambda
540 : << ", error : " << error << std::endl;
541 : }
542 : return lambda;
543 : }
544 :
545 : /*
546 : // Use the powermethod to compute the eigenvalues if no analytical solution is known
547 : beta = powermethod(std::move(op_m), x_0);
548 : // Trick for computing the smallest Eigenvalue of SPD Matrix
549 : alpha = adapted_powermethod(std::move(op_m), x_0, beta);
550 : */
551 :
552 : } // namespace ippl
553 :
554 : #endif // IPPL_PRECONDITIONER_H
|