xref: /petsc/src/snes/impls/ls/ls.c (revision fbe28522c53d349534977c8c6cbaf0d5bf3f7b02)
1 #ifndef lint
2 static char vcid[] = "$Id: ls.c,v 1.2 1995/04/05 20:34:33 bsmith Exp bsmith $";
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, iters, line, nlconv, history_len,ierr,lits;
33   double  fnorm, gnorm, gpnorm, 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   SNES_LS *ctx = (SNES_LS *)snes->data;
86   int             ierr;
87   snes->nwork = 3;
88   ierr = VecGetVecs( snes->vec_sol, snes->nwork,&snes->work ); CHKERR(ierr);
89   PLogObjectParents(snes,snes->nwork,snes->work );
90   return 0;
91 }
92 /* ------------------------------------------------------------ */
93 /*ARGSUSED*/
94 int SNESDestroy_LS(PetscObject obj)
95 {
96   SNES snes = (SNES) obj;
97   SLESDestroy(snes->sles);
98   VecFreeVecs(snes->work, snes->nwork );
99   PLogObjectDestroy(obj);
100   PETSCHEADERDESTROY(obj);
101   return 0;
102 }
103 /*@
104    SNESDefaultMonitor - Default monitor for NLE solvers.  Prints the
105    residual norm at each iteration.
106 
107    Input Parameters:
108 .  nlP - nonlinear context
109 .  x - current iterate
110 .  f - current residual (+/-)
111 .  fnorm - 2-norm residual value (may be estimated).
112 
113    Notes:
114    f is either the residual or its negative, depending on the user's
115    preference, as set with NLSetResidualRoutine().
116 
117 
118 @*/
119 int SNESDefaultMonitor(SNES snes,int its, Vec x,Vec f,double fnorm,void *dummy)
120 {
121   fprintf( stdout, "iter = %d, residual norm %g \n",its,fnorm);
122   return 0;
123 }
124 
125 /*@
126    SNESDefaultConverged - Default test for monitoring the convergence
127    of the NLE solvers.
128 
129    Input Parameters:
130 .  nlP - nonlinear context
131 .  xnorm - 2-norm of current iterate
132 .  pnorm - 2-norm of current step
133 .  fnorm - 2-norm of residual
134 
135    Returns:
136 $  2  if  ( fnorm < atol ),
137 $  3  if  ( pnorm < xtol*xnorm ),
138 $ -2  if  ( nres > max_res ),
139 $  0  otherwise,
140 
141    where
142 $    atol    - absolute residual norm tolerance,
143 $              set with NLSetAbsConvergenceTol()
144 $    max_res - maximum number of residual evaluations,
145 $              set with NLSetMaxResidualEvaluations()
146 $    nres    - number of residual evaluations
147 $    xtol    - relative residual norm tolerance,
148 
149 $              set with NLSetMaxResidualEvaluations()
150 $    nres    - number of residual evaluations
151 $    xtol    - relative residual norm tolerance,
152 $              set with NLSetRelConvergenceTol()
153 
154 @*/
155 int SNESDefaultConverged(SNES snes,double xnorm,double pnorm,double fnorm,
156                          void *dummy)
157 {
158   if (fnorm < snes->atol) {
159     PLogInfo((PetscObject)snes,"Converged due to absolute residual norm %g < %g\n",
160                    fnorm,snes->atol);
161     return 2;
162   }
163   if (pnorm < snes->xtol*(xnorm)) {
164     PLogInfo((PetscObject)snes,"Converged due to small update length  %g < %g*%g\n",
165                    pnorm,snes->xtol,xnorm);
166     return 3;
167   }
168   return 0;
169 }
170 
171 /* ------------------------------------------------------------ */
172 /*ARGSUSED*/
173 /*@
174    SNESNoLineSearch - This routine is not a line search at all;
175    it simply uses the full Newton step.  Thus, this routine is intended
176    to serve as a template and is not recommended for general use.
177 
178    Input Parameters:
179 .  snes - nonlinear context
180 .  x - current iterate
181 .  f - residual evaluated at x
182 .  y - search direction (contains new iterate on output)
183 .  w - work vector
184 .  fnorm - 2-norm of f
185 
186    Output parameters:
187 .  g - residual evaluated at new iterate y
188 .  y - new iterate (contains search direction on input)
189 .  gnorm - 2-norm of g
190 .  ynorm - 2-norm of search length
191 
192    Returns:
193    1, indicating success of the step.
194 
195 @*/
196 int SNESNoLineSearch(SNES snes, Vec x, Vec f, Vec g, Vec y, Vec w,
197                              double fnorm, double *ynorm, double *gnorm )
198 {
199   int    ierr;
200   Scalar one = 1.0;
201   VecNorm(y, ynorm );	/* ynorm = || y ||    */
202   VecAXPY(&one, x, y );	/* y <- x + y         */
203   ierr = SNESComputeResidual(snes,y,g); CHKERR(ierr);
204   VecNorm( g, gnorm ); 	/* gnorm = || g ||    */
205   return 1;
206 }
207 /* ------------------------------------------------------------------ */
208 /*@
209    SNESCubicLineSearch - This routine performs a cubic line search.
210 
211    Input Parameters:
212 .  snes - nonlinear context
213 .  x - current iterate
214 .  f - residual evaluated at x
215 .  y - search direction (contains new iterate on output)
216 .  w - work vector
217 .  fnorm - 2-norm of f
218 
219    Output parameters:
220 .  g - residual evaluated at new iterate y
221 .  y - new iterate (contains search direction on input)
222 .  gnorm - 2-norm of g
223 .  ynorm - 2-norm of search length
224 
225    Returns:
226    1 if the line search succeeds; 0 if the line search fails.
227 
228    Notes:
229    Use either NLSetStepLineSearchRoutines() or NLSetLineSearchRoutine()
230    to set this routine within the SNES_NLS1 method.
231 
232    This line search is taken from "Numerical Methods for Unconstrained
233    Optimization and Nonlinear Equations" by Dennis and Schnabel, page 325.
234 
235 @*/
236 int SNESCubicLineSearch(SNES snes, Vec x, Vec f, Vec g, Vec y, Vec w,
237                               double fnorm, double *ynorm, double *gnorm )
238 {
239   double  steptol, initslope;
240   double  lambdaprev, gnormprev;
241   double  a, b, d, t1, t2;
242   Scalar  cinitslope,clambda;
243   int     ierr,count;
244   SNES_LS *neP = (SNES_LS *) snes->data;
245   Scalar  one = 1.0,scale;
246   double  maxstep,minlambda,alpha,lambda,lambdatemp;
247 
248   alpha   = neP->alpha;
249   maxstep = neP->maxstep;
250   steptol = neP->steptol;
251 
252   VecNorm(y, ynorm );
253   if (*ynorm > maxstep) {	/* Step too big, so scale back */
254     scale = maxstep/(*ynorm);
255     PLogInfo((PetscObject)snes,"Scaling step by %g\n",scale);
256     VecScale(&scale, y );
257     *ynorm = maxstep;
258   }
259   minlambda = steptol/(*ynorm);
260 #if defined(PETSC_COMPLEX)
261   VecDot(f, y, &cinitslope );
262   initslope = real(cinitslope);
263 #else
264   VecDot(f, y, &initslope );
265 #endif
266   if (initslope > 0.0) initslope = -initslope;
267   if (initslope == 0.0) initslope = -1.0;
268 
269   VecCopy(y, w );
270   VecAXPY(&one, x, w );
271   ierr = SNESComputeResidual(snes,w,g); CHKERR(ierr);
272   VecNorm(g, gnorm );
273   if (*gnorm <= fnorm + alpha*initslope) {	/* Sufficient reduction */
274       VecCopy(w, y );
275       PLogInfo((PetscObject)snes,"Using full step\n");
276       return 0;
277   }
278 
279   /* Fit points with quadratic */
280   lambda = 1.0; count = 0;
281   lambdatemp = -initslope/(2.0*(*gnorm - fnorm - initslope));
282   lambdaprev = lambda;
283   gnormprev = *gnorm;
284   if (lambdatemp <= .1*lambda) {
285       lambda = .1*lambda;
286   } else lambda = lambdatemp;
287   VecCopy(x, w );
288 #if defined(PETSC_COMPLEX)
289   clambda = lambda; VecAXPY(&clambda, y, w );
290 #else
291   VecAXPY(&lambda, y, w );
292 #endif
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,"Quadratically determined step, lambda %g\n",lambda);
298       return 0;
299   }
300 
301   /* Fit points with cubic */
302   count = 1;
303   while (1) {
304       if (lambda <= minlambda) { /* bad luck; use full step */
305            PLogInfo((PetscObject)snes,"Unable to find good step length! %d \n",count);
306            PLogInfo((PetscObject)snes, "f %g fnew %g ynorm %g lambda %g \n",
307                    fnorm,*gnorm, *ynorm,lambda);
308            VecCopy(w, y );
309            return 0;
310       }
311       t1 = *gnorm - fnorm - lambda*initslope;
312       t2 = gnormprev  - fnorm - lambdaprev*initslope;
313       a = (t1/(lambda*lambda) -
314                 t2/(lambdaprev*lambdaprev))/(lambda-lambdaprev);
315       b = (-lambdaprev*t1/(lambda*lambda) +
316                 lambda*t2/(lambdaprev*lambdaprev))/(lambda-lambdaprev);
317       d = b*b - 3*a*initslope;
318       if (d < 0.0) d = 0.0;
319       if (a == 0.0) {
320          lambdatemp = -initslope/(2.0*b);
321       } else {
322          lambdatemp = (-b + sqrt(d))/(3.0*a);
323       }
324       if (lambdatemp > .5*lambda) {
325          lambdatemp = .5*lambda;
326       }
327       lambdaprev = lambda;
328       gnormprev = *gnorm;
329       if (lambdatemp <= .1*lambda) {
330          lambda = .1*lambda;
331       }
332       else lambda = lambdatemp;
333       VecCopy( x, w );
334 #if defined(PETSC_COMPLEX)
335       VecAXPY(&clambda, y, w );
336 #else
337       VecAXPY(&lambda, y, w );
338 #endif
339       ierr = SNESComputeResidual(snes,w,g); CHKERR(ierr);
340       VecNorm(g, gnorm );
341       if (*gnorm <= fnorm + alpha*initslope) {      /* is reduction enough */
342          VecCopy(w, y );
343          PLogInfo((PetscObject)snes,"Cubically determined step, lambda %g\n",lambda);
344          return 0;
345       }
346       count++;
347    }
348   return 0;
349 }
350 /*@
351    SNESQuadraticLineSearch - This routine performs a cubic line search.
352 
353    Input Parameters:
354 .  snes - nonlinear context
355 .  x - current iterate
356 .  f - residual evaluated at x
357 .  y - search direction (contains new iterate on output)
358 .  w - work vector
359 .  fnorm - 2-norm of f
360 
361    Output parameters:
362 .  g - residual evaluated at new iterate y
363 .  y - new iterate (contains search direction on input)
364 .  gnorm - 2-norm of g
365 .  ynorm - 2-norm of search length
366 
367    Returns:
368    1 if the line search succeeds; 0 if the line search fails.
369 
370    Notes:
371    Use SNESSetLineSearchRoutines()
372    to set this routine within the SNES_NLS1 method.
373 
374 @*/
375 int SNESQuadraticLineSearch(SNES snes, Vec x, Vec f, Vec g, Vec y, Vec w,
376                               double fnorm, double *ynorm, double *gnorm )
377 {
378   double  steptol, initslope;
379   double  lambdaprev, gnormprev;
380   double  a, b, d, t1, t2;
381   Scalar  cinitslope,clambda;
382   int     ierr,count;
383   SNES_LS *neP = (SNES_LS *) snes->data;
384   Scalar  one = 1.0,scale;
385   double  maxstep,minlambda,alpha,lambda,lambdatemp;
386 
387   alpha   = neP->alpha;
388   maxstep = neP->maxstep;
389   steptol = neP->steptol;
390 
391   VecNorm(y, ynorm );
392   if (*ynorm > maxstep) {	/* Step too big, so scale back */
393     scale = maxstep/(*ynorm);
394     VecScale(&scale, y );
395     *ynorm = maxstep;
396   }
397   minlambda = steptol/(*ynorm);
398 #if defined(PETSC_COMPLEX)
399   VecDot(f, y, &cinitslope );
400   initslope = real(cinitslope);
401 #else
402   VecDot(f, y, &initslope );
403 #endif
404   if (initslope > 0.0) initslope = -initslope;
405   if (initslope == 0.0) initslope = -1.0;
406 
407   VecCopy(y, w );
408   VecAXPY(&one, x, w );
409   ierr = SNESComputeResidual(snes,w,g); CHKERR(ierr);
410   VecNorm(g, gnorm );
411   if (*gnorm <= fnorm + alpha*initslope) {	/* Sufficient reduction */
412       VecCopy(w, y );
413       PLogInfo((PetscObject)snes,"Using full step\n");
414       return 0;
415   }
416 
417   /* Fit points with quadratic */
418   lambda = 1.0; count = 0;
419   count = 1;
420   while (1) {
421     if (lambda <= minlambda) { /* bad luck; use full step */
422       PLogInfo((PetscObject)snes,"Unable to find good step length! %d \n",count);
423       PLogInfo((PetscObject)snes, "f %g fnew %g ynorm %g lambda %g \n",
424                    fnorm,*gnorm, *ynorm,lambda);
425       VecCopy(w, y );
426       return 0;
427     }
428     lambdatemp = -initslope/(2.0*(*gnorm - fnorm - initslope));
429     lambdaprev = lambda;
430     gnormprev = *gnorm;
431     if (lambdatemp <= .1*lambda) {
432       lambda = .1*lambda;
433     } else lambda = lambdatemp;
434     VecCopy(x, w );
435 #if defined(PETSC_COMPLEX)
436     clambda = lambda; VecAXPY(&clambda, y, w );
437 #else
438     VecAXPY(&lambda, y, w );
439 #endif
440     ierr = SNESComputeResidual(snes,w,g); CHKERR(ierr);
441     VecNorm(g, gnorm );
442     if (*gnorm <= fnorm + alpha*initslope) {      /* sufficient reduction */
443       VecCopy(w, y );
444       PLogInfo((PetscObject)snes,"Quadratically determined step, lambda %g\n",lambda);
445       return 0;
446     }
447     count++;
448   }
449 
450   return 0;
451 }
452 /* ------------------------------------------------------------ */
453 /*@C
454    SNESSetLineSearchRoutine - Sets the line search routine to be used
455    by the method SNES_LS.
456 
457    Input Parameters:
458 .  snes - nonlinear context obtained from NLCreate()
459 .  func - pointer to int function
460 
461    Possible routines:
462    SNESCubicLineSearch() - default line search
463    SNESNoLineSearch() - the full Newton step (actually not a line search)
464 
465    Calling sequence of func:
466 .  func (SNES snes, Vec x, Vec f, Vec g, Vec y,
467          Vec w, double fnorm, double *ynorm, double *gnorm )
468 
469     Input parameters for func:
470 .   snes - nonlinear context
471 .   x - current iterate
472 .   f - residual evaluated at x
473 .   y - search direction (contains new iterate on output)
474 .   w - work vector
475 .   fnorm - 2-norm of f
476 
477     Output parameters for func:
478 .   g - residual evaluated at new iterate y
479 .   y - new iterate (contains search direction on input)
480 .   gnorm - 2-norm of g
481 .   ynorm - 2-norm of search length
482 
483     Returned by func:
484     1 if the line search succeeds; 0 if the line search fails.
485 @*/
486 int SNESSetLineSearchRoutine(SNES snes,int (*func)(SNES,Vec,Vec,Vec,Vec,Vec,
487                              double,double *,double*) )
488 {
489   if ((snes)->type == SNES_NLS)
490     ((SNES_LS *)(snes->data))->LineSearch = func;
491   return 0;
492 }
493 
494 static int SNESPrintHelp_LS(SNES snes)
495 {
496   SNES_LS *ls = (SNES_LS *)snes->data;
497   fprintf(stderr,"-snes_line_search [basic,quadratic,cubic]\n");
498   fprintf(stderr,"-snes_line_search_alpha alpha (default %g)\n",ls->alpha);
499   fprintf(stderr,"-snes_line_search_maxstep max (default %g)\n",ls->maxstep);
500   fprintf(stderr,"-snes_line_search_steptol tol (default %g)\n",ls->steptol);
501 }
502 
503 static int SNESSetFromOptions_LS(SNES snes)
504 {
505   SNES_LS *ls = (SNES_LS *)snes->data;
506   char    ver[16];
507   double  tmp;
508 
509   if (OptionsGetDouble(0,snes->prefix,"-snes_line_search_alpa",&tmp)) {
510     ls->alpha = tmp;
511   }
512   if (OptionsGetDouble(0,snes->prefix,"-snes_line_search_maxstep",&tmp)) {
513     ls->maxstep = tmp;
514   }
515   if (OptionsGetDouble(0,snes->prefix,"-snes_line_search_steptol",&tmp)) {
516     ls->steptol = tmp;
517   }
518   if (OptionsGetString(0,snes->prefix,"-snes_line_search",ver,16)) {
519     if (!strcmp(ver,"basic")) {
520       SNESSetLineSearchRoutine(snes,SNESNoLineSearch);
521     }
522     else if (!strcmp(ver,"quadratic")) {
523       SNESSetLineSearchRoutine(snes,SNESQuadraticLineSearch);
524     }
525     else if (!strcmp(ver,"cubic")) {
526       SNESSetLineSearchRoutine(snes,SNESCubicLineSearch);
527     }
528     else {SETERR(1,"Unknown line search?");}
529   }
530   return 0;
531 }
532 
533 /* ------------------------------------------------------------ */
534 int SNESCreate_LS(SNES  snes )
535 {
536   SNES_LS *neP;
537 
538   snes->type		= SNES_NLS;
539   snes->Setup		= SNESSetUp_LS;
540   snes->Solver		= SNESSolve_LS;
541   snes->destroy		= SNESDestroy_LS;
542   snes->Monitor  	= 0;
543   snes->Converged	= SNESDefaultConverged;
544   snes->PrintHelp       = SNESPrintHelp_LS;
545   snes->SetFromOptions  = SNESSetFromOptions_LS;
546 
547   neP			= NEW(SNES_LS);   CHKPTR(neP);
548   snes->data    	= (void *) neP;
549   neP->alpha		= 1.e-4;
550   neP->maxstep		= 1.e8;
551   neP->steptol		= 1.e-12;
552   neP->LineSearch       = SNESCubicLineSearch;
553   return 0;
554 }
555 
556 
557 
558 
559