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