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