Actual source code: eisen.c

  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>

  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;

 16: static PetscErrorCode PCMult_Eisenstat(Mat mat, Vec b, Vec x)
 17: {
 18:   PC            pc;
 19:   PC_Eisenstat *eis;

 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: }

 29: static PetscErrorCode PCNorm_Eisenstat(Mat mat, NormType type, PetscReal *nrm)
 30: {
 31:   PC            pc;
 32:   PC_Eisenstat *eis;

 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: }

 41: static PetscErrorCode PCApply_Eisenstat(PC pc, Vec x, Vec y)
 42: {
 43:   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
 44:   PetscBool     hasop;

 46:   PetscFunctionBegin;
 47:   if (eis->usediag) {
 48:     PetscCall(MatHasOperation(pc->pmat, MATOP_MULT_DIAGONAL_BLOCK, &hasop));
 49:     if (hasop) PetscCall(MatMultDiagonalBlock(pc->pmat, x, y));
 50:     else PetscCall(VecPointwiseMult(y, x, eis->diag));
 51:   } else PetscCall(VecCopy(x, y));
 52:   PetscFunctionReturn(PETSC_SUCCESS);
 53: }

 55: static PetscErrorCode PCApplyTranspose_Eisenstat(PC pc, Vec x, Vec y)
 56: {
 57:   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
 58:   PetscBool     hasop, set, sym;

 60:   PetscFunctionBegin;
 61:   PetscCall(MatIsSymmetricKnown(eis->A, &set, &sym));
 62:   PetscCheck(set && sym, PetscObjectComm((PetscObject)pc), PETSC_ERR_SUP, "Can only apply transpose of Eisenstat if matrix is symmetric");
 63:   if (eis->usediag) {
 64:     PetscCall(MatHasOperation(pc->pmat, MATOP_MULT_DIAGONAL_BLOCK, &hasop));
 65:     if (hasop) PetscCall(MatMultDiagonalBlock(pc->pmat, x, y));
 66:     else PetscCall(VecPointwiseMult(y, x, eis->diag));
 67:   } else PetscCall(VecCopy(x, y));
 68:   PetscFunctionReturn(PETSC_SUCCESS);
 69: }

 71: static PetscErrorCode PCPreSolve_Eisenstat(PC pc, KSP ksp, Vec b, Vec x)
 72: {
 73:   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
 74:   PetscBool     nonzero;

 76:   PetscFunctionBegin;
 77:   if (pc->presolvedone < 2) {
 78:     PetscCheck(pc->mat == pc->pmat, PetscObjectComm((PetscObject)pc), PETSC_ERR_SUP, "Cannot have different mat and pmat");
 79:     /* swap shell matrix and true matrix */
 80:     eis->A  = pc->mat;
 81:     pc->mat = eis->shell;
 82:   }

 84:   if (!eis->b[pc->presolvedone - 1]) PetscCall(VecDuplicate(b, &eis->b[pc->presolvedone - 1]));

 86:   /* if nonzero initial guess, modify x */
 87:   PetscCall(KSPGetInitialGuessNonzero(ksp, &nonzero));
 88:   if (nonzero) {
 89:     PetscCall(VecCopy(x, eis->b[pc->presolvedone - 1]));
 90:     PetscCall(MatSOR(eis->A, eis->b[pc->presolvedone - 1], eis->omega, SOR_APPLY_UPPER, 0.0, 1, 1, x));
 91:     PetscCall(MatFactorGetError(eis->A, (MatFactorError *)&pc->failedreason));
 92:   }

 94:   /* save true b, other option is to swap pointers */
 95:   PetscCall(VecCopy(b, eis->b[pc->presolvedone - 1]));

 97:   /* modify b by (L + D/omega)^{-1} */
 98:   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));
 99:   PetscCall(MatFactorGetError(eis->A, (MatFactorError *)&pc->failedreason));
100:   PetscFunctionReturn(PETSC_SUCCESS);
101: }

