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