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