103: static PetscErrorCode PCPostSolve_Eisenstat(PC pc, KSP ksp, Vec b, Vec x)
104: {
105:   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;

107:   PetscFunctionBegin;
108:   /* get back true b */
109:   PetscCall(VecCopy(eis->b[pc->presolvedone], b));

111:   /* modify x by (U + D/omega)^{-1} */
112:   PetscCall(VecCopy(x, eis->b[pc->presolvedone]));
113:   PetscCall(MatSOR(eis->A, eis->b[pc->presolvedone], eis->omega, (MatSORType)(SOR_ZERO_INITIAL_GUESS | SOR_LOCAL_BACKWARD_SWEEP), 0.0, 1, 1, x));
114:   PetscCall(MatFactorGetError(eis->A, (MatFactorError *)&pc->failedreason));
115:   if (!pc->presolvedone) pc->mat = eis->A;
116:   PetscFunctionReturn(PETSC_SUCCESS);
117: }

119: static PetscErrorCode PCReset_Eisenstat(PC pc)
120: {
121:   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;

123:   PetscFunctionBegin;
124:   PetscCall(VecDestroy(&eis->b[0]));
125:   PetscCall(VecDestroy(&eis->b[1]));
126:   PetscCall(MatDestroy(&eis->shell));
127:   PetscCall(VecDestroy(&eis->diag));
128:   PetscFunctionReturn(PETSC_SUCCESS);
129: }

131: static PetscErrorCode PCDestroy_Eisenstat(PC pc)
132: {
133:   PetscFunctionBegin;
134:   PetscCall(PCReset_Eisenstat(pc));
135:   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatSetOmega_C", NULL));
136:   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatSetNoDiagonalScaling_C", NULL));
137:   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatGetOmega_C", NULL));
138:   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatGetNoDiagonalScaling_C", NULL));
139:   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCPreSolveChangeRHS_C", NULL));
140:   PetscCall(PetscFree(pc->data));
141:   PetscFunctionReturn(PETSC_SUCCESS);
142: }

144: static PetscErrorCode PCSetFromOptions_Eisenstat(PC pc, PetscOptionItems PetscOptionsObject)
145: {
146:   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
147:   PetscBool     set, flg;
148:   PetscReal     omega;

150:   PetscFunctionBegin;
151:   PetscOptionsHeadBegin(PetscOptionsObject, "Eisenstat SSOR options");
152:   PetscCall(PetscOptionsReal("-pc_eisenstat_omega", "Relaxation factor 0 < omega < 2", "PCEisenstatSetOmega", eis->omega, &omega, &flg));
153:   if (flg) PetscCall(PCEisenstatSetOmega(pc, omega));
154:   PetscCall(PetscOptionsBool("-pc_eisenstat_no_diagonal_scaling", "Do not use standard diagonal scaling", "PCEisenstatSetNoDiagonalScaling", eis->usediag ? PETSC_FALSE : PETSC_TRUE, &flg, &set));
155:   if (set) PetscCall(PCEisenstatSetNoDiagonalScaling(pc, flg));
156:   PetscOptionsHeadEnd();
157:   PetscFunctionReturn(PETSC_SUCCESS);
158: }

160: static PetscErrorCode PCView_Eisenstat(PC pc, PetscViewer viewer)
161: {
162:   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;
163:   PetscBool     isascii;

165:   PetscFunctionBegin;
166:   PetscCall(PetscObjectTypeCompare((PetscObject)viewer, PETSCVIEWERASCII, &isascii));
167:   if (isascii) {
168:     PetscCall(PetscViewerASCIIPrintf(viewer, "  omega = %g\n", (double)eis->omega));
169:     if (eis->usediag) {
170:       PetscCall(PetscViewerASCIIPrintf(viewer, "  Using diagonal scaling (default)\n"));
171:     } else {
172:       PetscCall(PetscViewerASCIIPrintf(viewer, "  Not using diagonal scaling\n"));
173:     }
174:   }
175:   PetscFunctionReturn(PETSC_SUCCESS);
176: }

