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