xref: /petsc/src/snes/impls/ncg/snesncg.c (revision ebead697dbf761eb322f829370bbe90b3bd93fa3)
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