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