xref: /petsc/src/snes/impls/ls/ls.c (revision 28ae5a21eae2971b30316e042a3c7285afd33cce)
1 #ifndef lint
2 static char vcid[] = "$Id: ls.c,v 1.6 1995/04/17 03:33:14 curfman Exp curfman $";
3 #endif
4 
5 #include <math.h>
6 #include "ls.h"
7 
8 /*
9      Implements a line search variant of Newton's Method
10     for solving systems of nonlinear equations.
11 
12     Input parameters:
13 .   snes - nonlinear context obtained from SNESCreate()
14 
15     Output Parameters:
16 .   its  - Number of global iterations until termination.
17 
18     Notes:
19     See SNESCreate() and SNESSetUp() for information on the definition and
20     initialization of the nonlinear solver context.
21 
22     This implements essentially a truncated Newton method with a
23     line search.  By default a cubic backtracking line search
24     is employed, as described in the text "Numerical Methods for
25     Unconstrained Optimization and Nonlinear Equations" by Dennis
26     and Schnabel.  See the examples in src/snes/examples.
27 */
28 
29 int SNESSolve_LS( SNES snes, int *outits )
30 {
31   SNES_LS *neP = (SNES_LS *) snes->data;
32   int     maxits, i, history_len,ierr,lits;
33   double  fnorm, gnorm, xnorm, ynorm, *history;
34   Vec     Y, X, F, G, W, TMP;
35 
36   history	= snes->conv_hist;	/* convergence history */
37   history_len	= snes->conv_hist_len;	/* convergence history length */
38   maxits	= snes->max_its;	/* maximum number of iterations */
39   X		= snes->vec_sol;		/* solution vector */
40   F		= snes->vec_res;		/* residual vector */
41   Y		= snes->work[0];		/* work vectors */
42   G		= snes->work[1];
43   W		= snes->work[2];
44 
45   ierr = SNESComputeInitialGuess(snes,X); CHKERR(ierr);  /* X <- X_0 */
46   VecNorm( X, &xnorm );		       /* xnorm = || X || */
47   ierr = SNESComputeResidual(snes,X,F); CHKERR(ierr); /* (+/-) F(X) */
48   VecNorm(F, &fnorm );	        	/* fnorm <- || F || */
49   snes->norm = fnorm;
50   if (history && history_len > 0) history[0] = fnorm;
51   if (snes->Monitor) (*snes->Monitor)(snes,0,X,F,fnorm,snes->monP);
52 
53   for ( i=0; i<maxits; i++ ) {
54        snes->iter = i+1;
55 
56        /* Solve J Y = -F, where J is Jacobian matrix */
57        (*snes->ComputeJacobian)(X,&snes->jacobian,snes->jacP);
58        ierr = SLESSetOperators(snes->sles,snes->jacobian,snes->jacobian,0);
59        ierr = SLESSolve(snes->sles,F,Y,&lits); CHKERR(ierr);
60        ierr = (*neP->LineSearch)(snes, X, F, G, Y, W, fnorm, &ynorm, &gnorm );
61 
62        TMP = F; F = G; G = TMP;
63        TMP = X; X = Y; Y = TMP;
64        fnorm = gnorm;
65 
66        snes->norm = fnorm;
67        if (history && history_len > i+1) history[i+1] = fnorm;
68        VecNorm( X, &xnorm );		/* xnorm = || X || */
69        if (snes->Monitor) (*snes->Monitor)(snes,i+1,X,F,fnorm,snes->monP);
70 
71        /* Test for convergence */
72        if ((*snes->Converged)( snes, xnorm, ynorm, fnorm,snes->cnvP )) {
73            if (X != snes->vec_sol) VecCopy( X, snes->vec_sol );
74            break;
75        }
76   }
77   if (i == maxits) i--;
78   *outits = i+1;
79   return 0;
80 }
81 /* ------------------------------------------------------------ */
82 /*ARGSUSED*/
83 int SNESSetUp_LS(SNES snes )
84 {
85   int ierr;
86   snes->nwork = 3;
87   ierr = VecGetVecs( snes->vec_sol, snes->nwork,&snes->work ); CHKERR(ierr);
88   PLogObjectParents(snes,snes->nwork,snes->work );
89   return 0;
90 }
91 /* ------------------------------------------------------------ */
92 /*ARGSUSED*/
93 int SNESDestroy_LS(PetscObject obj)
94 {
95   SNES snes = (SNES) obj;
96   SLESDestroy(snes->sles);
97   VecFreeVecs(snes->work, snes->nwork );
98   PLogObjectDestroy(obj);
99   PETSCHEADERDESTROY(obj);
100   return 0;
101 }
102 /*@
103    SNESDefaultMonitor - Default SNES monitoring routine.  Prints the
104    residual norm at each iteration.
105 
106    Input Parameters:
107 .  snes - the SNES context
108 .  its - iteration number
109 .  x - current iterate
110 .  f - current residual (+/-)
111 .  fnorm - 2-norm residual value (may be estimated)
112 .  dummy - unused context
113 
114    Notes:
115    f is either the residual or its negative, depending on the user's
116    preference, as set with SNESSetResidual().
117 
118 .keywords: SNES, nonlinear, default, monitor, residual, norm
119 
120 .seealso: SNESSetMonitor()
121 @*/
122 int SNESDefaultMonitor(SNES snes,int its, Vec x,Vec f,double fnorm,void *dummy)
123 {
124   fprintf( stdout, "iter = %d, residual norm %g \n",its,fnorm);
125   return 0;
126 }
127 
128 /*@
129    SNESDefaultConverged - Default test for monitoring the convergence
130    of the SNES solvers.
131 
132    Input Parameters:
133 .  snes - the SNES context
134 .  xnorm - 2-norm of current iterate
135 .  pnorm - 2-norm of current step
136 .  fnorm - 2-norm of residual
137 .  dummy - unused context
138 
139    Returns:
140 $  2  if  ( fnorm < atol ),
141 $  3  if  ( pnorm < xtol*xnorm ),
142 $ -2  if  ( nres > max_res ),
143 $  0  otherwise,
144 
145    where
146 $    atol    - absolute residual norm tolerance,
147 $              set with SNESSetAbsoluteTolerance()
148 $    max_res - maximum number of residual evaluations,
149 $              set with SNESSetMaxResidualEvaluations()
150 $    nres    - number of residual evaluations
151 $    xtol    - relative residual norm tolerance,
152 $              set with SNESSetRelativeTolerance()
153 
154 .keywords: SNES, nonlinear, default, converged, convergence
155 
156 .seealso: SNESSetConvergenceTest()
157 @*/
158 int SNESDefaultConverged(SNES snes,double xnorm,double pnorm,double fnorm,
159                          void *dummy)
160 {
161   if (fnorm < snes->atol) {
162     PLogInfo((PetscObject)snes,
163       "Converged due to absolute residual norm %g < %g\n",fnorm,snes->atol);
164     return 2;
165   }
166   if (pnorm < snes->xtol*(xnorm)) {
167     PLogInfo((PetscObject)snes,
168       "Converged due to small update length  %g < %g*%g\n",
169        pnorm,snes->xtol,xnorm);
170     return 3;
171   }
172   if (snes->nresids > snes->max_resids) {
173     PLogInfo((PetscObject)snes,
174       "Exceeded maximum number of residual evaluations: %d > %d\n",
175        snes->nresids, snes->max_resids );
176     return -2;
177   }
178   return 0;
179 }
180 
181 /* ------------------------------------------------------------ */
182 /*ARGSUSED*/
183 /*@
184    SNESNoLineSearch - This routine is not a line search at all;
185    it simply uses the full Newton step.  Thus, this routine is intended
186    to serve as a template and is not recommended for general use.
187 
188    Input Parameters:
189 .  snes - nonlinear context
190 .  x - current iterate
191 .  f - residual evaluated at x
192 .  y - search direction (contains new iterate on output)
193 .  w - work vector
194 .  fnorm - 2-norm of f
195 
196    Output parameters:
197 .  g - residual evaluated at new iterate y
198 .  y - new iterate (contains search direction on input)
199 .  gnorm - 2-norm of g
200 .  ynorm - 2-norm of search length
201 
202    Returns:
203    1, indicating success of the step.
204 
205 .keywords: SNES, nonlinear, line search, cubic
206 
207 .seealso: SNESCubicLineSearch()
208 @*/
209 int SNESNoLineSearch(SNES snes, Vec x, Vec f, Vec g, Vec y, Vec w,
210                              double fnorm, double *ynorm, double *gnorm )
211 {
212   int    ierr;
213   Scalar one = 1.0;
214   VecNorm(y, ynorm );	/* ynorm = || y ||    */
215   VecAXPY(&one, x, y );	/* y <- x + y         */
216   ierr = SNESComputeResidual(snes,y,g); CHKERR(ierr);
217   VecNorm( g, gnorm ); 	/* gnorm = || g ||    */
218   return 1;
219 }
220 /* ------------------------------------------------------------------ */
221 /*@
222    SNESCubicLineSearch - This routine performs a cubic line search.
223 
224    Input Parameters:
225 .  snes - nonlinear context
226 .  x - current iterate
227 .  f - residual evaluated at x
228 .  y - search direction (contains new iterate on output)
229 .  w - work vector
230 .  fnorm - 2-norm of f
231 
232    Output parameters:
233 .  g - residual evaluated at new iterate y
234 .  y - new iterate (contains search direction on input)
235 .  gnorm - 2-norm of g
236 .  ynorm - 2-norm of search length
237 
238    Returns:
239    1 if the line search succeeds; 0 if the line search fails.
240 
241    Notes:
242    Use either NLSetStepLineSearchRoutines() or NLSetLineSearchRoutine()
243    to set this routine within the SNES_NLS1 method.
244 
245    This line search is taken from "Numerical Methods for Unconstrained
246    Optimization and Nonlinear Equations" by Dennis and Schnabel, page 325.
247 
248 .keywords: SNES, nonlinear, line search, cubic
249 
250 .seealso: SNESNoLineSearch()
251 @*/
252 int SNESCubicLineSearch(SNES snes, Vec x, Vec f, Vec g, Vec y, Vec w,
253                               double fnorm, double *ynorm, double *gnorm )
254 {
255   double  steptol, initslope;
256   double  lambdaprev, gnormprev;
257   double  a, b, d, t1, t2;
258 #if defined(PETSC_COMPLEX)
259   Scalar  cinitslope,clambda;
260 #endif
261   int     ierr,count;
262   SNES_LS *neP = (SNES_LS *) snes->data;
263   Scalar  one = 1.0,scale;
264   double  maxstep,minlambda,alpha,lambda,lambdatemp;
265 
266   alpha   = neP->alpha;
267   maxstep = neP->maxstep;
268   steptol = neP->steptol;
269 
270   VecNorm(y, ynorm );
271   if (*ynorm > maxstep) {	/* Step too big, so scale back */
272     scale = maxstep/(*ynorm);
273 #if defined(PETSC_COMPLEX)
274     PLogInfo((PetscObject)snes,"Scaling step by %g\n",real(scale));
275 #else
276     PLogInfo((PetscObject)snes,"Scaling step by %g\n",scale);
277 #endif
278     VecScale(&scale, y );
279     *ynorm = maxstep;
280   }
281   minlambda = steptol/(*ynorm);
282 #if defined(PETSC_COMPLEX)
283   VecDot(f, y, &cinitslope );
284   initslope = real(cinitslope);
285 #else
286   VecDot(f, y, &initslope );
287 #endif
288   if (initslope > 0.0) initslope = -initslope;
289   if (initslope == 0.0) initslope = -1.0;
290 
291   VecCopy(y, w );
292   VecAXPY(&one, x, w );
293   ierr = SNESComputeResidual(snes,w,g); CHKERR(ierr);
294   VecNorm(g, gnorm );
295   if (*gnorm <= fnorm + alpha*initslope) {	/* Sufficient reduction */
296       VecCopy(w, y );
297       PLogInfo((PetscObject)snes,"Using full step\n");
298       return 0;
299   }
300 
301   /* Fit points with quadratic */
302   lambda = 1.0; count = 0;
303   lambdatemp = -initslope/(2.0*(*gnorm - fnorm - initslope));
304   lambdaprev = lambda;
305   gnormprev = *gnorm;
306   if (lambdatemp <= .1*lambda) {
307       lambda = .1*lambda;
308   } else lambda = lambdatemp;
309   VecCopy(x, w );
310 #if defined(PETSC_COMPLEX)
311   clambda = lambda; VecAXPY(&clambda, y, w );
312 #else
313   VecAXPY(&lambda, y, w );
314 #endif
315   ierr = SNESComputeResidual(snes,w,g); CHKERR(ierr);
316   VecNorm(g, gnorm );
317   if (*gnorm <= fnorm + alpha*initslope) {      /* sufficient reduction */
318       VecCopy(w, y );
319       PLogInfo((PetscObject)snes,"Quadratically determined step, lambda %g\n",lambda);
320       return 0;
321   }
322 
323   /* Fit points with cubic */
324   count = 1;
325   while (1) {
326       if (lambda <= minlambda) { /* bad luck; use full step */
327            PLogInfo((PetscObject)snes,"Unable to find good step length! %d \n",count);
328            PLogInfo((PetscObject)snes, "f %g fnew %g ynorm %g lambda %g \n",
329                    fnorm,*gnorm, *ynorm,lambda);
330            VecCopy(w, y );
331            return 0;
332       }
333       t1 = *gnorm - fnorm - lambda*initslope;
334       t2 = gnormprev  - fnorm - lambdaprev*initslope;
335       a = (t1/(lambda*lambda) -
336                 t2/(lambdaprev*lambdaprev))/(lambda-lambdaprev);
337       b = (-lambdaprev*t1/(lambda*lambda) +
338                 lambda*t2/(lambdaprev*lambdaprev))/(lambda-lambdaprev);
339       d = b*b - 3*a*initslope;
340       if (d < 0.0) d = 0.0;
341       if (a == 0.0) {
342          lambdatemp = -initslope/(2.0*b);
343       } else {
344          lambdatemp = (-b + sqrt(d))/(3.0*a);
345       }
346       if (lambdatemp > .5*lambda) {
347          lambdatemp = .5*lambda;
348       }
349       lambdaprev = lambda;
350       gnormprev = *gnorm;
351       if (lambdatemp <= .1*lambda) {
352          lambda = .1*lambda;
353       }
354       else lambda = lambdatemp;
355       VecCopy( x, w );
356 #if defined(PETSC_COMPLEX)
357       VecAXPY(&clambda, y, w );
358 #else
359       VecAXPY(&lambda, y, w );
360 #endif
361       ierr = SNESComputeResidual(snes,w,g); CHKERR(ierr);
362       VecNorm(g, gnorm );
363       if (*gnorm <= fnorm + alpha*initslope) {      /* is reduction enough */
364          VecCopy(w, y );
365          PLogInfo((PetscObject)snes,"Cubically determined step, lambda %g\n",lambda);
366          return 0;
367       }
368       count++;
369    }
370   return 0;
371 }
372 /*@
373    SNESQuadraticLineSearch - This routine performs a cubic line search.
374 
375    Input Parameters:
376 .  snes - nonlinear context
377 .  x - current iterate
378 .  f - residual evaluated at x
379 .  y - search direction (contains new iterate on output)
380 .  w - work vector
381 .  fnorm - 2-norm of f
382 
383    Output parameters:
384 .  g - residual evaluated at new iterate y
385 .  y - new iterate (contains search direction on input)
386 .  gnorm - 2-norm of g
387 .  ynorm - 2-norm of search length
388 
389    Returns:
390    1 if the line search succeeds; 0 if the line search fails.
391 
392    Notes:
393    Use SNESSetLineSearchRoutines()
394    to set this routine within the SNES_NLS1 method.
395 
396 @*/
397 int SNESQuadraticLineSearch(SNES snes, Vec x, Vec f, Vec g, Vec y, Vec w,
398                               double fnorm, double *ynorm, double *gnorm )
399 {
400   double  steptol, initslope;
401   double  lambdaprev, gnormprev;
402 #if defined(PETSC_COMPLEX)
403   Scalar  cinitslope,clambda;
404 #endif
405   int     ierr,count;
406   SNES_LS *neP = (SNES_LS *) snes->data;
407   Scalar  one = 1.0,scale;
408   double  maxstep,minlambda,alpha,lambda,lambdatemp;
409 
410   alpha   = neP->alpha;
411   maxstep = neP->maxstep;
412   steptol = neP->steptol;
413 
414   VecNorm(y, ynorm );
415   if (*ynorm > maxstep) {	/* Step too big, so scale back */
416     scale = maxstep/(*ynorm);
417     VecScale(&scale, y );
418     *ynorm = maxstep;
419   }
420   minlambda = steptol/(*ynorm);
421 #if defined(PETSC_COMPLEX)
422   VecDot(f, y, &cinitslope );
423   initslope = real(cinitslope);
424 #else
425   VecDot(f, y, &initslope );
426 #endif
427   if (initslope > 0.0) initslope = -initslope;
428   if (initslope == 0.0) initslope = -1.0;
429 
430   VecCopy(y, w );
431   VecAXPY(&one, x, w );
432   ierr = SNESComputeResidual(snes,w,g); CHKERR(ierr);
433   VecNorm(g, gnorm );
434   if (*gnorm <= fnorm + alpha*initslope) {	/* Sufficient reduction */
435       VecCopy(w, y );
436       PLogInfo((PetscObject)snes,"Using full step\n");
437       return 0;
438   }
439 
440   /* Fit points with quadratic */
441   lambda = 1.0; count = 0;
442   count = 1;
443   while (1) {
444     if (lambda <= minlambda) { /* bad luck; use full step */
445       PLogInfo((PetscObject)snes,"Unable to find good step length! %d \n",count);
446       PLogInfo((PetscObject)snes, "f %g fnew %g ynorm %g lambda %g \n",
447                    fnorm,*gnorm, *ynorm,lambda);
448       VecCopy(w, y );
449       return 0;
450     }
451     lambdatemp = -initslope/(2.0*(*gnorm - fnorm - initslope));
452     lambdaprev = lambda;
453     gnormprev = *gnorm;
454     if (lambdatemp <= .1*lambda) {
455       lambda = .1*lambda;
456     } else lambda = lambdatemp;
457     VecCopy(x, w );
458 #if defined(PETSC_COMPLEX)
459     clambda = lambda; VecAXPY(&clambda, y, w );
460 #else
461     VecAXPY(&lambda, y, w );
462 #endif
463     ierr = SNESComputeResidual(snes,w,g); CHKERR(ierr);
464     VecNorm(g, gnorm );
465     if (*gnorm <= fnorm + alpha*initslope) {      /* sufficient reduction */
466       VecCopy(w, y );
467       PLogInfo((PetscObject)snes,"Quadratically determined step, lambda %g\n",lambda);
468       return 0;
469     }
470     count++;
471   }
472 
473   return 0;
474 }
475 /* ------------------------------------------------------------ */
476 /*@C
477    SNESSetLineSearchRoutine - Sets the line search routine to be used
478    by the method SNES_LS.
479 
480    Input Parameters:
481 .  snes - nonlinear context obtained from NLCreate()
482 .  func - pointer to int function
483 
484    Possible routines:
485    SNESCubicLineSearch() - default line search
486    SNESNoLineSearch() - the full Newton step (actually not a line search)
487 
488    Calling sequence of func:
489 .  func (SNES snes, Vec x, Vec f, Vec g, Vec y,
490          Vec w, double fnorm, double *ynorm, double *gnorm )
491 
492     Input parameters for func:
493 .   snes - nonlinear context
494 .   x - current iterate
495 .   f - residual evaluated at x
496 .   y - search direction (contains new iterate on output)
497 .   w - work vector
498 .   fnorm - 2-norm of f
499 
500     Output parameters for func:
501 .   g - residual evaluated at new iterate y
502 .   y - new iterate (contains search direction on input)
503 .   gnorm - 2-norm of g
504 .   ynorm - 2-norm of search length
505 
506     Returned by func:
507     1 if the line search succeeds; 0 if the line search fails.
508 @*/
509 int SNESSetLineSearchRoutine(SNES snes,int (*func)(SNES,Vec,Vec,Vec,Vec,Vec,
510                              double,double *,double*) )
511 {
512   if ((snes)->type == SNES_NLS)
513     ((SNES_LS *)(snes->data))->LineSearch = func;
514   return 0;
515 }
516 
517 static int SNESPrintHelp_LS(SNES snes)
518 {
519   SNES_LS *ls = (SNES_LS *)snes->data;
520   fprintf(stderr,"-snes_line_search [basic,quadratic,cubic]\n");
521   fprintf(stderr,"-snes_line_search_alpha alpha (default %g)\n",ls->alpha);
522   fprintf(stderr,"-snes_line_search_maxstep max (default %g)\n",ls->maxstep);
523   fprintf(stderr,"-snes_line_search_steptol tol (default %g)\n",ls->steptol);
524   return 0;
525 }
526 
527 #include "options.h"
528 static int SNESSetFromOptions_LS(SNES snes)
529 {
530   SNES_LS *ls = (SNES_LS *)snes->data;
531   char    ver[16];
532   double  tmp;
533 
534   if (OptionsGetDouble(0,snes->prefix,"-snes_line_search_alpa",&tmp)) {
535     ls->alpha = tmp;
536   }
537   if (OptionsGetDouble(0,snes->prefix,"-snes_line_search_maxstep",&tmp)) {
538     ls->maxstep = tmp;
539   }
540   if (OptionsGetDouble(0,snes->prefix,"-snes_line_search_steptol",&tmp)) {
541     ls->steptol = tmp;
542   }
543   if (OptionsGetString(0,snes->prefix,"-snes_line_search",ver,16)) {
544     if (!strcmp(ver,"basic")) {
545       SNESSetLineSearchRoutine(snes,SNESNoLineSearch);
546     }
547     else if (!strcmp(ver,"quadratic")) {
548       SNESSetLineSearchRoutine(snes,SNESQuadraticLineSearch);
549     }
550     else if (!strcmp(ver,"cubic")) {
551       SNESSetLineSearchRoutine(snes,SNESCubicLineSearch);
552     }
553     else {SETERR(1,"Unknown line search?");}
554   }
555   return 0;
556 }
557 
558 /* ------------------------------------------------------------ */
559 int SNESCreate_LS(SNES  snes )
560 {
561   SNES_LS *neP;
562 
563   snes->type		= SNES_NLS;
564   snes->Setup		= SNESSetUp_LS;
565   snes->Solver		= SNESSolve_LS;
566   snes->destroy		= SNESDestroy_LS;
567   snes->Monitor  	= 0;
568   snes->Converged	= SNESDefaultConverged;
569   snes->PrintHelp       = SNESPrintHelp_LS;
570   snes->SetFromOptions  = SNESSetFromOptions_LS;
571 
572   neP			= NEW(SNES_LS);   CHKPTR(neP);
573   snes->data    	= (void *) neP;
574   neP->alpha		= 1.e-4;
575   neP->maxstep		= 1.e8;
576   neP->steptol		= 1.e-12;
577   neP->LineSearch       = SNESCubicLineSearch;
578   return 0;
579 }
580 
581 
582 
583 
584