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