1 #include <../src/snes/impls/ncg/snesncgimpl.h> /*I "petscsnes.h" I*/ 2 const char *const SNESNCGTypes[] = {"FR", "PRP", "HS", "DY", "CD", "SNESNCGType", "SNES_NCG_", NULL}; 3 4 static PetscErrorCode SNESReset_NCG(SNES snes) 5 { 6 PetscFunctionBegin; 7 PetscFunctionReturn(PETSC_SUCCESS); 8 } 9 10 static PetscErrorCode SNESDestroy_NCG(SNES snes) 11 { 12 PetscFunctionBegin; 13 PetscCall(PetscObjectComposeFunction((PetscObject)snes, "SNESNCGSetType_C", NULL)); 14 PetscCall(PetscFree(snes->data)); 15 PetscFunctionReturn(PETSC_SUCCESS); 16 } 17 18 static PetscErrorCode SNESSetUp_NCG(SNES snes) 19 { 20 PetscFunctionBegin; 21 PetscCall(SNESSetWorkVecs(snes, 2)); 22 PetscCheck(snes->npcside != PC_RIGHT, PetscObjectComm((PetscObject)snes), PETSC_ERR_ARG_WRONGSTATE, "SNESNCG only supports left preconditioning"); 23 if (snes->functype == SNES_FUNCTION_DEFAULT) snes->functype = SNES_FUNCTION_UNPRECONDITIONED; 24 PetscFunctionReturn(PETSC_SUCCESS); 25 } 26 27 static PetscErrorCode SNESLineSearchApply_NCGLinear(SNESLineSearch linesearch) 28 { 29 PetscScalar alpha, ptAp; 30 Vec X, Y, F, W; 31 SNES snes; 32 PetscReal *fnorm, *xnorm, *ynorm; 33 34 PetscFunctionBegin; 35 PetscCall(SNESLineSearchGetSNES(linesearch, &snes)); 36 X = linesearch->vec_sol; 37 W = linesearch->vec_sol_new; 38 F = linesearch->vec_func; 39 Y = linesearch->vec_update; 40 fnorm = &linesearch->fnorm; 41 xnorm = &linesearch->xnorm; 42 ynorm = &linesearch->ynorm; 43 44 if (!snes->jacobian) PetscCall(SNESSetUpMatrices(snes)); 45 46 /* 47 The exact step size for unpreconditioned linear CG is just: 48 alpha = (r, r) / (p, Ap) = (f, f) / (y, Jy) 49 */ 50 PetscCall(SNESComputeJacobian(snes, X, snes->jacobian, snes->jacobian_pre)); 51 PetscCall(VecDot(F, F, &alpha)); 52 PetscCall(MatMult(snes->jacobian, Y, W)); 53 PetscCall(VecDot(Y, W, &ptAp)); 54 alpha = alpha / ptAp; 55 PetscCall(VecAXPY(X, -alpha, Y)); 56 PetscCall(SNESComputeFunction(snes, X, F)); 57 58 PetscCall(VecNorm(F, NORM_2, fnorm)); 59 PetscCall(VecNorm(X, NORM_2, xnorm)); 60 PetscCall(VecNorm(Y, NORM_2, ynorm)); 61 PetscFunctionReturn(PETSC_SUCCESS); 62 } 63 64 /*MC 65 SNESLINESEARCHNCGLINEAR - Special line search only for the nonlinear CG solver `SNESNCG` 66 67 This line search uses the length "as if" the problem is linear (that is what is computed by the linear CG method) using the Jacobian of the function. 68 alpha = (r, r) / (p, Ap) = (f, f) / (y, Jy) where r (f) is the current residual (function value), p (y) is the current search direction. 69 70 Notes: 71 This requires a Jacobian-vector product but does not require the solution of a linear system with the Jacobian 72 73 This is a "odd-ball" line search, we don't know if it is in the literature or used in practice by anyone. 74 75 Level: advanced 76 77 .seealso: `SNESLineSearchCreate()`, `SNESLineSearchSetType()` 78 M*/ 79 80 PETSC_EXTERN PetscErrorCode SNESLineSearchCreate_NCGLinear(SNESLineSearch linesearch) 81 { 82 PetscFunctionBegin; 83 linesearch->ops->apply = SNESLineSearchApply_NCGLinear; 84 linesearch->ops->destroy = NULL; 85 linesearch->ops->setfromoptions = NULL; 86 linesearch->ops->reset = NULL; 87 linesearch->ops->view = NULL; 88 linesearch->ops->setup = NULL; 89 PetscFunctionReturn(PETSC_SUCCESS); 90 } 91 92 static PetscErrorCode SNESSetFromOptions_NCG(SNES snes, PetscOptionItems *PetscOptionsObject) 93 { 94 SNES_NCG *ncg = (SNES_NCG *)snes->data; 95 PetscBool debug = PETSC_FALSE; 96 SNESNCGType ncgtype = ncg->type; 97 SNESLineSearch linesearch; 98 99 PetscFunctionBegin; 100 PetscOptionsHeadBegin(PetscOptionsObject, "SNES NCG options"); 101 PetscCall(PetscOptionsBool("-snes_ncg_monitor", "Monitor the beta values used in the NCG iterations", "SNES", ncg->monitor ? PETSC_TRUE : PETSC_FALSE, &debug, NULL)); 102 if (debug) ncg->monitor = PETSC_VIEWER_STDOUT_(PetscObjectComm((PetscObject)snes)); 103 PetscCall(PetscOptionsEnum("-snes_ncg_type", "NCG Beta type used", "SNESNCGSetType", SNESNCGTypes, (PetscEnum)ncg->type, (PetscEnum *)&ncgtype, NULL)); 104 PetscCall(SNESNCGSetType(snes, ncgtype)); 105 PetscOptionsHeadEnd(); 106 if (!snes->linesearch) { 107 PetscCall(SNESGetLineSearch(snes, &linesearch)); 108 if (!((PetscObject)linesearch)->type_name) { 109 if (!snes->npc) { 110 PetscCall(SNESLineSearchSetType(linesearch, SNESLINESEARCHCP)); 111 } else { 112 PetscCall(SNESLineSearchSetType(linesearch, SNESLINESEARCHL2)); 113 } 114 } 115 } 116 PetscFunctionReturn(PETSC_SUCCESS); 117 } 118 119 static PetscErrorCode SNESView_NCG(SNES snes, PetscViewer viewer) 120 { 121 SNES_NCG *ncg = (SNES_NCG *)snes->data; 122 PetscBool iascii; 123 124 PetscFunctionBegin; 125 PetscCall(PetscObjectTypeCompare((PetscObject)viewer, PETSCVIEWERASCII, &iascii)); 126 if (iascii) PetscCall(PetscViewerASCIIPrintf(viewer, " type: %s\n", SNESNCGTypes[ncg->type])); 127 PetscFunctionReturn(PETSC_SUCCESS); 128 } 129 130 /* 131 132 Assuming F = SNESComputeFunction(X) compute Y^tJ^tF using a simple secant approximation of the jacobian. 133 134 This routine is not currently used. I don't know what its intended purpose is. 135 136 Note it has a hardwired differencing parameter of 1e-5 137 138 */ 139 PetscErrorCode SNESNCGComputeYtJtF_Private(SNES snes, Vec X, Vec F, Vec Y, Vec W, Vec G, PetscScalar *ytJtf) 140 { 141 PetscScalar ftf, ftg, fty, h; 142 143 PetscFunctionBegin; 144 PetscCall(VecDot(F, F, &ftf)); 145 PetscCall(VecDot(F, Y, &fty)); 146 h = 1e-5 * fty / fty; 147 PetscCall(VecCopy(X, W)); 148 PetscCall(VecAXPY(W, -h, Y)); /* this is arbitrary */ 149 PetscCall(SNESComputeFunction(snes, W, G)); 150 PetscCall(VecDot(G, F, &ftg)); 151 *ytJtf = (ftg - ftf) / h; 152 PetscFunctionReturn(PETSC_SUCCESS); 153 } 154 155 /*@ 156 SNESNCGSetType - Sets the conjugate update type for nonlinear CG `SNESNCG`. 157 158 Logically Collective 159 160 Input Parameters: 161 + snes - the iterative context 162 - btype - update type 163 164 Options Database Key: 165 . -snes_ncg_type <prp,fr,hs,dy,cd> -strategy for selecting algorithm for computing beta 166 167 Level: intermediate 168 169 `SNESNCGType`s: 170 + `SNES_NCG_FR` - Fletcher-Reeves update 171 . `SNES_NCG_PRP` - Polak-Ribiere-Polyak update 172 . `SNES_NCG_HS` - Hestenes-Steifel update 173 . `SNES_NCG_DY` - Dai-Yuan update 174 - `SNES_NCG_CD` - Conjugate Descent update 175 176 Notes: 177 `SNES_NCG_PRP` is the default, and the only one that tolerates generalized search directions. 178 179 It is not clear what "generalized search directions" means, does it mean use with a nonlinear preconditioner, 180 that is using -npc_snes_type <type>, `SNESSetNPC()`, or `SNESGetNPC()`? 181 182 Developer Note: 183 There should be a `SNESNCGSetType()` 184 185 .seealso: `SNESNCGType`, `SNES_NCG_FR`, `SNES_NCG_PRP`, `SNES_NCG_HS`, `SNES_NCG_DY`, `SNES_NCG_CD` 186 @*/ 187 PetscErrorCode SNESNCGSetType(SNES snes, SNESNCGType btype) 188 { 189 PetscFunctionBegin; 190 PetscValidHeaderSpecific(snes, SNES_CLASSID, 1); 191 PetscTryMethod(snes, "SNESNCGSetType_C", (SNES, SNESNCGType), (snes, btype)); 192 PetscFunctionReturn(PETSC_SUCCESS); 193 } 194 195 static PetscErrorCode SNESNCGSetType_NCG(SNES snes, SNESNCGType btype) 196 { 197 SNES_NCG *ncg = (SNES_NCG *)snes->data; 198 199 PetscFunctionBegin; 200 ncg->type = btype; 201 PetscFunctionReturn(PETSC_SUCCESS); 202 } 203 204 /* 205 SNESSolve_NCG - Solves a nonlinear system with the Nonlinear Conjugate Gradient method. 206 207 Input Parameter: 208 . snes - the `SNES` context 209 210 Output Parameter: 211 . outits - number of iterations until termination 212 213 Application Interface Routine: SNESSolve() 214 */ 215 static PetscErrorCode SNESSolve_NCG(SNES snes) 216 { 217 SNES_NCG *ncg = (SNES_NCG *)snes->data; 218 Vec X, dX, lX, F, dXold; 219 PetscReal fnorm, ynorm, xnorm, beta = 0.0; 220 PetscScalar dXdotdX, dXolddotdXold, dXdotdXold, lXdotdX, lXdotdXold; 221 PetscInt maxits, i; 222 SNESLineSearchReason lsresult = SNES_LINESEARCH_SUCCEEDED; 223 SNESLineSearch linesearch; 224 SNESConvergedReason reason; 225 226 PetscFunctionBegin; 227 PetscCheck(!snes->xl && !snes->xu && !snes->ops->computevariablebounds, PetscObjectComm((PetscObject)snes), PETSC_ERR_ARG_WRONGSTATE, "SNES solver %s does not support bounds", ((PetscObject)snes)->type_name); 228 229 PetscCall(PetscCitationsRegister(SNESCitation, &SNEScite)); 230 snes->reason = SNES_CONVERGED_ITERATING; 231 232 maxits = snes->max_its; /* maximum number of iterations */ 233 X = snes->vec_sol; /* X^n */ 234 dXold = snes->work[0]; /* The previous iterate of X */ 235 dX = snes->work[1]; /* the preconditioned direction */ 236 lX = snes->vec_sol_update; /* the conjugate direction */ 237 F = snes->vec_func; /* residual vector */ 238 239 PetscCall(SNESGetLineSearch(snes, &linesearch)); 240 241 PetscCall(PetscObjectSAWsTakeAccess((PetscObject)snes)); 242 snes->iter = 0; 243 snes->norm = 0.; 244 PetscCall(PetscObjectSAWsGrantAccess((PetscObject)snes)); 245 246 /* compute the initial function and preconditioned update dX */ 247 248 if (snes->npc && snes->functype == SNES_FUNCTION_PRECONDITIONED) { 249 PetscCall(SNESApplyNPC(snes, X, NULL, dX)); 250 PetscCall(SNESGetConvergedReason(snes->npc, &reason)); 251 if (reason < 0 && reason != SNES_DIVERGED_MAX_IT) { 252 snes->reason = SNES_DIVERGED_INNER; 253 PetscFunctionReturn(PETSC_SUCCESS); 254 } 255 PetscCall(VecCopy(dX, F)); 256 PetscCall(VecNorm(F, NORM_2, &fnorm)); 257 } else { 258 if (!snes->vec_func_init_set) { 259 PetscCall(SNESComputeFunction(snes, X, F)); 260 } else snes->vec_func_init_set = PETSC_FALSE; 261 262 /* convergence test */ 263 PetscCall(VecNorm(F, NORM_2, &fnorm)); 264 SNESCheckFunctionNorm(snes, fnorm); 265 PetscCall(VecCopy(F, dX)); 266 } 267 if (snes->npc) { 268 if (snes->functype == SNES_FUNCTION_UNPRECONDITIONED) { 269 PetscCall(SNESApplyNPC(snes, X, F, dX)); 270 PetscCall(SNESGetConvergedReason(snes->npc, &reason)); 271 if (reason < 0 && reason != SNES_DIVERGED_MAX_IT) { 272 snes->reason = SNES_DIVERGED_INNER; 273 PetscFunctionReturn(PETSC_SUCCESS); 274 } 275 } 276 } 277 PetscCall(VecCopy(dX, lX)); 278 PetscCall(VecDot(dX, dX, &dXdotdX)); 279 280 PetscCall(PetscObjectSAWsTakeAccess((PetscObject)snes)); 281 snes->norm = fnorm; 282 PetscCall(PetscObjectSAWsGrantAccess((PetscObject)snes)); 283 PetscCall(SNESLogConvergenceHistory(snes, fnorm, 0)); 284 285 /* test convergence */ 286 PetscCall(SNESConverged(snes, 0, 0.0, 0.0, fnorm)); 287 PetscCall(SNESMonitor(snes, 0, fnorm)); 288 if (snes->reason) PetscFunctionReturn(PETSC_SUCCESS); 289 290 /* Call general purpose update function */ 291 PetscTryTypeMethod(snes, update, snes->iter); 292 293 /* first update -- just use the (preconditioned) residual direction for the initial conjugate direction */ 294 295 for (i = 1; i < maxits + 1; i++) { 296 /* some update types require the old update direction or conjugate direction */ 297 if (ncg->type != SNES_NCG_FR) PetscCall(VecCopy(dX, dXold)); 298 PetscCall(SNESLineSearchApply(linesearch, X, F, &fnorm, lX)); 299 PetscCall(SNESLineSearchGetReason(linesearch, &lsresult)); 300 PetscCall(SNESLineSearchGetNorms(linesearch, &xnorm, &fnorm, &ynorm)); 301 if (lsresult) { 302 if (++snes->numFailures >= snes->maxFailures) { 303 snes->reason = SNES_DIVERGED_LINE_SEARCH; 304 PetscFunctionReturn(PETSC_SUCCESS); 305 } 306 } 307 if (snes->nfuncs >= snes->max_funcs && snes->max_funcs >= 0) { 308 snes->reason = SNES_DIVERGED_FUNCTION_COUNT; 309 PetscFunctionReturn(PETSC_SUCCESS); 310 } 311 /* Monitor convergence */ 312 PetscCall(PetscObjectSAWsTakeAccess((PetscObject)snes)); 313 snes->iter = i; 314 snes->norm = fnorm; 315 snes->xnorm = xnorm; 316 snes->ynorm = ynorm; 317 PetscCall(PetscObjectSAWsGrantAccess((PetscObject)snes)); 318 PetscCall(SNESLogConvergenceHistory(snes, snes->norm, 0)); 319 320 /* Test for convergence */ 321 PetscCall(SNESConverged(snes, snes->iter, xnorm, ynorm, fnorm)); 322 PetscCall(SNESMonitor(snes, snes->iter, snes->norm)); 323 if (snes->reason) PetscFunctionReturn(PETSC_SUCCESS); 324 325 /* Call general purpose update function */ 326 PetscTryTypeMethod(snes, update, snes->iter); 327 if (snes->npc) { 328 if (snes->functype == SNES_FUNCTION_PRECONDITIONED) { 329 PetscCall(SNESApplyNPC(snes, X, NULL, dX)); 330 PetscCall(SNESGetConvergedReason(snes->npc, &reason)); 331 if (reason < 0 && reason != SNES_DIVERGED_MAX_IT) { 332 snes->reason = SNES_DIVERGED_INNER; 333 PetscFunctionReturn(PETSC_SUCCESS); 334 } 335 PetscCall(VecCopy(dX, F)); 336 } else { 337 PetscCall(SNESApplyNPC(snes, X, F, dX)); 338 PetscCall(SNESGetConvergedReason(snes->npc, &reason)); 339 if (reason < 0 && reason != SNES_DIVERGED_MAX_IT) { 340 snes->reason = SNES_DIVERGED_INNER; 341 PetscFunctionReturn(PETSC_SUCCESS); 342 } 343 } 344 } else { 345 PetscCall(VecCopy(F, dX)); 346 } 347 348 /* compute the conjugate direction lX = dX + beta*lX with beta = ((dX, dX) / (dX_old, dX_old) (Fletcher-Reeves update)*/ 349 switch (ncg->type) { 350 case SNES_NCG_FR: /* Fletcher-Reeves */ 351 dXolddotdXold = dXdotdX; 352 PetscCall(VecDot(dX, dX, &dXdotdX)); 353 beta = PetscRealPart(dXdotdX / dXolddotdXold); 354 break; 355 case SNES_NCG_PRP: /* Polak-Ribiere-Poylak */ 356 dXolddotdXold = dXdotdX; 357 PetscCall(VecDotBegin(dX, dX, &dXdotdXold)); 358 PetscCall(VecDotBegin(dXold, dX, &dXdotdXold)); 359 PetscCall(VecDotEnd(dX, dX, &dXdotdX)); 360 PetscCall(VecDotEnd(dXold, dX, &dXdotdXold)); 361 beta = PetscRealPart(((dXdotdX - dXdotdXold) / dXolddotdXold)); 362 if (beta < 0.0) beta = 0.0; /* restart */ 363 break; 364 case SNES_NCG_HS: /* Hestenes-Stiefel */ 365 PetscCall(VecDotBegin(dX, dX, &dXdotdX)); 366 PetscCall(VecDotBegin(dX, dXold, &dXdotdXold)); 367 PetscCall(VecDotBegin(lX, dX, &lXdotdX)); 368 PetscCall(VecDotBegin(lX, dXold, &lXdotdXold)); 369 PetscCall(VecDotEnd(dX, dX, &dXdotdX)); 370 PetscCall(VecDotEnd(dX, dXold, &dXdotdXold)); 371 PetscCall(VecDotEnd(lX, dX, &lXdotdX)); 372 PetscCall(VecDotEnd(lX, dXold, &lXdotdXold)); 373 beta = PetscRealPart((dXdotdX - dXdotdXold) / (lXdotdX - lXdotdXold)); 374 break; 375 case SNES_NCG_DY: /* Dai-Yuan */ 376 PetscCall(VecDotBegin(dX, dX, &dXdotdX)); 377 PetscCall(VecDotBegin(lX, dX, &lXdotdX)); 378 PetscCall(VecDotBegin(lX, dXold, &lXdotdXold)); 379 PetscCall(VecDotEnd(dX, dX, &dXdotdX)); 380 PetscCall(VecDotEnd(lX, dX, &lXdotdX)); 381 PetscCall(VecDotEnd(lX, dXold, &lXdotdXold)); 382 beta = PetscRealPart(dXdotdX / (lXdotdXold - lXdotdX)); 383 break; 384 case SNES_NCG_CD: /* Conjugate Descent */ 385 PetscCall(VecDotBegin(dX, dX, &dXdotdX)); 386 PetscCall(VecDotBegin(lX, dXold, &lXdotdXold)); 387 PetscCall(VecDotEnd(dX, dX, &dXdotdX)); 388 PetscCall(VecDotEnd(lX, dXold, &lXdotdXold)); 389 beta = PetscRealPart(dXdotdX / lXdotdXold); 390 break; 391 } 392 if (ncg->monitor) PetscCall(PetscViewerASCIIPrintf(ncg->monitor, "beta = %e\n", (double)beta)); 393 PetscCall(VecAYPX(lX, beta, dX)); 394 } 395 PetscFunctionReturn(PETSC_SUCCESS); 396 } 397 398 /*MC 399 SNESNCG - Nonlinear Conjugate-Gradient method for the solution of nonlinear systems. 400 401 Level: beginner 402 403 Options Database Keys: 404 + -snes_ncg_type <fr, prp, dy, hs, cd> - Choice of conjugate-gradient update parameter, default is prp. 405 . -snes_linesearch_type <cp,l2,basic> - Line search type. 406 - -snes_ncg_monitor - Print the beta values used in the ncg iteration, . 407 408 Notes: 409 This solves the nonlinear system of equations F(x) = 0 using the nonlinear generalization of the conjugate 410 gradient method. This may be used with a nonlinear preconditioner used to pick the new search directions, but otherwise 411 chooses the initial search direction as F(x) for the initial guess x. 412 413 Only supports left non-linear preconditioning. 414 415 Default line search is `SNESLINESEARCHCP`, unless a nonlinear preconditioner is used with -npc_snes_type <type>, `SNESSetNPC()`, or `SNESGetNPC()` then 416 `SNESLINESEARCHL2` is used. Also supports the special purpose line search `SNESLINESEARCHNCGLINEAR` 417 418 References: 419 . * - Peter R. Brune, Matthew G. Knepley, Barry F. Smith, and Xuemin Tu,"Composing Scalable Nonlinear Algebraic Solvers", 420 SIAM Review, 57(4), 2015 421 422 .seealso: `SNESCreate()`, `SNES`, `SNESSetType()`, `SNESNEWTONLS`, `SNESNEWTONTR`, `SNESNGMRES`, `SNESQN`, `SNESLINESEARCHNCGLINEAR`, `SNESNCGSetType()`, `SNESLineSearchSetType()` 423 M*/ 424 PETSC_EXTERN PetscErrorCode SNESCreate_NCG(SNES snes) 425 { 426 SNES_NCG *neP; 427 428 PetscFunctionBegin; 429 snes->ops->destroy = SNESDestroy_NCG; 430 snes->ops->setup = SNESSetUp_NCG; 431 snes->ops->setfromoptions = SNESSetFromOptions_NCG; 432 snes->ops->view = SNESView_NCG; 433 snes->ops->solve = SNESSolve_NCG; 434 snes->ops->reset = SNESReset_NCG; 435 436 snes->usesksp = PETSC_FALSE; 437 snes->usesnpc = PETSC_TRUE; 438 snes->npcside = PC_LEFT; 439 440 snes->alwayscomputesfinalresidual = PETSC_TRUE; 441 442 if (!snes->tolerancesset) { 443 snes->max_funcs = 30000; 444 snes->max_its = 10000; 445 snes->stol = 1e-20; 446 } 447 448 PetscCall(PetscNew(&neP)); 449 snes->data = (void *)neP; 450 neP->monitor = NULL; 451 neP->type = SNES_NCG_PRP; 452 PetscCall(PetscObjectComposeFunction((PetscObject)snes, "SNESNCGSetType_C", SNESNCGSetType_NCG)); 453 PetscFunctionReturn(PETSC_SUCCESS); 454 } 455