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