178: static PetscErrorCode PCSetUp_Eisenstat(PC pc)
179: {
180:   PetscInt      M, N, m, n;
181:   PetscBool     set, sym;
182:   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;

184:   PetscFunctionBegin;
185:   if (!pc->setupcalled) {
186:     PetscCall(MatGetSize(pc->mat, &M, &N));
187:     PetscCall(MatGetLocalSize(pc->mat, &m, &n));
188:     PetscCall(MatIsSymmetricKnown(pc->mat, &set, &sym));
189:     PetscCall(MatCreate(PetscObjectComm((PetscObject)pc), &eis->shell));
190:     PetscCall(MatSetSizes(eis->shell, m, n, M, N));
191:     PetscCall(MatSetType(eis->shell, MATSHELL));
192:     PetscCall(MatSetUp(eis->shell));
193:     PetscCall(MatShellSetContext(eis->shell, pc));
194:     PetscCall(MatShellSetOperation(eis->shell, MATOP_MULT, (PetscErrorCodeFn *)PCMult_Eisenstat));
195:     if (set && sym) PetscCall(MatShellSetOperation(eis->shell, MATOP_MULT_TRANSPOSE, (PetscErrorCodeFn *)PCMult_Eisenstat));
196:     PetscCall(MatShellSetOperation(eis->shell, MATOP_NORM, (PetscErrorCodeFn *)PCNorm_Eisenstat));
197:   }
198:   if (!eis->usediag) PetscFunctionReturn(PETSC_SUCCESS);
199:   if (!pc->setupcalled) PetscCall(MatCreateVecs(pc->pmat, &eis->diag, NULL));
200:   PetscCall(MatGetDiagonal(pc->pmat, eis->diag));
201:   PetscFunctionReturn(PETSC_SUCCESS);
202: }

204: static PetscErrorCode PCEisenstatSetOmega_Eisenstat(PC pc, PetscReal omega)
205: {
206:   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;

208:   PetscFunctionBegin;
209:   PetscCheck(omega > 0.0 && omega < 2.0, PetscObjectComm((PetscObject)pc), PETSC_ERR_ARG_OUTOFRANGE, "Relaxation out of range");
210:   eis->omega = omega;
211:   PetscFunctionReturn(PETSC_SUCCESS);
212: }

214: static PetscErrorCode PCEisenstatSetNoDiagonalScaling_Eisenstat(PC pc, PetscBool flg)
215: {
216:   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;

218:   PetscFunctionBegin;
219:   eis->usediag = flg;
220:   PetscFunctionReturn(PETSC_SUCCESS);
221: }

223: static PetscErrorCode PCEisenstatGetOmega_Eisenstat(PC pc, PetscReal *omega)
224: {
225:   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;

227:   PetscFunctionBegin;
228:   *omega = eis->omega;
229:   PetscFunctionReturn(PETSC_SUCCESS);
230: }

232: static PetscErrorCode PCEisenstatGetNoDiagonalScaling_Eisenstat(PC pc, PetscBool *flg)
233: {
234:   PC_Eisenstat *eis = (PC_Eisenstat *)pc->data;

236:   PetscFunctionBegin;
237:   *flg = eis->usediag;
238:   PetscFunctionReturn(PETSC_SUCCESS);
239: }

241: /*@
242:   PCEisenstatSetOmega - Sets the SSOR relaxation coefficient, omega,
243:   to use with Eisenstat's trick (where omega = 1.0 by default)

245:   Logically Collective

247:   Input Parameters:
248: + pc    - the preconditioner context
249: - omega - relaxation coefficient (0 < omega < 2)

251:   Options Database Key:
252: . -pc_eisenstat_omega omega - Sets omega

254:   Level: intermediate

256:   Notes:
257:   The Eisenstat trick implementation of SSOR requires about 50% of the
258:   usual amount of floating point operations used for SSOR + Krylov method;
259:   however, the preconditioned problem must be solved with both left
260:   and right preconditioning.

262:   To use SSOR without the Eisenstat trick, employ the `PCSOR` preconditioner,
263:   which can be chosen with the database options `-pc_type sor -pc_sor_symmetric`

265: .seealso: [](ch_ksp), `PCSORSetOmega()`, `PCEISENSTAT`
266: @*/
267: PetscErrorCode PCEisenstatSetOmega(PC pc, PetscReal omega)
268: {
269:   PetscFunctionBegin;
272:   PetscTryMethod(pc, "PCEisenstatSetOmega_C", (PC, PetscReal), (pc, omega));
273:   PetscFunctionReturn(PETSC_SUCCESS);
274: }

