xref: /petsc/src/ksp/pc/impls/eisens/eisen.c (revision bcd4bb4a4158aa96f212e9537e87b40407faf83e)
1 /*
2    Defines a  Eisenstat trick SSOR  preconditioner. This uses about
3  %50 of the usual amount of floating point ops used for SSOR + Krylov
4  method. But it requires actually solving the preconditioned problem
5  with both left and right preconditioning.
6 */
7 #include <petsc/private/pcimpl.h> /*I "petscpc.h" I*/
8 
9 typedef struct {
10   Mat       shell, A;
11   Vec       b[2], diag; /* temporary storage for true right-hand side */
12   PetscReal omega;
13   PetscBool usediag; /* indicates preconditioner should include diagonal scaling*/
14 } PC_Eisenstat;
15 
16 static PetscErrorCode PCMult_Eisenstat(Mat mat, Vec b, Vec x)
17 {
18   PC            pc;
19   PC_Eisenstat *eis;
20 
21   PetscFunctionBegin;
22   PetscCall(MatShellGetContext(mat, &pc));
23   eis = (PC_Eisenstat *)pc->data;
24   PetscCall(MatSOR(eis->A, b, eis->omega, SOR_EISENSTAT, 0.0, 1, 1, x));
25   PetscCall(MatFactorGetError(eis->A, (MatFactorError *)&pc->failedreason));
26   PetscFunctionReturn(PETSC_SUCCESS);
27 }
28 
29 static PetscErrorCode PCNorm_Eisenstat(Mat mat, NormType type, PetscReal *nrm)
30 {
31   PC            pc;
32   PC_Eisenstat *eis;
33 
34   PetscFunctionBegin;
35   PetscCall(MatShellGetContext(mat, &pc));
36   eis = (PC_Eisenstat *)pc->data;
37   PetscCall(MatNorm(eis->A, type, nrm));
38   PetscFunctionReturn(PETSC_SUCCESS);
39 }
40 
41 static PetscErrorCode PCApply_Eisenstat(PC pc, Vec x, Vec y)
42 {
43   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
44   PetscBool     hasop;
45 
46   PetscFunctionBegin;
47   if (eis->usediag) {
48     PetscCall(MatHasOperation(pc->pmat, MATOP_MULT_DIAGONAL_BLOCK, &hasop));
49     if (hasop) {
50       PetscCall(MatMultDiagonalBlock(pc->pmat, x, y));
51     } else {
52       PetscCall(VecPointwiseMult(y, x, eis->diag));
53     }
54   } else PetscCall(VecCopy(x, y));
55   PetscFunctionReturn(PETSC_SUCCESS);
56 }
57 
58 static PetscErrorCode PCApplyTranspose_Eisenstat(PC pc, Vec x, Vec y)
59 {
60   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
61   PetscBool     hasop, set, sym;
62 
63   PetscFunctionBegin;
64   PetscCall(MatIsSymmetricKnown(eis->A, &set, &sym));
65   PetscCheck(set && sym, PetscObjectComm((PetscObject)pc), PETSC_ERR_SUP, "Can only apply transpose of Eisenstat if matrix is symmetric");
66   if (eis->usediag) {
67     PetscCall(MatHasOperation(pc->pmat, MATOP_MULT_DIAGONAL_BLOCK, &hasop));
68     if (hasop) {
69       PetscCall(MatMultDiagonalBlock(pc->pmat, x, y));
70     } else {
71       PetscCall(VecPointwiseMult(y, x, eis->diag));
72     }
73   } else PetscCall(VecCopy(x, y));
74   PetscFunctionReturn(PETSC_SUCCESS);
75 }
76 
77 static PetscErrorCode PCPreSolve_Eisenstat(PC pc, KSP ksp, Vec b, Vec x)
78 {
79   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
80   PetscBool     nonzero;
81 
82   PetscFunctionBegin;
83   if (pc->presolvedone < 2) {
84     PetscCheck(pc->mat == pc->pmat, PetscObjectComm((PetscObject)pc), PETSC_ERR_SUP, "Cannot have different mat and pmat");
85     /* swap shell matrix and true matrix */
86     eis->A  = pc->mat;
87     pc->mat = eis->shell;
88   }
89 
90   if (!eis->b[pc->presolvedone - 1]) PetscCall(VecDuplicate(b, &eis->b[pc->presolvedone - 1]));
91 
92   /* if nonzero initial guess, modify x */
93   PetscCall(KSPGetInitialGuessNonzero(ksp, &nonzero));
94   if (nonzero) {
95     PetscCall(VecCopy(x, eis->b[pc->presolvedone - 1]));
96     PetscCall(MatSOR(eis->A, eis->b[pc->presolvedone - 1], eis->omega, SOR_APPLY_UPPER, 0.0, 1, 1, x));
97     PetscCall(MatFactorGetError(eis->A, (MatFactorError *)&pc->failedreason));
98   }
99 
100   /* save true b, other option is to swap pointers */
101   PetscCall(VecCopy(b, eis->b[pc->presolvedone - 1]));
102 
103   /* modify b by (L + D/omega)^{-1} */
104   PetscCall(MatSOR(eis->A, eis->b[pc->presolvedone - 1], eis->omega, (MatSORType)(SOR_ZERO_INITIAL_GUESS | SOR_LOCAL_FORWARD_SWEEP), 0.0, 1, 1, b));
105   PetscCall(MatFactorGetError(eis->A, (MatFactorError *)&pc->failedreason));
106   PetscFunctionReturn(PETSC_SUCCESS);
107 }
108 
109 static PetscErrorCode PCPostSolve_Eisenstat(PC pc, KSP ksp, Vec b, Vec x)
110 {
111   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
112 
113   PetscFunctionBegin;
114   /* get back true b */
115   PetscCall(VecCopy(eis->b[pc->presolvedone], b));
116 
117   /* modify x by (U + D/omega)^{-1} */
118   PetscCall(VecCopy(x, eis->b[pc->presolvedone]));
119   PetscCall(MatSOR(eis->A, eis->b[pc->presolvedone], eis->omega, (MatSORType)(SOR_ZERO_INITIAL_GUESS | SOR_LOCAL_BACKWARD_SWEEP), 0.0, 1, 1, x));
120   PetscCall(MatFactorGetError(eis->A, (MatFactorError *)&pc->failedreason));
121   if (!pc->presolvedone) pc->mat = eis->A;
122   PetscFunctionReturn(PETSC_SUCCESS);
123 }
124 
125 static PetscErrorCode PCReset_Eisenstat(PC pc)
126 {
127   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
128 
129   PetscFunctionBegin;
130   PetscCall(VecDestroy(&eis->b[0]));
131   PetscCall(VecDestroy(&eis->b[1]));
132   PetscCall(MatDestroy(&eis->shell));
133   PetscCall(VecDestroy(&eis->diag));
134   PetscFunctionReturn(PETSC_SUCCESS);
135 }
136 
137 static PetscErrorCode PCDestroy_Eisenstat(PC pc)
138 {
139   PetscFunctionBegin;
140   PetscCall(PCReset_Eisenstat(pc));
141   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatSetOmega_C", NULL));
142   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatSetNoDiagonalScaling_C", NULL));
143   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatGetOmega_C", NULL));
144   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatGetNoDiagonalScaling_C", NULL));
145   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCPreSolveChangeRHS_C", NULL));
146   PetscCall(PetscFree(pc->data));
147   PetscFunctionReturn(PETSC_SUCCESS);
148 }
149 
150 static PetscErrorCode PCSetFromOptions_Eisenstat(PC pc, PetscOptionItems PetscOptionsObject)
151 {
152   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
153   PetscBool     set, flg;
154   PetscReal     omega;
155 
156   PetscFunctionBegin;
157   PetscOptionsHeadBegin(PetscOptionsObject, "Eisenstat SSOR options");
158   PetscCall(PetscOptionsReal("-pc_eisenstat_omega", "Relaxation factor 0 < omega < 2", "PCEisenstatSetOmega", eis->omega, &omega, &flg));
159   if (flg) PetscCall(PCEisenstatSetOmega(pc, omega));
160   PetscCall(PetscOptionsBool("-pc_eisenstat_no_diagonal_scaling", "Do not use standard diagonal scaling", "PCEisenstatSetNoDiagonalScaling", eis->usediag ? PETSC_FALSE : PETSC_TRUE, &flg, &set));
161   if (set) PetscCall(PCEisenstatSetNoDiagonalScaling(pc, flg));
162   PetscOptionsHeadEnd();
163   PetscFunctionReturn(PETSC_SUCCESS);
164 }
165 
166 static PetscErrorCode PCView_Eisenstat(PC pc, PetscViewer viewer)
167 {
168   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
169   PetscBool     isascii;
170 
171   PetscFunctionBegin;
172   PetscCall(PetscObjectTypeCompare((PetscObject)viewer, PETSCVIEWERASCII, &isascii));
173   if (isascii) {
174     PetscCall(PetscViewerASCIIPrintf(viewer, "  omega = %g\n", (double)eis->omega));
175     if (eis->usediag) {
176       PetscCall(PetscViewerASCIIPrintf(viewer, "  Using diagonal scaling (default)\n"));
177     } else {
178       PetscCall(PetscViewerASCIIPrintf(viewer, "  Not using diagonal scaling\n"));
179     }
180   }
181   PetscFunctionReturn(PETSC_SUCCESS);
182 }
183 
184 static PetscErrorCode PCSetUp_Eisenstat(PC pc)
185 {
186   PetscInt      M, N, m, n;
187   PetscBool     set, sym;
188   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
189 
190   PetscFunctionBegin;
191   if (!pc->setupcalled) {
192     PetscCall(MatGetSize(pc->mat, &M, &N));
193     PetscCall(MatGetLocalSize(pc->mat, &m, &n));
194     PetscCall(MatIsSymmetricKnown(pc->mat, &set, &sym));
195     PetscCall(MatCreate(PetscObjectComm((PetscObject)pc), &eis->shell));
196     PetscCall(MatSetSizes(eis->shell, m, n, M, N));
197     PetscCall(MatSetType(eis->shell, MATSHELL));
198     PetscCall(MatSetUp(eis->shell));
199     PetscCall(MatShellSetContext(eis->shell, pc));
200     PetscCall(MatShellSetOperation(eis->shell, MATOP_MULT, (PetscErrorCodeFn *)PCMult_Eisenstat));
201     if (set && sym) PetscCall(MatShellSetOperation(eis->shell, MATOP_MULT_TRANSPOSE, (PetscErrorCodeFn *)PCMult_Eisenstat));
202     PetscCall(MatShellSetOperation(eis->shell, MATOP_NORM, (PetscErrorCodeFn *)PCNorm_Eisenstat));
203   }
204   if (!eis->usediag) PetscFunctionReturn(PETSC_SUCCESS);
205   if (!pc->setupcalled) PetscCall(MatCreateVecs(pc->pmat, &eis->diag, NULL));
206   PetscCall(MatGetDiagonal(pc->pmat, eis->diag));
207   PetscFunctionReturn(PETSC_SUCCESS);
208 }
209 
210 /* --------------------------------------------------------------------*/
211 
212 static PetscErrorCode PCEisenstatSetOmega_Eisenstat(PC pc, PetscReal omega)
213 {
214   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
215 
216   PetscFunctionBegin;
217   PetscCheck(omega > 0.0 && omega < 2.0, PetscObjectComm((PetscObject)pc), PETSC_ERR_ARG_OUTOFRANGE, "Relaxation out of range");
218   eis->omega = omega;
219   PetscFunctionReturn(PETSC_SUCCESS);
220 }
221 
222 static PetscErrorCode PCEisenstatSetNoDiagonalScaling_Eisenstat(PC pc, PetscBool flg)
223 {
224   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
225 
226   PetscFunctionBegin;
227   eis->usediag = flg;
228   PetscFunctionReturn(PETSC_SUCCESS);
229 }
230 
231 static PetscErrorCode PCEisenstatGetOmega_Eisenstat(PC pc, PetscReal *omega)
232 {
233   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
234 
235   PetscFunctionBegin;
236   *omega = eis->omega;
237   PetscFunctionReturn(PETSC_SUCCESS);
238 }
239 
240 static PetscErrorCode PCEisenstatGetNoDiagonalScaling_Eisenstat(PC pc, PetscBool *flg)
241 {
242   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
243 
244   PetscFunctionBegin;
245   *flg = eis->usediag;
246   PetscFunctionReturn(PETSC_SUCCESS);
247 }
248 
249 /*@
250   PCEisenstatSetOmega - Sets the SSOR relaxation coefficient, omega,
251   to use with Eisenstat's trick (where omega = 1.0 by default)
252 
253   Logically Collective
254 
255   Input Parameters:
256 + pc    - the preconditioner context
257 - omega - relaxation coefficient (0 < omega < 2)
258 
259   Options Database Key:
260 . -pc_eisenstat_omega <omega> - Sets omega
261 
262   Level: intermediate
263 
264   Notes:
265   The Eisenstat trick implementation of SSOR requires about 50% of the
266   usual amount of floating point operations used for SSOR + Krylov method;
267   however, the preconditioned problem must be solved with both left
268   and right preconditioning.
269 
270   To use SSOR without the Eisenstat trick, employ the `PCSOR` preconditioner,
271   which can be chosen with the database options `-pc_type sor -pc_sor_symmetric`
272 
273 .seealso: [](ch_ksp), `PCSORSetOmega()`, `PCEISENSTAT`
274 @*/
275 PetscErrorCode PCEisenstatSetOmega(PC pc, PetscReal omega)
276 {
277   PetscFunctionBegin;
278   PetscValidHeaderSpecific(pc, PC_CLASSID, 1);
279   PetscValidLogicalCollectiveReal(pc, omega, 2);
280   PetscTryMethod(pc, "PCEisenstatSetOmega_C", (PC, PetscReal), (pc, omega));
281   PetscFunctionReturn(PETSC_SUCCESS);
282 }
283 
284 /*@
285   PCEisenstatSetNoDiagonalScaling - Causes the Eisenstat preconditioner, `PCEISENSTAT`
286   not to do additional diagonal preconditioning. For matrices with a constant
287   along the diagonal, this may save a small amount of work.
288 
289   Logically Collective
290 
291   Input Parameters:
292 + pc  - the preconditioner context
293 - flg - `PETSC_TRUE` turns off diagonal scaling inside the algorithm
294 
295   Options Database Key:
296 . -pc_eisenstat_no_diagonal_scaling - Activates `PCEisenstatSetNoDiagonalScaling()`
297 
298   Level: intermediate
299 
300   Note:
301   If you use the `KSPSetDiagonalScaling()` or -ksp_diagonal_scale option then you will
302   likely want to use this routine since it will save you some unneeded flops.
303 
304 .seealso: [](ch_ksp), `PCEisenstatSetOmega()`, `PCEISENSTAT`
305 @*/
306 PetscErrorCode PCEisenstatSetNoDiagonalScaling(PC pc, PetscBool flg)
307 {
308   PetscFunctionBegin;
309   PetscValidHeaderSpecific(pc, PC_CLASSID, 1);
310   PetscTryMethod(pc, "PCEisenstatSetNoDiagonalScaling_C", (PC, PetscBool), (pc, flg));
311   PetscFunctionReturn(PETSC_SUCCESS);
312 }
313 
314 /*@
315   PCEisenstatGetOmega - Gets the SSOR relaxation coefficient, omega,
316   to use with Eisenstat's trick (where omega = 1.0 by default).
317 
318   Logically Collective
319 
320   Input Parameter:
321 . pc - the preconditioner context
322 
323   Output Parameter:
324 . omega - relaxation coefficient (0 < omega < 2)
325 
326   Options Database Key:
327 . -pc_eisenstat_omega <omega> - Sets omega
328 
329   Notes:
330   The Eisenstat trick implementation of SSOR requires about 50% of the
331   usual amount of floating point operations used for SSOR + Krylov method;
332   however, the preconditioned problem must be solved with both left
333   and right preconditioning.
334 
335   To use SSOR without the Eisenstat trick, employ the PCSOR preconditioner,
336   which can be chosen with the database options `-pc_type sor -pc_sor_symmetric`
337 
338   Level: intermediate
339 
340 .seealso: [](ch_ksp), `PCEISENSTAT`, `PCSORGetOmega()`, `PCEisenstatSetOmega()`
341 @*/
342 PetscErrorCode PCEisenstatGetOmega(PC pc, PetscReal *omega)
343 {
344   PetscFunctionBegin;
345   PetscValidHeaderSpecific(pc, PC_CLASSID, 1);
346   PetscUseMethod(pc, "PCEisenstatGetOmega_C", (PC, PetscReal *), (pc, omega));
347   PetscFunctionReturn(PETSC_SUCCESS);
348 }
349 
350 /*@
351   PCEisenstatGetNoDiagonalScaling - Tells if the Eisenstat preconditioner
352   not to do additional diagonal preconditioning. For matrices with a constant
353   along the diagonal, this may save a small amount of work.
354 
355   Logically Collective
356 
357   Input Parameter:
358 . pc - the preconditioner context
359 
360   Output Parameter:
361 . flg - `PETSC_TRUE` means there is no diagonal scaling applied
362 
363   Options Database Key:
364 . -pc_eisenstat_no_diagonal_scaling - Activates `PCEisenstatSetNoDiagonalScaling()`
365 
366   Level: intermediate
367 
368   Note:
369   If you use the KSPSetDiagonalScaling() or -ksp_diagonal_scale option then you will
370   likely want to use this routine since it will save you some unneeded flops.
371 
372 .seealso: , `PCEISENSTAT`, `PCEisenstatGetOmega()`
373 @*/
374 PetscErrorCode PCEisenstatGetNoDiagonalScaling(PC pc, PetscBool *flg)
375 {
376   PetscFunctionBegin;
377   PetscValidHeaderSpecific(pc, PC_CLASSID, 1);
378   PetscUseMethod(pc, "PCEisenstatGetNoDiagonalScaling_C", (PC, PetscBool *), (pc, flg));
379   PetscFunctionReturn(PETSC_SUCCESS);
380 }
381 
382 static PetscErrorCode PCPreSolveChangeRHS_Eisenstat(PC pc, PetscBool *change)
383 {
384   PetscFunctionBegin;
385   *change = PETSC_TRUE;
386   PetscFunctionReturn(PETSC_SUCCESS);
387 }
388 
389 /*MC
390      PCEISENSTAT - An implementation of SSOR (symmetric successive over relaxation, symmetric Gauss-Seidel)
391                    preconditioning that incorporates Eisenstat's trick to reduce the amount of computation needed.
392 
393    Options Database Keys:
394 +  -pc_eisenstat_omega <omega> - Sets omega
395 -  -pc_eisenstat_no_diagonal_scaling - Activates `PCEisenstatSetNoDiagonalScaling()`
396 
397    Level: beginner
398 
399    Notes:
400    Only implemented for the `MATAIJ` matrix format.
401 
402    Not a true parallel SOR, in parallel this implementation corresponds to block Jacobi with SOR on each block.
403 
404    Developer Note:
405    Since this algorithm runs the Krylov method on a transformed linear system the implementation provides `PCPreSolve()` and `PCPostSolve()`
406    routines that `KSP` uses to set up the transformed linear system.
407 
408 .seealso: [](ch_ksp), `PCCreate()`, `PCSetType()`, `PCType`, `PC`, `PCEisenstatGetOmega()`,
409           `PCEisenstatSetNoDiagonalScaling()`, `PCEisenstatSetOmega()`, `PCSOR`
410 M*/
411 
412 PETSC_EXTERN PetscErrorCode PCCreate_Eisenstat(PC pc)
413 {
414   PC_Eisenstat *eis;
415 
416   PetscFunctionBegin;
417   PetscCall(PetscNew(&eis));
418 
419   pc->ops->apply           = PCApply_Eisenstat;
420   pc->ops->applytranspose  = PCApplyTranspose_Eisenstat;
421   pc->ops->presolve        = PCPreSolve_Eisenstat;
422   pc->ops->postsolve       = PCPostSolve_Eisenstat;
423   pc->ops->applyrichardson = NULL;
424   pc->ops->setfromoptions  = PCSetFromOptions_Eisenstat;
425   pc->ops->destroy         = PCDestroy_Eisenstat;
426   pc->ops->reset           = PCReset_Eisenstat;
427   pc->ops->view            = PCView_Eisenstat;
428   pc->ops->setup           = PCSetUp_Eisenstat;
429 
430   pc->data     = eis;
431   eis->omega   = 1.0;
432   eis->b[0]    = NULL;
433   eis->b[1]    = NULL;
434   eis->diag    = NULL;
435   eis->usediag = PETSC_TRUE;
436 
437   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatSetOmega_C", PCEisenstatSetOmega_Eisenstat));
438   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatSetNoDiagonalScaling_C", PCEisenstatSetNoDiagonalScaling_Eisenstat));
439   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatGetOmega_C", PCEisenstatGetOmega_Eisenstat));
440   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatGetNoDiagonalScaling_C", PCEisenstatGetNoDiagonalScaling_Eisenstat));
441   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCPreSolveChangeRHS_C", PCPreSolveChangeRHS_Eisenstat));
442   PetscFunctionReturn(PETSC_SUCCESS);
443 }
444