Date: 2026-06-01T23:13:38+00:00

Actual source code: ls.c

      1: #include <../src/snes/impls/ls/lsimpl.h>

      3: /*
      4:      This file implements a truncated Newton method with a line search,
      5:      for solving a system of nonlinear equations, using the KSP, Vec,
      6:      and Mat interfaces for linear solvers, vectors, and matrices,
      7:      respectively.

      9:      The following basic routines are required for each nonlinear solver:
     10:           SNESCreate_XXX()          - Creates a nonlinear solver context
     11:           SNESSetFromOptions_XXX()  - Sets runtime options
     12:           SNESSolve_XXX()           - Solves the nonlinear system
     13:           SNESDestroy_XXX()         - Destroys the nonlinear solver context
     14:      The suffix "_XXX" denotes a particular implementation, in this case
     15:      we use _NEWTONLS (e.g., SNESCreate_NEWTONLS, SNESSolve_NEWTONLS) for solving
     16:      systems of nonlinear equations with a line search (LS) method.
     17:      These routines are actually called via the common user interface
     18:      routines SNESCreate(), SNESSetFromOptions(), SNESSolve(), and
     19:      SNESDestroy(), so the application code interface remains identical
     20:      for all nonlinear solvers.

     22:      Another key routine is:
     23:           SNESSetUp_XXX()           - Prepares for the use of a nonlinear solver
     24:      by setting data structures and options.   The interface routine SNESSetUp()
     25:      is not usually called directly by the user, but instead is called by
     26:      SNESSolve() if necessary.

     28:      Additional basic routines are:
     29:           SNESView_XXX()            - Prints details of runtime options that
     30:                                       have actually been used.
     31:      These are called by application codes via the interface routines
     32:      SNESView().

     34:      The various types of solvers (preconditioners, Krylov subspace methods,
     35:      nonlinear solvers, timesteppers) are all organized similarly, so the
     36:      above description applies to these categories also.

     38: */

     40: /*
     41:      Checks if J^T F = 0 which implies we've found a local minimum of the norm of the function,
     42:     || F(u) ||_2 but not a zero, F(u) = 0. In the case when one cannot compute J^T F we use the fact that
     43:     0 = (J^T F)^T W = F^T J W iff W not in the null space of J. Thanks for Jorge More
     44:     for this trick. One assumes that the probability that W is in the null space of J is very, very small.
     45: */
     46: static PetscErrorCode SNESNEWTONLSCheckLocalMin_Private(SNES snes, Mat A, Vec F, PetscReal fnorm, PetscBool *ismin)
     47: {
     48:   PetscReal        a1;
     49:   PetscBool        hastranspose;
     50:   Vec              W;
     51:   SNESObjectiveFn *objective;

     53:   PetscFunctionBegin;
     54:   *ismin = PETSC_FALSE;
     55:   PetscCall(SNESGetObjective(snes, &objective, NULL));
     56:   if (!objective) {
     57:     PetscCall(MatHasOperation(A, MATOP_MULT_TRANSPOSE, &hastranspose));
     58:     PetscCall(VecDuplicate(F, &W));
     59:     if (hastranspose) {
     60:       /* Compute || J^T F|| */
     61:       PetscCall(MatMultTranspose(A, F, W));
     62:       PetscCall(VecNorm(W, NORM_2, &a1));
     63:       PetscCall(PetscInfo(snes, "|| J^T F|| %14.12e near zero implies found a local minimum\n", (double)(a1 / fnorm)));
     64:       if (a1 / fnorm < 1.e-4) *ismin = PETSC_TRUE;
     65:     } else {
     66:       Vec         work;
     67:       PetscScalar result;
     68:       PetscReal   wnorm;

     70:       PetscCall(VecSetRandom(W, NULL));
     71:       PetscCall(VecNorm(W, NORM_2, &wnorm));
     72:       PetscCall(VecDuplicate(W, &work));
     73:       PetscCall(MatMult(A, W, work));
     74:       PetscCall(VecDot(F, work, &result));
     75:       PetscCall(VecDestroy(&work));
     76:       a1 = PetscAbsScalar(result) / (fnorm * wnorm);
     77:       PetscCall(PetscInfo(snes, "(F^T J random)/(|| F ||*||random|| %14.12e near zero implies found a local minimum\n", (double)a1));
     78:       if (a1 < 1.e-4) *ismin = PETSC_TRUE;
     79:     }
     80:     PetscCall(VecDestroy(&W));
     81:   }
     82:   PetscFunctionReturn(PETSC_SUCCESS);
     83: }

     85: /*
     86:      Checks if J^T(F - J*X) = 0
     87: */
     88: static PetscErrorCode SNESNEWTONLSCheckResidual_Private(SNES snes, Mat A, Vec F, Vec X)
     89: {
     90:   PetscReal        a1, a2;
     91:   PetscBool        hastranspose;
     92:   SNESObjectiveFn *objective;

     94:   PetscFunctionBegin;
     95:   PetscCall(MatHasOperation(A, MATOP_MULT_TRANSPOSE, &hastranspose));
     96:   PetscCall(SNESGetObjective(snes, &objective, NULL));
     97:   if (hastranspose && !objective) {
     98:     Vec W1, W2;

    100:     PetscCall(VecDuplicate(F, &W1));
    101:     PetscCall(VecDuplicate(F, &W2));
    102:     PetscCall(MatMult(A, X, W1));
    103:     PetscCall(VecAXPY(W1, -1.0, F));

    105:     /* Compute || J^T W|| */
    106:     PetscCall(MatMultTranspose(A, W1, W2));
    107:     PetscCall(VecNorm(W1, NORM_2, &a1));
    108:     PetscCall(VecNorm(W2, NORM_2, &a2));
    109:     if (a1 != 0.0) PetscCall(PetscInfo(snes, "||J^T(F-Ax)||/||F-AX|| %14.12e near zero implies inconsistent rhs\n", (double)(a2 / a1)));
    110:     PetscCall(VecDestroy(&W1));
    111:     PetscCall(VecDestroy(&W2));
    112:   }
    113:   PetscFunctionReturn(PETSC_SUCCESS);
    114: }

    116: // PetscClangLinter pragma disable: -fdoc-sowing-chars
    117: /*
    118:   SNESSolve_NEWTONLS - Solves a nonlinear system with a truncated Newton
    119:   method with a line search.

    121:   Input Parameter:
    122: . snes - the SNES context

    124: */
    125: static PetscErrorCode SNESSolve_NEWTONLS(SNES snes)
    126: {
    127:   PetscInt             maxits, i, lits;
    128:   SNESLineSearchReason lsreason;
    129:   PetscReal            fnorm, xnorm, ynorm;
    130:   Vec                  Y, X, F;
    131:   SNESLineSearch       linesearch;
    132:   SNESConvergedReason  reason;
    133:   PC                   pc;
    134: #if defined(PETSC_USE_INFO)
    135:   PetscReal gnorm;
    136: #endif

    138:   PetscFunctionBegin;
    139:   PetscCheck(!snes->xl && !snes->xu && !snes->ops->computevariablebounds, PetscObjectComm((PetscObject)snes), PETSC_ERR_ARG_WRONGSTATE, "SNES solver %s does not support bounds", ((PetscObject)snes)->type_name);

    141:   snes->numFailures            = 0;
    142:   snes->numLinearSolveFailures = 0;
    143:   snes->reason                 = SNES_CONVERGED_ITERATING;

    145:   maxits = snes->max_its;        /* maximum number of iterations */
    146:   X      = snes->vec_sol;        /* solution vector */
    147:   F      = snes->vec_func;       /* residual vector */
    148:   Y      = snes->vec_sol_update; /* newton step */

    150:   PetscCall(PetscObjectSAWsTakeAccess((PetscObject)snes));
    151:   snes->iter = 0;
    152:   snes->norm = 0.0;
    153:   PetscCall(PetscObjectSAWsGrantAccess((PetscObject)snes));
    154:   PetscCall(SNESGetLineSearch(snes, &linesearch));

    156:   /* compute the preconditioned function first in the case of left preconditioning with preconditioned function */
    157:   if (snes->npc && snes->npcside == PC_LEFT && snes->functype == SNES_FUNCTION_PRECONDITIONED) {
    158:     PetscCall(SNESApplyNPC(snes, X, NULL, F));
    159:     PetscCall(SNESGetConvergedReason(snes->npc, &reason));
    160:     if (reason < 0 && reason != SNES_DIVERGED_MAX_IT && reason != SNES_DIVERGED_TR_DELTA) {
    161:       PetscCall(SNESSetConvergedReason(snes, SNES_DIVERGED_INNER));
    162:       PetscFunctionReturn(PETSC_SUCCESS);
    163:     }

    165:     PetscCall(VecNormBegin(F, NORM_2, &fnorm));
    166:     PetscCall(VecNormEnd(F, NORM_2, &fnorm));
    167:   } else {
    168:     if (!snes->vec_func_init_set) PetscCall(SNESComputeFunction(snes, X, F));
    169:     else snes->vec_func_init_set = PETSC_FALSE;
    170:   }

    172:   PetscCall(VecNorm(F, NORM_2, &fnorm)); /* fnorm <- ||F||  */
    173:   SNESCheckFunctionDomainError(snes, fnorm);
    174:   PetscCall(PetscObjectSAWsTakeAccess((PetscObject)snes));
    175:   snes->norm = fnorm;
    176:   PetscCall(PetscObjectSAWsGrantAccess((PetscObject)snes));
    177:   PetscCall(SNESLogConvergenceHistory(snes, fnorm, 0));

    179:   /* test convergence */
    180:   PetscCall(SNESConverged(snes, 0, 0.0, 0.0, fnorm));
    181:   PetscCall(SNESMonitor(snes, 0, fnorm));
    182:   if (snes->reason) PetscFunctionReturn(PETSC_SUCCESS);

    184:   /* hook state vector to BFGS preconditioner */
    185:   PetscCall(KSPGetPC(snes->ksp, &pc));
    186:   PetscCall(PCLMVMSetUpdateVec(pc, X));

    188:   for (i = 0; i < maxits; i++) {
    189:     /* Call general purpose update function */
    190:     PetscTryTypeMethod(snes, update, snes->iter);
    191:     PetscCall(VecNorm(snes->vec_func, NORM_2, &fnorm)); /* no-op unless update() function changed f() */

    193:     /* apply the nonlinear preconditioner */
    194:     if (snes->npc) {
    195:       if (snes->npcside == PC_RIGHT) {
    196:         PetscCall(SNESSetInitialFunction(snes->npc, F));
    197:         PetscCall(PetscLogEventBegin(SNES_NPCSolve, snes->npc, X, snes->vec_rhs, 0));
    198:         PetscCall(SNESSolve(snes->npc, snes->vec_rhs, X));
    199:         PetscCall(PetscLogEventEnd(SNES_NPCSolve, snes->npc, X, snes->vec_rhs, 0));
    200:         PetscCall(SNESGetConvergedReason(snes->npc, &reason));
    201:         if (reason < 0 && reason != SNES_DIVERGED_MAX_IT && reason != SNES_DIVERGED_TR_DELTA) {
    202:           PetscCall(SNESSetConvergedReason(snes, SNES_DIVERGED_INNER));
    203:           PetscFunctionReturn(PETSC_SUCCESS);
    204:         }
    205:         PetscCall(SNESGetNPCFunction(snes, F, &fnorm));
    206:       } else if (snes->npcside == PC_LEFT && snes->functype == SNES_FUNCTION_UNPRECONDITIONED) {
    207:         PetscCall(SNESApplyNPC(snes, X, F, F));
    208:         PetscCall(SNESGetConvergedReason(snes->npc, &reason));
    209:         if (reason < 0 && reason != SNES_DIVERGED_MAX_IT && reason != SNES_DIVERGED_TR_DELTA) {
    210:           PetscCall(SNESSetConvergedReason(snes, SNES_DIVERGED_INNER));
    211:           PetscFunctionReturn(PETSC_SUCCESS);
    212:         }
    213:       }
    214:     }

    216:     /* Solve J Y = F, where J is Jacobian matrix */
    217:     PetscCall(SNESComputeJacobian(snes, X, snes->jacobian, snes->jacobian_pre));
    218:     SNESCheckJacobianDomainError(snes);
    219:     PetscCall(KSPSetOperators(snes->ksp, snes->jacobian, snes->jacobian_pre));
    220:     PetscCall(KSPSolve(snes->ksp, F, Y));
    221:     SNESCheckKSPSolve(snes);
    222:     PetscCall(KSPGetIterationNumber(snes->ksp, &lits));
    223:     PetscCall(PetscInfo(snes, "iter=%" PetscInt_FMT ", linear solve iterations=%" PetscInt_FMT "\n", snes->iter, lits));

    225:     if (PetscLogPrintInfo) PetscCall(SNESNEWTONLSCheckResidual_Private(snes, snes->jacobian, F, Y));

    227: #if defined(PETSC_USE_INFO)
    228:     gnorm = fnorm;
    229: #endif
    230:     /* Compute a (scaled) negative update in the line search routine:
    231:          X <- X - lambda*Y
    232:        and evaluate F = function(X) (depends on the line search).
    233:     */
    234:     PetscCall(SNESLineSearchApply(linesearch, X, F, &fnorm, Y));
    235:     if (snes->reason) break;
    236:     PetscCall(SNESLineSearchGetNorms(linesearch, &xnorm, &fnorm, &ynorm));
    237:     PetscCall(PetscInfo(snes, "fnorm=%18.16e, gnorm=%18.16e, ynorm=%18.16e, lsreason=%d\n", (double)gnorm, (double)fnorm, (double)ynorm, (int)lsreason));
    238:     SNESCheckFunctionDomainError(snes, fnorm);
    239:     PetscCall(SNESLineSearchGetReason(linesearch, &lsreason));
    240:     if (lsreason) {
    241:       if (snes->stol * xnorm > ynorm) {
    242:         snes->reason = SNES_CONVERGED_SNORM_RELATIVE;
    243:         break;
    244:       } else if (lsreason == SNES_LINESEARCH_FAILED_FUNCTION_DOMAIN) {
    245:         snes->reason = SNES_DIVERGED_FUNCTION_DOMAIN;
    246:         break;
    247:       } else if (lsreason == SNES_LINESEARCH_FAILED_NANORINF) {
    248:         snes->reason = SNES_DIVERGED_FUNCTION_NANORINF;
    249:         break;
    250:       } else if (lsreason == SNES_LINESEARCH_FAILED_OBJECTIVE_DOMAIN) {
    251:         snes->reason = SNES_DIVERGED_OBJECTIVE_DOMAIN;
    252:         break;
    253:       } else if (lsreason == SNES_LINESEARCH_FAILED_JACOBIAN_DOMAIN) {
    254:         snes->reason = SNES_DIVERGED_JACOBIAN_DOMAIN;
    255:         break;
    256:       } else if (++snes->numFailures >= snes->maxFailures) {
    257:         PetscBool ismin;

    259:         snes->reason = SNES_DIVERGED_LINE_SEARCH;
    260:         PetscCall(SNESNEWTONLSCheckLocalMin_Private(snes, snes->jacobian, F, fnorm, &ismin));
    261:         if (ismin) snes->reason = SNES_DIVERGED_LOCAL_MIN;
    262:         break;
    263:       }
    264:     }
    265:     /* Monitor convergence */
    266:     PetscCall(PetscObjectSAWsTakeAccess((PetscObject)snes));
    267:     snes->iter  = i + 1;
    268:     snes->norm  = fnorm;
    269:     snes->ynorm = ynorm;
    270:     snes->xnorm = xnorm;
    271:     PetscCall(PetscObjectSAWsGrantAccess((PetscObject)snes));
    272:     PetscCall(SNESLogConvergenceHistory(snes, snes->norm, lits));
    273:     /* Test for convergence */
    274:     PetscCall(SNESConverged(snes, snes->iter, xnorm, ynorm, fnorm));
    275:     PetscCall(SNESMonitor(snes, snes->iter, snes->norm));
    276:     if (snes->reason) break;
    277:   }
    278:   PetscFunctionReturn(PETSC_SUCCESS);
    279: }

    281: /*
    282:    SNESSetUp_NEWTONLS - Sets up the internal data structures for the later use
    283:    of the SNESNEWTONLS nonlinear solver.

    285:    Input Parameter:
    286: .  snes - the SNES context
    287: .  x - the solution vector

    289:    Application Interface Routine: SNESSetUp()

    291:  */
    292: static PetscErrorCode SNESSetUp_NEWTONLS(SNES snes)
    293: {
    294:   PetscFunctionBegin;
    295:   PetscCall(SNESSetUpMatrices(snes));
    296:   if (snes->npcside == PC_LEFT && snes->functype == SNES_FUNCTION_DEFAULT) snes->functype = SNES_FUNCTION_PRECONDITIONED;
    297:   PetscFunctionReturn(PETSC_SUCCESS);
    298: }

    300: /*
    301:    SNESDestroy_NEWTONLS - Destroys the private SNES_NEWTONLS context that was created
    302:    with SNESCreate_NEWTONLS().

    304:    Input Parameter:
    305: .  snes - the SNES context

    307:    Application Interface Routine: SNESDestroy()
    308:  */
    309: static PetscErrorCode SNESDestroy_NEWTONLS(SNES snes)
    310: {
    311:   PetscFunctionBegin;
    312:   PetscCall(PetscFree(snes->data));
    313:   PetscFunctionReturn(PETSC_SUCCESS);
    314: }

    316: /*MC
    317:    SNESNEWTONLS - Newton based nonlinear solver that uses a line search

    319:    Options Database Keys:
    320: +   -snes_linesearch_type bt             - basic (or equivalently none), `bt`, `l2`, `cp`, `nleqerr`, `shell`.  Select line search type, see `SNESLineSearchSetType()`
    321: .   -snes_linesearch_order 3             - 2, 3. Selects the order of the line search for `bt`, see `SNESLineSearchSetOrder()`
    322: .   -snes_linesearch_norms true          - Turns on/off computation of the norms for basic linesearch (`SNESLineSearchSetComputeNorms()`)
    323: .   -snes_linesearch_alpha alpha         - Sets `alpha` used in determining if reduction in function norm is sufficient
    324: .   -snes_linesearch_maxstep maxstep     - Sets the maximum `stepsize` the line search will use (if the $ ||y|| > maxstep $ then scale `y` to be $y = y * maxstep/||y||.$
    325: .   -snes_linesearch_minlambda minlambda - Sets the minimum `lambda` the line search will tolerate
    326: .   -snes_linesearch_monitor             - print information about the progress of line searches
    327: -   -snes_linesearch_damping             - damping factor used for the basic line search

    329:    Level: beginner

    331:    Note:
    332:    This is the default nonlinear solver in `SNES`

    334: .seealso: [](ch_snes), `SNESCreate()`, `SNES`, `SNESSetType()`, `SNESNEWTONTR`, `SNESQN`, `SNESLineSearchSetType()`, `SNESLineSearchSetOrder()`,
    335:           `SNESLineSearchSetPostCheck()`, `SNESLineSearchSetPreCheck()`, `SNESLineSearchSetComputeNorms()`, `SNESGetLineSearch()`
    336: M*/
    337: PETSC_EXTERN PetscErrorCode SNESCreate_NEWTONLS(SNES snes)
    338: {
    339:   SNES_NEWTONLS *neP;
    340:   SNESLineSearch linesearch;

    342:   PetscFunctionBegin;
    343:   snes->ops->setup   = SNESSetUp_NEWTONLS;
    344:   snes->ops->solve   = SNESSolve_NEWTONLS;
    345:   snes->ops->destroy = SNESDestroy_NEWTONLS;

    347:   snes->npcside = PC_RIGHT;
    348:   snes->usesksp = PETSC_TRUE;
    349:   snes->usesnpc = PETSC_TRUE;

    351:   PetscCall(SNESGetLineSearch(snes, &linesearch));
    352:   if (!((PetscObject)linesearch)->type_name) PetscCall(SNESLineSearchSetType(linesearch, SNESLINESEARCHBT));

    354:   snes->alwayscomputesfinalresidual = PETSC_TRUE;

    356:   PetscCall(SNESParametersInitialize(snes));

    358:   PetscCall(PetscNew(&neP));
    359:   snes->data = (void *)neP;
    360:   PetscFunctionReturn(PETSC_SUCCESS);
    361: }