276: /*@
277:   PCEisenstatSetNoDiagonalScaling - Causes the Eisenstat preconditioner, `PCEISENSTAT`
278:   not to do additional diagonal preconditioning. For matrices with a constant
279:   along the diagonal, this may save a small amount of work.

281:   Logically Collective

283:   Input Parameters:
284: + pc  - the preconditioner context
285: - flg - `PETSC_TRUE` turns off diagonal scaling inside the algorithm

287:   Options Database Key:
288: . -pc_eisenstat_no_diagonal_scaling - Activates `PCEisenstatSetNoDiagonalScaling()`

290:   Level: intermediate

292:   Note:
293:   If you use the `KSPSetDiagonalScaling()` or -ksp_diagonal_scale option then you will
294:   likely want to use this routine since it will save you some unneeded flops.

296: .seealso: [](ch_ksp), `PCEisenstatSetOmega()`, `PCEISENSTAT`
297: @*/
298: PetscErrorCode PCEisenstatSetNoDiagonalScaling(PC pc, PetscBool flg)
299: {
300:   PetscFunctionBegin;
302:   PetscTryMethod(pc, "PCEisenstatSetNoDiagonalScaling_C", (PC, PetscBool), (pc, flg));
303:   PetscFunctionReturn(PETSC_SUCCESS);
304: }

306: /*@
307:   PCEisenstatGetOmega - Gets the SSOR relaxation coefficient, omega,
308:   to use with Eisenstat's trick (where omega = 1.0 by default).

310:   Logically Collective

312:   Input Parameter:
313: . pc - the preconditioner context

315:   Output Parameter:
316: . omega - relaxation coefficient (0 < omega < 2)

318:   Options Database Key:
319: . -pc_eisenstat_omega omega - Sets omega

321:   Notes:
322:   The Eisenstat trick implementation of SSOR requires about 50% of the
323:   usual amount of floating point operations used for SSOR + Krylov method;
324:   however, the preconditioned problem must be solved with both left
325:   and right preconditioning.

327:   To use SSOR without the Eisenstat trick, employ the PCSOR preconditioner,
328:   which can be chosen with the database options `-pc_type sor -pc_sor_symmetric`

330:   Level: intermediate

332: .seealso: [](ch_ksp), `PCEISENSTAT`, `PCSORGetOmega()`, `PCEisenstatSetOmega()`
333: @*/
334: PetscErrorCode PCEisenstatGetOmega(PC pc, PetscReal *omega)
335: {
336:   PetscFunctionBegin;
338:   PetscUseMethod(pc, "PCEisenstatGetOmega_C", (PC, PetscReal *), (pc, omega));
339:   PetscFunctionReturn(PETSC_SUCCESS);
340: }

342: /*@
343:   PCEisenstatGetNoDiagonalScaling - Tells if the Eisenstat preconditioner
344:   not to do additional diagonal preconditioning. For matrices with a constant
345:   along the diagonal, this may save a small amount of work.

347:   Logically Collective

349:   Input Parameter:
350: . pc - the preconditioner context

352:   Output Parameter:
353: . flg - `PETSC_TRUE` means there is no diagonal scaling applied

355:   Options Database Key:
356: . -pc_eisenstat_no_diagonal_scaling - Activates `PCEisenstatSetNoDiagonalScaling()`

358:   Level: intermediate

360:   Note:
361:   If you use the KSPSetDiagonalScaling() or -ksp_diagonal_scale option then you will
362:   likely want to use this routine since it will save you some unneeded flops.

364: .seealso: `PCEISENSTAT`, `PCEisenstatGetOmega()`
365: @*/
366: PetscErrorCode PCEisenstatGetNoDiagonalScaling(PC pc, PetscBool *flg)
367: {
368:   PetscFunctionBegin;
370:   PetscUseMethod(pc, "PCEisenstatGetNoDiagonalScaling_C", (PC, PetscBool *), (pc, flg));
371:   PetscFunctionReturn(PETSC_SUCCESS);
372: }

