Branch data Line data Source code
1 : : //
2 : : // Class PCG
3 : : // Preconditioned Conjugate Gradient solver algorithm
4 : : //
5 : :
6 : : #ifndef IPPL_PCG_H
7 : : #define IPPL_PCG_H
8 : :
9 : : #include "Preconditioner.h"
10 : : #include "SolverAlgorithm.h"
11 : :
12 : : namespace ippl {
13 : : template <typename OperatorRet, typename LowerRet, typename UpperRet, typename UpperLowerRet,
14 : : typename InverseDiagRet, typename DiagRet, typename FieldLHS,
15 : : typename FieldRHS = FieldLHS>
16 : : class CG : public SolverAlgorithm<FieldLHS, FieldRHS> {
17 : : using Base = SolverAlgorithm<FieldLHS, FieldRHS>;
18 : : typedef typename Base::lhs_type::value_type T;
19 : :
20 : : public:
21 : : using typename Base::lhs_type, typename Base::rhs_type;
22 : : using OperatorF = std::function<OperatorRet(lhs_type)>;
23 : : using LowerF = std::function<LowerRet(lhs_type)>;
24 : : using UpperF = std::function<UpperRet(lhs_type)>;
25 : : using UpperLowerF = std::function<UpperLowerRet(lhs_type)>;
26 : : using InverseDiagF = std::function<InverseDiagRet(lhs_type)>;
27 : : using DiagF = std::function<DiagRet(lhs_type)>;
28 : :
29 : 0 : virtual ~CG() = default;
30 : :
31 : : /*!
32 : : * Sets the differential operator for the conjugate gradient algorithm
33 : : * @param op A function that returns OpRet and takes a field of the LHS type
34 : : */
35 : 0 : virtual void setOperator(OperatorF op) { op_m = std::move(op); }
36 : 0 : virtual void setPreconditioner(
37 : : [[maybe_unused]] OperatorF&& op, // Operator passed to chebyshev and newton
38 : : [[maybe_unused]] LowerF&& lower, // Operator passed to 2-step gauss-seidel and ssor
39 : : [[maybe_unused]] UpperF&& upper, // Operator passed to 2-step gauss-seidel and ssor
40 : : [[maybe_unused]] UpperLowerF&&
41 : : upper_and_lower, // Operator passed to 2-step gauss-seidel
42 : : [[maybe_unused]] InverseDiagF&&
43 : : inverse_diagonal, // Operator passed to jacobi, 2-step gauss-seidel and ssor
44 : : [[maybe_unused]] DiagF&& diagonal, // Operator passed to SSOR
45 : : [[maybe_unused]] double alpha, // smallest eigenvalue of the operator
46 : : [[maybe_unused]] double beta, // largest eigenvalue of the operator
47 : : [[maybe_unused]] std::string preconditioner_type =
48 : : "", // Name of the preconditioner that should be used
49 : : [[maybe_unused]] int level =
50 : : 5, // This is a dummy default parameter, actual default parameter should be
51 : : // set in main
52 : : [[maybe_unused]] int degree =
53 : : 31, // This is a dummy default parameter, actual default parameter should
54 : : // be set in main
55 : : [[maybe_unused]] int richardson_iterations =
56 : : 1, // This is a dummy default parameter, actual default
57 : : // parameter should be set in main
58 : : [[maybe_unused]] int inner =
59 : : 5, // This is a dummy default parameter, actual default parameter should be
60 : : // set in main
61 : : [[maybe_unused]] int outer =
62 : : 1, // This is a dummy default parameter, actual default parameter should be
63 : : [[maybe_unused]] double omega =
64 : : 1 // This is a dummy default parameter, actual default parameter should be
65 : : // set in main
66 : 0 : ) {}
67 : : /*!
68 : : * Query how many iterations were required to obtain the solution
69 : : * the last time this solver was used
70 : : * @return Iteration count of last solve
71 : : */
72 : 0 : virtual int getIterationCount() { return iterations_m; }
73 : :
74 : 0 : virtual void operator()(lhs_type& lhs, rhs_type& rhs,
75 : : const ParameterList& params) override {
76 : 0 : constexpr unsigned Dim = lhs_type::dim;
77 : 0 : typename lhs_type::Mesh_t mesh = lhs.get_mesh();
78 [ # # # # ]: 0 : typename lhs_type::Layout_t layout = lhs.getLayout();
79 : :
80 : 0 : iterations_m = 0;
81 [ # # # # ]: 0 : const int maxIterations = params.get<int>("max_iterations");
82 : :
83 : : // Variable names mostly based on description in
84 : : // https://www.cs.cmu.edu/~quake-papers/painless-conjugate-gradient.pdf
85 [ # # ]: 0 : lhs_type r(mesh, layout);
86 [ # # ]: 0 : lhs_type d(mesh, layout);
87 : :
88 : : using bc_type = BConds<lhs_type, Dim>;
89 : 0 : bc_type lhsBCs = lhs.getFieldBC();
90 : 0 : bc_type bc;
91 : :
92 : 0 : bool allFacesPeriodic = true;
93 [ # # ]: 0 : for (unsigned int i = 0; i < 2 * Dim; ++i) {
94 [ # # ]: 0 : FieldBC bcType = lhsBCs[i]->getBCType();
95 [ # # ]: 0 : if (bcType == PERIODIC_FACE) {
96 : : // If the LHS has periodic BCs, so does the residue
97 [ # # ]: 0 : bc[i] = std::make_shared<PeriodicFace<lhs_type>>(i);
98 [ # # ]: 0 : } else if (bcType & CONSTANT_FACE) {
99 : : // If the LHS has constant BCs, the residue is zero on the BCs
100 : : // Bitwise AND with CONSTANT_FACE will succeed for ZeroFace or ConstantFace
101 [ # # ]: 0 : bc[i] = std::make_shared<ZeroFace<lhs_type>>(i);
102 : 0 : allFacesPeriodic = false;
103 : : } else {
104 [ # # # # : 0 : throw IpplException("PCG::operator()",
# # ]
105 : : "Only periodic or constant BCs for LHS supported.");
106 : : return;
107 : : }
108 : : }
109 : :
110 [ # # # # : 0 : r = rhs - op_m(lhs);
# # # # ]
111 [ # # # # ]: 0 : d = r.deepCopy();
112 [ # # ]: 0 : d.setFieldBC(bc);
113 : :
114 [ # # ]: 0 : T delta1 = innerProduct(r, d);
115 : 0 : T delta0 = delta1;
116 : 0 : residueNorm = std::sqrt(delta1);
117 [ # # # # : 0 : const T tolerance = params.get<T>("tolerance") * norm(rhs);
# # ]
118 : :
119 [ # # ]: 0 : lhs_type q(mesh, layout);
120 : :
121 [ # # # # ]: 0 : while (iterations_m < maxIterations && residueNorm > tolerance) {
122 [ # # # # : 0 : q = op_m(d);
# # ]
123 : :
124 [ # # ]: 0 : T alpha = delta1 / innerProduct(d, q);
125 [ # # # # : 0 : lhs = lhs + alpha * d;
# # ]
126 : :
127 : : // The exact residue is given by
128 : : // r = rhs - op_m(lhs);
129 : : // This correction is generally not used in practice because
130 : : // applying the Laplacian is computationally expensive and
131 : : // the correction does not have a significant effect on accuracy;
132 : : // in some implementations, the correction may be applied every few
133 : : // iterations to offset accumulated floating point errors
134 [ # # # # : 0 : r = r - alpha * q;
# # ]
135 : 0 : delta0 = delta1;
136 [ # # ]: 0 : delta1 = innerProduct(r, r);
137 : 0 : T beta = delta1 / delta0;
138 : :
139 : 0 : residueNorm = std::sqrt(delta1);
140 [ # # # # : 0 : d = r + beta * d;
# # ]
141 : 0 : ++iterations_m;
142 : : }
143 : :
144 [ # # ]: 0 : if (allFacesPeriodic) {
145 [ # # ]: 0 : T avg = lhs.getVolumeAverage();
146 [ # # # # ]: 0 : lhs = lhs - avg;
147 : : }
148 [ # # # # : 0 : }
# # # # #
# # # ]
149 : :
150 : 0 : virtual T getResidue() const { return residueNorm; }
151 : :
152 : : protected:
153 : : OperatorF op_m;
154 : : T residueNorm = 0;
155 : : int iterations_m = 0;
156 : : };
157 : :
158 : : template <typename OperatorRet, typename LowerRet, typename UpperRet, typename UpperLowerRet,
159 : : typename InverseDiagRet, typename DiagRet, typename FieldLHS,
160 : : typename FieldRHS = FieldLHS>
161 : : class PCG : public CG<OperatorRet, LowerRet, UpperRet, UpperLowerRet, InverseDiagRet, DiagRet,
162 : : FieldLHS, FieldRHS> {
163 : : using Base = SolverAlgorithm<FieldLHS, FieldRHS>;
164 : : typedef typename Base::lhs_type::value_type T;
165 : :
166 : : public:
167 : : using typename Base::lhs_type, typename Base::rhs_type;
168 : : using OperatorF = std::function<OperatorRet(lhs_type)>;
169 : : using LowerF = std::function<LowerRet(lhs_type)>;
170 : : using UpperF = std::function<UpperRet(lhs_type)>;
171 : : using UpperLowerF = std::function<UpperLowerRet(lhs_type)>;
172 : : using InverseDiagF = std::function<InverseDiagRet(lhs_type)>;
173 : : using DiagF = std::function<DiagRet(lhs_type)>;
174 : :
175 : 0 : PCG()
176 : : : CG<OperatorRet, LowerRet, UpperRet, UpperLowerRet, InverseDiagRet, DiagRet, FieldLHS,
177 : : FieldRHS>()
178 : 0 : , preconditioner_m(nullptr){};
179 : :
180 : : /*!
181 : : * Sets the differential operator for the conjugate gradient algorithm
182 : : * @param op A function that returns OpRet and takes a field of the LHS type
183 : : */
184 : 0 : void setPreconditioner(
185 : : OperatorF&& op, // Operator passed to chebyshev and newton
186 : : LowerF&& lower, // Operator passed to 2-step gauss-seidel
187 : : UpperF&& upper, // Operator passed to 2-step gauss-seidel
188 : : UpperLowerF&& upper_and_lower, // Operator passed to 2-step gauss-seidel
189 : : InverseDiagF&& inverse_diagonal, // Operator passed to jacobi and 2-step gauss-seidel
190 : : DiagF&& diagonal, // Operator passed to ssor
191 : : double alpha, // smallest eigenvalue of the operator
192 : : double beta, // largest eigenvalue of the operator
193 : : std::string preconditioner_type = "", // Name of the preconditioner that should be used
194 : : int level = 5, // This is a dummy default parameter, actual default parameter should be
195 : : // set in main
196 : : int degree = 31, // This is a dummy default parameter, actual default parameter should
197 : : // be set in main
198 : : int richardson_iterations = 4, // This is a dummy default parameter, actual default
199 : : // parameter should be set in main
200 : : int inner = 2, // This is a dummy default parameter, actual default parameter should be
201 : : // set in main
202 : : int outer = 2, // This is a dummy default parameter, actual default parameter should be
203 : : // set in main
204 : : double omega = 1.57079632679 // This is a dummy default parameter, actual default
205 : : // parameter should be set in main
206 : : // default = pi/2 as this was found optimal during hyperparameter scan for test case
207 : : // (see https://amas.web.psi.ch/people/aadelmann/ETH-Accel-Lecture-1/projectscompleted/cse/BSc-mbolliger.pdf)
208 : : ) override {
209 [ # # ]: 0 : if (preconditioner_type == "jacobi") {
210 : : // Turn on damping parameter
211 : : /*
212 : : double w = 2.0 / ((alpha + beta));
213 : : preconditioner_m = std::move(std::make_unique<jacobi_preconditioner<FieldLHS ,
214 : : InverseDiagF>>(std::move(inverse_diagonal), w));
215 : : */
216 : 0 : preconditioner_m =
217 [ # # ]: 0 : std::move(std::make_unique<jacobi_preconditioner<FieldLHS, InverseDiagF>>(
218 : 0 : std::move(inverse_diagonal)));
219 [ # # ]: 0 : } else if (preconditioner_type == "newton") {
220 : 0 : preconditioner_m = std::move(
221 [ # # ]: 0 : std::make_unique<polynomial_newton_preconditioner<FieldLHS, OperatorF>>(
222 : 0 : std::move(op), alpha, beta, level, 1e-3));
223 [ # # ]: 0 : } else if (preconditioner_type == "chebyshev") {
224 : 0 : preconditioner_m = std::move(
225 [ # # ]: 0 : std::make_unique<polynomial_chebyshev_preconditioner<FieldLHS, OperatorF>>(
226 : 0 : std::move(op), alpha, beta, degree, 1e-3));
227 [ # # ]: 0 : } else if (preconditioner_type == "richardson") {
228 : 0 : preconditioner_m =
229 : 0 : std::move(std::make_unique<
230 [ # # ]: 0 : richardson_preconditioner<FieldLHS, UpperLowerF, InverseDiagF>>(
231 : 0 : std::move(upper_and_lower), std::move(inverse_diagonal),
232 : : richardson_iterations));
233 [ # # ]: 0 : } else if (preconditioner_type == "gauss-seidel") {
234 : 0 : preconditioner_m = std::move(
235 [ # # ]: 0 : std::make_unique<gs_preconditioner<FieldLHS, LowerF, UpperF, InverseDiagF>>(
236 : 0 : std::move(lower), std::move(upper), std::move(inverse_diagonal), inner,
237 : : outer));
238 [ # # ]: 0 : } else if (preconditioner_type == "ssor") {
239 : 0 : preconditioner_m =
240 : 0 : std::move(std::make_unique<
241 [ # # ]: 0 : ssor_preconditioner<FieldLHS, LowerF, UpperF, InverseDiagF, DiagF>>(
242 : 0 : std::move(lower), std::move(upper), std::move(inverse_diagonal),
243 : 0 : std::move(diagonal), inner, outer, omega));
244 : : } else {
245 [ # # ]: 0 : preconditioner_m = std::move(std::make_unique<preconditioner<FieldLHS>>());
246 : : }
247 : 0 : }
248 : :
249 : 0 : void operator()(lhs_type& lhs, rhs_type& rhs, const ParameterList& params) override {
250 : 0 : constexpr unsigned Dim = lhs_type::dim;
251 : :
252 [ # # ]: 0 : if (preconditioner_m == nullptr) {
253 [ # # # # : 0 : throw IpplException("PCG::operator()",
# # ]
254 : : "Preconditioner has not been set for PCG solver");
255 : : }
256 : :
257 : 0 : typename lhs_type::Mesh_t mesh = lhs.get_mesh();
258 [ # # # # ]: 0 : typename lhs_type::Layout_t layout = lhs.getLayout();
259 : :
260 : 0 : this->iterations_m = 0;
261 [ # # # # ]: 0 : const int maxIterations = params.get<int>("max_iterations");
262 : :
263 : : // Variable names mostly based on description in
264 : : // https://www.cs.cmu.edu/~quake-papers/painless-conjugate-gradient.pdf
265 [ # # ]: 0 : lhs_type r(mesh, layout);
266 [ # # ]: 0 : lhs_type d(mesh, layout);
267 [ # # ]: 0 : lhs_type s(mesh, layout);
268 [ # # ]: 0 : lhs_type q(mesh, layout);
269 : :
270 [ # # ]: 0 : preconditioner_m->init_fields(lhs);
271 : :
272 : : using bc_type = BConds<lhs_type, Dim>;
273 : 0 : bc_type lhsBCs = lhs.getFieldBC();
274 : 0 : bc_type bc;
275 : :
276 : 0 : bool allFacesPeriodic = true;
277 [ # # ]: 0 : for (unsigned int i = 0; i < 2 * Dim; ++i) {
278 [ # # ]: 0 : FieldBC bcType = lhsBCs[i]->getBCType();
279 [ # # ]: 0 : if (bcType == PERIODIC_FACE) {
280 : : // If the LHS has periodic BCs, so does the residue
281 [ # # ]: 0 : bc[i] = std::make_shared<PeriodicFace<lhs_type>>(i);
282 [ # # ]: 0 : } else if (bcType & CONSTANT_FACE) {
283 : : // If the LHS has constant BCs, the residue is zero on the BCs
284 : : // Bitwise AND with CONSTANT_FACE will succeed for ZeroFace or ConstantFace
285 [ # # ]: 0 : bc[i] = std::make_shared<ZeroFace<lhs_type>>(i);
286 : 0 : allFacesPeriodic = false;
287 : : } else {
288 [ # # # # : 0 : throw IpplException("PCG::operator()",
# # ]
289 : : "Only periodic or constant BCs for LHS supported.");
290 : : return;
291 : : }
292 : : }
293 : :
294 [ # # # # : 0 : r = rhs - this->op_m(lhs);
# # # # ]
295 [ # # # # ]: 0 : d = preconditioner_m->operator()(r);
296 [ # # ]: 0 : d.setFieldBC(bc);
297 : :
298 [ # # ]: 0 : T delta1 = innerProduct(r, d);
299 : 0 : T delta0 = delta1;
300 : 0 : this->residueNorm = Kokkos::sqrt(Kokkos::abs(delta1));
301 [ # # # # ]: 0 : const T tolerance = params.get<T>("tolerance") * delta1;
302 : :
303 [ # # # # ]: 0 : while (this->iterations_m<maxIterations&& this->residueNorm> tolerance) {
304 [ # # # # : 0 : q = this->op_m(d);
# # ]
305 [ # # ]: 0 : T alpha = delta1 / innerProduct(d, q);
306 [ # # # # : 0 : lhs = lhs + alpha * d;
# # ]
307 : :
308 : : // The exact residue is given by
309 : : // r = rhs - BaseCG::op_m(lhs);
310 : : // This correction is generally not used in practice because
311 : : // applying the Laplacian is computationally expensive and
312 : : // the correction does not have a significant effect on accuracy;
313 : : // in some implementations, the correction may be applied every few
314 : : // iterations to offset accumulated floating point errors
315 [ # # # # : 0 : r = r - alpha * q;
# # ]
316 [ # # # # ]: 0 : s = preconditioner_m->operator()(r);
317 : :
318 : 0 : delta0 = delta1;
319 [ # # ]: 0 : delta1 = innerProduct(r, s);
320 : :
321 : 0 : T beta = delta1 / delta0;
322 : 0 : this->residueNorm = Kokkos::sqrt(Kokkos::abs(delta1));
323 : :
324 [ # # # # : 0 : d = s + beta * d;
# # ]
325 : 0 : ++this->iterations_m;
326 : : }
327 : :
328 [ # # ]: 0 : if (allFacesPeriodic) {
329 [ # # ]: 0 : T avg = lhs.getVolumeAverage();
330 [ # # # # ]: 0 : lhs = lhs - avg;
331 : : }
332 [ # # # # : 0 : }
# # # # #
# # # # #
# # ]
333 : :
334 : : protected:
335 : : std::unique_ptr<preconditioner<FieldLHS>> preconditioner_m;
336 : : };
337 : :
338 : : }; // namespace ippl
339 : :
340 : : #endif
|