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