374: static PetscErrorCode PCPreSolveChangeRHS_Eisenstat(PC pc, PetscBool *change)
375: {
376:   PetscFunctionBegin;
377:   *change = PETSC_TRUE;
378:   PetscFunctionReturn(PETSC_SUCCESS);
379: }

381: /*MC
382:      PCEISENSTAT - An implementation of SSOR (symmetric successive over relaxation, symmetric Gauss-Seidel)
383:                    preconditioning that incorporates Eisenstat's trick to reduce the amount of computation needed.

385:    Options Database Keys:
386: +  -pc_eisenstat_omega omega         - Sets omega
387: -  -pc_eisenstat_no_diagonal_scaling - Activates `PCEisenstatSetNoDiagonalScaling()`

389:    Level: beginner

391:    Notes:
392:    Only implemented for the `MATAIJ` matrix format.

394:    Not a true parallel SOR, in parallel this implementation corresponds to block Jacobi with SOR on each block.

396:    Developer Note:
397:    Since this algorithm runs the Krylov method on a transformed linear system the implementation provides `PCPreSolve()` and `PCPostSolve()`
398:    routines that `KSP` uses to set up the transformed linear system.

400: .seealso: [](ch_ksp), `PCCreate()`, `PCSetType()`, `PCType`, `PC`, `PCEisenstatGetOmega()`,
401:           `PCEisenstatSetNoDiagonalScaling()`, `PCEisenstatSetOmega()`, `PCSOR`
402: M*/

404: PETSC_EXTERN PetscErrorCode PCCreate_Eisenstat(PC pc)
405: {
406:   PC_Eisenstat *eis;

408:   PetscFunctionBegin;
409:   PetscCall(PetscNew(&eis));

411:   pc->ops->apply           = PCApply_Eisenstat;
412:   pc->ops->applytranspose  = PCApplyTranspose_Eisenstat;
413:   pc->ops->presolve        = PCPreSolve_Eisenstat;
414:   pc->ops->postsolve       = PCPostSolve_Eisenstat;
415:   pc->ops->applyrichardson = NULL;
416:   pc->ops->setfromoptions  = PCSetFromOptions_Eisenstat;
417:   pc->ops->destroy         = PCDestroy_Eisenstat;
418:   pc->ops->reset           = PCReset_Eisenstat;
419:   pc->ops->view            = PCView_Eisenstat;
420:   pc->ops->setup           = PCSetUp_Eisenstat;

422:   pc->data     = eis;
423:   eis->omega   = 1.0;
424:   eis->b[0]    = NULL;
425:   eis->b[1]    = NULL;
426:   eis->diag    = NULL;
427:   eis->usediag = PETSC_TRUE;

429:   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatSetOmega_C", PCEisenstatSetOmega_Eisenstat));
430:   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatSetNoDiagonalScaling_C", PCEisenstatSetNoDiagonalScaling_Eisenstat));
431:   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatGetOmega_C", PCEisenstatGetOmega_Eisenstat));
432:   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCEisenstatGetNoDiagonalScaling_C", PCEisenstatGetNoDiagonalScaling_Eisenstat));
433:   PetscCall(PetscObjectComposeFunction((PetscObject)pc, "PCPreSolveChangeRHS_C", PCPreSolveChangeRHS_Eisenstat));
434:   PetscFunctionReturn(PETSC_SUCCESS);
435: }