xref: /petsc/src/snes/impls/al/al.c (revision bfe80ac4a46d58cb7760074b25f5e81b2f541d8a)
1 #include <../src/snes/impls/al/alimpl.h> /*I "petscsnes.h" I*/
2 
3 /*
4      This file implements a truncated Newton method with arc length continuation,
5      for solving a system of nonlinear equations, using the KSP, Vec,
6      and Mat interfaces for linear solvers, vectors, and matrices,
7      respectively.
8 */
9 
10 const char NewtonALExactCitation[]   = "@article{Ritto-CorreaCamotim2008,\n"
11                                        "  title={On the arc-length and other quadratic control methods: Established, less known and new implementation procedures},\n"
12                                        "  volume={86},\n"
13                                        "  ISSN={0045-7949},\n"
14                                        "  DOI={10.1016/j.compstruc.2007.08.003},\n"
15                                        "  number={11},\n"
16                                        "  journal={Computers & Structures},\n"
17                                        "  author={Ritto-Corr{\\^{e}}a, Manuel and Camotim, Dinar},\n"
18                                        "  year={2008},\n"
19                                        "  month=jun,\n"
20                                        "  pages={1353-1368},\n"
21                                        "}\n";
22 PetscBool  NewtonALExactCitationSet  = PETSC_FALSE;
23 const char NewtonALNormalCitation[]  = "@article{LeonPaulinoPereiraMenezesLages_2011,\n"
24                                        "  title={A Unified Library of Nonlinear Solution Schemes},\n"
25                                        "  volume={64},\n"
26                                        "  ISSN={0003-6900, 2379-0407},\n"
27                                        "  DOI={10.1115/1.4006992},\n"
28                                        "  number={4},\n"
29                                        "  journal={Applied Mechanics Reviews},\n"
30                                        "  author={Leon, Sofie E. and Paulino, Glaucio H. and Pereira, Anderson and Menezes, Ivan F. M. and Lages, Eduardo N.},\n"
31                                        "  year={2011},\n"
32                                        "  month=jul,\n"
33                                        "  pages={040803},\n"
34                                        "  language={en}\n"
35                                        "}\n";
36 PetscBool  NewtonALNormalCitationSet = PETSC_FALSE;
37 
38 const char *const SNESNewtonALCorrectionTypes[] = {"EXACT", "NORMAL", "SNESNewtonALCorrectionType", "SNES_NEWTONAL_CORRECTION_", NULL};
39 
40 static PetscErrorCode SNESNewtonALCheckArcLength(SNES snes, Vec XStep, PetscReal lambdaStep, PetscReal stepSize)
41 {
42   PetscReal      arcLength, arcLengthError;
43   SNES_NEWTONAL *al = (SNES_NEWTONAL *)snes->data;
44 
45   PetscFunctionBegin;
46   PetscCall(VecNorm(XStep, NORM_2, &arcLength));
47   arcLength      = PetscSqrtReal(PetscSqr(arcLength) + al->psisq * lambdaStep * lambdaStep);
48   arcLengthError = PetscAbsReal(arcLength - stepSize);
49 
50   if (arcLengthError > PETSC_SQRT_MACHINE_EPSILON) PetscCall(PetscInfo(snes, "Arc length differs from specified step size: computed=%18.16e, expected=%18.16e, error=%18.16e \n", (double)arcLength, (double)stepSize, (double)arcLengthError));
51   PetscFunctionReturn(PETSC_SUCCESS);
52 }
53 
54 /* stable implementation of roots of a*x^2 + b*x + c = 0 */
55 static inline void PetscQuadraticRoots(PetscReal a, PetscReal b, PetscReal c, PetscReal *xm, PetscReal *xp)
56 {
57   PetscReal temp = -0.5 * (b + PetscCopysignReal(1.0, b) * PetscSqrtReal(b * b - 4 * a * c));
58   PetscReal x1   = temp / a;
59   PetscReal x2   = c / temp;
60   *xm            = PetscMin(x1, x2);
61   *xp            = PetscMax(x1, x2);
62 }
63 
64 static PetscErrorCode SNESNewtonALSetCorrectionType_NEWTONAL(SNES snes, SNESNewtonALCorrectionType ctype)
65 {
66   SNES_NEWTONAL *al = (SNES_NEWTONAL *)snes->data;
67 
68   PetscFunctionBegin;
69   al->correction_type = ctype;
70   PetscFunctionReturn(PETSC_SUCCESS);
71 }
72 
73 /*@
74   SNESNewtonALSetCorrectionType - Set the type of correction to use in the arc-length continuation method.
75 
76   Logically Collective
77 
78   Input Parameters:
79 + snes  - the nonlinear solver object
80 - ctype - the type of correction to use
81 
82   Options Database Key:
83 . -snes_newtonal_correction_type <type> - Set the type of correction to use; use -help for a list of available types
84 
85   Level: intermediate
86 
87 .seealso: [](ch_snes), `SNES`, `SNESNEWTONAL`, `SNESNewtonALCorrectionType`
88 @*/
89 PetscErrorCode SNESNewtonALSetCorrectionType(SNES snes, SNESNewtonALCorrectionType ctype)
90 {
91   PetscFunctionBegin;
92   PetscValidHeaderSpecific(snes, SNES_CLASSID, 1);
93   PetscValidLogicalCollectiveEnum(snes, ctype, 2);
94   PetscTryMethod(snes, "SNESNewtonALSetCorrectionType_C", (SNES, SNESNewtonALCorrectionType), (snes, ctype));
95   PetscFunctionReturn(PETSC_SUCCESS);
96 }
97 
98 static PetscErrorCode SNESNewtonALSetFunction_NEWTONAL(SNES snes, SNESFunctionFn *func, void *ctx)
99 {
100   SNES_NEWTONAL *al = (SNES_NEWTONAL *)snes->data;
101 
102   PetscFunctionBegin;
103   al->computealfunction = func;
104   al->alctx             = ctx;
105   PetscFunctionReturn(PETSC_SUCCESS);
106 }
107 
108 /*@C
109   SNESNewtonALSetFunction - Sets a user function that is called at each function evaluation to
110   compute the tangent load vector for the arc-length continuation method.
111 
112   Logically Collective
113 
114   Input Parameters:
115 + snes - the nonlinear solver object
116 . func - [optional] tangent load function evaluation routine, see `SNESFunctionFn` for the calling sequence. `U` is the current solution vector, `Q` is the output tangent load vector
117 - ctx  - [optional] user-defined context for private data for the function evaluation routine (may be `NULL`)
118 
119   Level: intermediate
120 
121   Notes:
122   If the current value of the load parameter is needed in `func`, it can be obtained with `SNESNewtonALGetLoadParameter()`.
123 
124   The tangent load vector is the partial derivative of external load with respect to the load parameter.
125   In the case of proportional loading, the tangent load vector is the full external load vector at the end of the load step.
126 
127 .seealso: [](ch_snes), `SNES`, `SNESNEWTONAL`, `SNESNewtonALGetFunction()`, `SNESNewtonALGetLoadParameter()`
128 @*/
129 PetscErrorCode SNESNewtonALSetFunction(SNES snes, SNESFunctionFn *func, void *ctx)
130 {
131   PetscFunctionBegin;
132   PetscValidHeaderSpecific(snes, SNES_CLASSID, 1);
133   PetscTryMethod(snes, "SNESNewtonALSetFunction_C", (SNES, SNESFunctionFn *, void *), (snes, func, ctx));
134   PetscFunctionReturn(PETSC_SUCCESS);
135 }
136 
137 static PetscErrorCode SNESNewtonALGetFunction_NEWTONAL(SNES snes, SNESFunctionFn **func, void **ctx)
138 {
139   SNES_NEWTONAL *al = (SNES_NEWTONAL *)snes->data;
140 
141   PetscFunctionBegin;
142   if (func) *func = al->computealfunction;
143   if (ctx) *ctx = al->alctx;
144   PetscFunctionReturn(PETSC_SUCCESS);
145 }
146 
147 /*@C
148   SNESNewtonALGetFunction - Get the user function and context set with `SNESNewtonALSetFunction`
149 
150   Logically Collective
151 
152   Input Parameters:
153 + snes - the nonlinear solver object
154 . func - [optional] tangent load function evaluation routine, see `SNESNewtonALSetFunction()` for the call sequence
155 - ctx  - [optional] user-defined context for private data for the function evaluation routine (may be `NULL`)
156 
157   Level: intermediate
158 
159 .seealso: [](ch_snes), `SNES`, `SNESNEWTONAL`, `SNESNewtonALSetFunction()`
160 @*/
161 PetscErrorCode SNESNewtonALGetFunction(SNES snes, SNESFunctionFn **func, void **ctx)
162 {
163   PetscFunctionBegin;
164   PetscValidHeaderSpecific(snes, SNES_CLASSID, 1);
165   PetscUseMethod(snes, "SNESNewtonALGetFunction_C", (SNES, SNESFunctionFn **, void **), (snes, func, ctx));
166   PetscFunctionReturn(PETSC_SUCCESS);
167 }
168 
169 static PetscErrorCode SNESNewtonALGetLoadParameter_NEWTONAL(SNES snes, PetscReal *lambda)
170 {
171   SNES_NEWTONAL *al;
172 
173   PetscFunctionBeginHot;
174   al      = (SNES_NEWTONAL *)snes->data;
175   *lambda = al->lambda;
176   PetscFunctionReturn(PETSC_SUCCESS);
177 }
178 
179 /*@C
180   SNESNewtonALGetLoadParameter - Get the value of the load parameter `lambda` for the arc-length continuation method.
181 
182   Logically Collective
183 
184   Input Parameter:
185 . snes - the nonlinear solver object
186 
187   Output Parameter:
188 . lambda - the arc-length parameter
189 
190   Level: intermediate
191 
192   Notes:
193   This function should be used in the functions provided to `SNESSetFunction()` and `SNESNewtonALSetFunction()`
194   to compute the residual and tangent load vectors for a given value of `lambda` (0 <= lambda <= 1).
195 
196   Usually, `lambda` is used to scale the external force vector in the residual function, i.e. proportional loading,
197   in which case the tangent load vector is the full external force vector.
198 
199 .seealso: [](ch_snes), `SNES`, `SNESNEWTONAL`, `SNESNewtonALSetFunction()`
200 @*/
201 PetscErrorCode SNESNewtonALGetLoadParameter(SNES snes, PetscReal *lambda)
202 {
203   PetscFunctionBeginHot;
204   PetscValidHeaderSpecific(snes, SNES_CLASSID, 1);
205   PetscAssertPointer(lambda, 2);
206   PetscUseMethod(snes, "SNESNewtonALGetLoadParameter_C", (SNES, PetscReal *), (snes, lambda));
207   PetscFunctionReturn(PETSC_SUCCESS);
208 }
209 
210 static PetscErrorCode SNESNewtonALComputeFunction_NEWTONAL(SNES snes, Vec X, Vec Q)
211 {
212   void           *ctx               = NULL;
213   SNESFunctionFn *computealfunction = NULL;
214   SNES_NEWTONAL  *al;
215 
216   PetscFunctionBegin;
217   al = (SNES_NEWTONAL *)snes->data;
218   PetscCall(SNESNewtonALGetFunction(snes, &computealfunction, &ctx));
219 
220   PetscCall(VecZeroEntries(Q));
221   PetscCheck(computealfunction || (snes->vec_rhs && al->scale_rhs), PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "No tangent load function or rhs vector has been set");
222   if (computealfunction) {
223     PetscCall(VecLockReadPush(X));
224     PetscCallBack("SNES callback NewtonAL tangent load function", (*computealfunction)(snes, X, Q, ctx));
225     PetscCall(VecLockReadPop(X));
226   }
227   if (al->scale_rhs && snes->vec_rhs) {
228     /* Save original RHS vector values, then scale `snes->vec_rhs` by load parameter */
229     if (!al->vec_rhs_orig) { PetscCall(VecDuplicate(snes->vec_rhs, &al->vec_rhs_orig)); }
230     if (!al->copied_rhs) {
231       PetscCall(VecCopy(snes->vec_rhs, al->vec_rhs_orig));
232       al->copied_rhs = PETSC_TRUE;
233     }
234     PetscCall(VecAXPBY(snes->vec_rhs, al->lambda, 0.0, al->vec_rhs_orig));
235     PetscCall(VecAXPY(Q, 1, al->vec_rhs_orig));
236   }
237   PetscFunctionReturn(PETSC_SUCCESS);
238 }
239 
240 /*@C
241   SNESNewtonALComputeFunction - Calls the function that has been set with `SNESNewtonALSetFunction()`.
242 
243   Collective
244 
245   Input Parameters:
246 + snes - the `SNES` context
247 - X    - input vector
248 
249   Output Parameter:
250 . Q - tangent load vector, as set by `SNESNewtonALSetFunction()`
251 
252   Level: developer
253 
254   Note:
255   `SNESNewtonALComputeFunction()` is typically used within nonlinear solvers
256   implementations, so users would not generally call this routine themselves.
257 
258 .seealso: [](ch_snes), `SNES`, `SNESNewtonALSetFunction()`, `SNESNewtonALGetFunction()`
259 @*/
260 PetscErrorCode SNESNewtonALComputeFunction(SNES snes, Vec X, Vec Q)
261 {
262   PetscFunctionBegin;
263   PetscValidHeaderSpecific(snes, SNES_CLASSID, 1);
264   PetscValidHeaderSpecific(X, VEC_CLASSID, 2);
265   PetscValidHeaderSpecific(Q, VEC_CLASSID, 3);
266   PetscCheckSameComm(snes, 1, X, 2);
267   PetscCheckSameComm(snes, 1, Q, 3);
268   PetscCall(VecValidValues_Internal(X, 2, PETSC_TRUE));
269   PetscCall(PetscLogEventBegin(SNES_NewtonALEval, snes, X, Q, 0));
270   PetscTryMethod(snes, "SNESNewtonALComputeFunction_C", (SNES, Vec, Vec), (snes, X, Q));
271   PetscCall(PetscLogEventEnd(SNES_NewtonALEval, snes, X, Q, 0));
272   PetscFunctionReturn(PETSC_SUCCESS);
273 }
274 
275 /*
276   SNESSolve_NEWTONAL - Solves a nonlinear system with Newton's method with arc length continuation.
277 
278   Input Parameter:
279 . snes - the `SNES` context
280 
281   Application Interface Routine: SNESSolve()
282 */
283 static PetscErrorCode SNESSolve_NEWTONAL(SNES snes)
284 {
285   SNES_NEWTONAL *data = (SNES_NEWTONAL *)snes->data;
286   PetscInt       maxits, maxincs, lits;
287   PetscReal      fnorm, xnorm, ynorm, stepSize;
288   Vec            DeltaX, deltaX, X, R, Q, deltaX_Q, deltaX_R;
289 
290   PetscFunctionBegin;
291   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);
292 
293   /* Register citations */
294   PetscCall(PetscCitationsRegister(SNESCitation, &SNEScite));
295   if (data->correction_type == SNES_NEWTONAL_CORRECTION_EXACT) {
296     PetscCall(PetscCitationsRegister(NewtonALExactCitation, &NewtonALExactCitationSet));
297   } else if (data->correction_type == SNES_NEWTONAL_CORRECTION_NORMAL) {
298     PetscCall(PetscCitationsRegister(NewtonALNormalCitation, &NewtonALNormalCitationSet));
299   }
300 
301   data->copied_rhs             = PETSC_FALSE;
302   data->lambda_update          = 0.0;
303   data->lambda                 = 0.0;
304   snes->numFailures            = 0;
305   snes->numLinearSolveFailures = 0;
306   snes->reason                 = SNES_CONVERGED_ITERATING;
307 
308   maxits   = snes->max_its;                /* maximum number of iterations */
309   maxincs  = data->max_continuation_steps; /* maximum number of increments */
310   X        = snes->vec_sol;                /* solution vector */
311   R        = snes->vec_func;               /* residual vector */
312   Q        = snes->work[0];                /* tangent load vector */
313   deltaX_Q = snes->work[1];                /* variation of X with respect to lambda */
314   deltaX_R = snes->work[2];                /* linearized error correction */
315   DeltaX   = snes->work[3];                /* step from equilibrium */
316   deltaX   = snes->vec_sol_update;         /* full newton step */
317   stepSize = data->step_size;              /* initial step size */
318 
319   PetscCall(VecZeroEntries(DeltaX));
320 
321   /* set snes->max_its for convergence test */
322   snes->max_its = maxits * maxincs;
323 
324   /* main incremental-iterative loop */
325   for (PetscInt i = 0; i < maxincs || maxincs < 0; i++) {
326     PetscReal deltaLambda;
327 
328     PetscCall(PetscObjectSAWsTakeAccess((PetscObject)snes));
329     snes->norm = 0.0;
330     PetscCall(PetscObjectSAWsGrantAccess((PetscObject)snes));
331     PetscCall(SNESNewtonALComputeFunction(snes, X, Q));
332     PetscCall(SNESComputeFunction(snes, X, R));
333     PetscCall(VecAXPY(R, 1, Q));           /* R <- R + Q */
334     PetscCall(VecNorm(R, NORM_2, &fnorm)); /* fnorm <- ||R|| */
335     SNESCheckFunctionNorm(snes, fnorm);
336 
337     /* Monitor convergence */
338     PetscCall(SNESConverged(snes, snes->iter, 0.0, 0.0, fnorm));
339     PetscCall(SNESMonitor(snes, snes->iter, fnorm));
340     if (i == 0 && snes->reason) break;
341     for (PetscInt j = 0; j < maxits; j++) {
342       PetscReal normsqX_Q, deltaS = 1;
343 
344       /* Call general purpose update function */
345       PetscTryTypeMethod(snes, update, snes->iter);
346 
347       PetscCall(SNESComputeJacobian(snes, X, snes->jacobian, snes->jacobian_pre));
348       SNESCheckJacobianDomainerror(snes);
349       PetscCall(KSPSetOperators(snes->ksp, snes->jacobian, snes->jacobian_pre));
350       /* Solve J deltaX_Q = Q, where J is Jacobian matrix */
351       PetscCall(KSPSolve(snes->ksp, Q, deltaX_Q));
352       SNESCheckKSPSolve(snes);
353       PetscCall(KSPGetIterationNumber(snes->ksp, &lits));
354       PetscCall(PetscInfo(snes, "iter=%" PetscInt_FMT ", tangent load linear solve iterations=%" PetscInt_FMT "\n", snes->iter, lits));
355       /* Compute load parameter variation */
356       PetscCall(VecNorm(deltaX_Q, NORM_2, &normsqX_Q));
357       normsqX_Q *= normsqX_Q;
358       /* On first iter, use predictor. This is the same regardless of corrector scheme. */
359       if (j == 0) {
360         PetscReal sign = 1.0;
361         if (i > 0) {
362           PetscCall(VecDotRealPart(DeltaX, deltaX_Q, &sign));
363           sign += data->psisq * data->lambda_update;
364           sign = sign >= 0 ? 1.0 : -1.0;
365         }
366         data->lambda_update = 0.0;
367         PetscCall(VecZeroEntries(DeltaX));
368         deltaLambda = sign * stepSize / PetscSqrtReal(normsqX_Q + data->psisq);
369       } else {
370         /* Solve J deltaX_R = -R */
371         PetscCall(KSPSolve(snes->ksp, R, deltaX_R));
372         SNESCheckKSPSolve(snes);
373         PetscCall(KSPGetIterationNumber(snes->ksp, &lits));
374         PetscCall(PetscInfo(snes, "iter=%" PetscInt_FMT ", residual linear solve iterations=%" PetscInt_FMT "\n", snes->iter, lits));
375         PetscCall(VecScale(deltaX_R, -1));
376 
377         if (data->correction_type == SNES_NEWTONAL_CORRECTION_NORMAL) {
378           /*
379             Take a step orthogonal to the current incremental update DeltaX.
380             Note, this approach is cheaper than the exact correction, but may exhibit convergence
381             issues due to the iterative trial points not being on the quadratic constraint surface.
382             On the bright side, we always have a real and unique solution for deltaLambda.
383           */
384           PetscScalar coefs[2];
385           Vec         rhs[] = {deltaX_R, deltaX_Q};
386 
387           PetscCall(VecMDot(DeltaX, 2, rhs, coefs));
388           deltaLambda = -PetscRealPart(coefs[0]) / (PetscRealPart(coefs[1]) + data->psisq * data->lambda_update);
389         } else {
390           /*
391             Solve
392               a*deltaLambda^2 + b*deltaLambda + c = 0  (*)
393             where
394               a = a0
395               b = b0 + b1*deltaS
396               c = c0 + c1*deltaS + c2*deltaS^2
397             and deltaS is either 1, or the largest value in (0, 1) that satisfies
398               b^2 - 4*a*c = as*deltaS^2 + bs*deltaS + cs >= 0
399             where
400               as = b1^2 - 4*a0*c2
401               bs = 2*b1*b0 - 4*a0*c1
402               cs = b0^2 - 4*a0*c0
403             These "partial corrections" prevent (*) from having complex roots.
404           */
405           PetscReal   psisqLambdaUpdate, discriminant;
406           PetscReal   as, bs, cs;
407           PetscReal   a0, b0, b1, c0, c1, c2;
408           PetscScalar coefs1[3]; /* coefs[0] = deltaX_Q*DeltaX, coefs[1] = deltaX_R*DeltaX, coefs[2] = DeltaX*DeltaX */
409           PetscScalar coefs2[2]; /* coefs[0] = deltaX_Q*deltaX_R, coefs[1] = deltaX_R*deltaX_R */
410           const Vec   rhs1[3] = {deltaX_Q, deltaX_R, DeltaX};
411           const Vec   rhs2[2] = {deltaX_Q, deltaX_R};
412 
413           psisqLambdaUpdate = data->psisq * data->lambda_update;
414           PetscCall(VecMDotBegin(DeltaX, 3, rhs1, coefs1));
415           PetscCall(VecMDotBegin(deltaX_R, 2, rhs2, coefs2));
416           PetscCall(VecMDotEnd(DeltaX, 3, rhs1, coefs1));
417           PetscCall(VecMDotEnd(deltaX_R, 2, rhs2, coefs2));
418 
419           a0 = normsqX_Q + data->psisq;
420           b0 = 2 * (PetscRealPart(coefs1[0]) + psisqLambdaUpdate);
421           b1 = 2 * PetscRealPart(coefs2[0]);
422           c0 = PetscRealPart(coefs1[2]) + psisqLambdaUpdate * data->lambda_update - stepSize * stepSize;
423           c1 = 2 * PetscRealPart(coefs1[1]);
424           c2 = PetscRealPart(coefs2[1]);
425 
426           as = b1 * b1 - 4 * a0 * c2;
427           bs = 2 * (b1 * b0 - 2 * a0 * c1);
428           cs = b0 * b0 - 4 * a0 * c0;
429 
430           discriminant = cs + bs * deltaS + as * deltaS * deltaS;
431 
432           if (discriminant < 0) {
433             /* Take deltaS < 1 with the unique root -b/(2*a) */
434             PetscReal x1;
435 
436             /* Compute deltaS to be the largest root of (as * x^2 + bs * x + cs = 0) */
437             PetscQuadraticRoots(as, bs, cs, &x1, &deltaS);
438             PetscCall(PetscInfo(snes, "iter=%" PetscInt_FMT ", discriminant=%18.16e < 0, shrinking residual update size to deltaS = %18.16e\n", snes->iter, (double)discriminant, (double)deltaS));
439             deltaLambda = -0.5 * (b0 + b1 * deltaS) / a0;
440           } else {
441             /* Use deltaS = 1, pick root that is closest to the last point to prevent doubling back */
442             PetscReal dlambda1, dlambda2;
443 
444             PetscQuadraticRoots(a0, b0 + b1, c0 + c1 + c2, &dlambda1, &dlambda2);
445             deltaLambda = (b0 * dlambda1) > (b0 * dlambda2) ? dlambda1 : dlambda2;
446           }
447         }
448       }
449       PetscCall(PetscObjectSAWsTakeAccess((PetscObject)snes));
450       data->lambda = data->lambda + deltaLambda;
451       if (data->lambda > data->lambda_max) {
452         /* Ensure that lambda = lambda_max exactly at the end of incremental process. This ensures the final solution matches the problem we want to solve. */
453         deltaLambda  = deltaLambda - (data->lambda - data->lambda_max);
454         data->lambda = data->lambda_max;
455       }
456       if (data->lambda < data->lambda_min) {
457         // LCOV_EXCL_START
458         /* Ensure that lambda >= lambda_min. This prevents some potential oscillatory behavior. */
459         deltaLambda  = deltaLambda - (data->lambda - data->lambda_min);
460         data->lambda = data->lambda_min;
461         // LCOV_EXCL_STOP
462       }
463       data->lambda_update = data->lambda_update + deltaLambda;
464       PetscCall(PetscObjectSAWsGrantAccess((PetscObject)snes));
465       PetscCall(PetscInfo(snes, "iter=%" PetscInt_FMT ", lambda=%18.16e, lambda_update=%18.16e\n", snes->iter, (double)data->lambda, (double)data->lambda_update));
466       if (j == 0) {
467         /* deltaX = deltaLambda*deltaX_Q */
468         PetscCall(VecCopy(deltaX_Q, deltaX));
469         PetscCall(VecScale(deltaX, deltaLambda));
470       } else {
471         /* deltaX = deltaS*deltaX_R + deltaLambda*deltaX_Q */
472         PetscCall(VecAXPBYPCZ(deltaX, deltaS, deltaLambda, 0, deltaX_R, deltaX_Q));
473       }
474       PetscCall(VecAXPY(DeltaX, 1, deltaX));
475       PetscCall(VecAXPY(X, 1, deltaX));
476       /* Q = -dF/dlambda(X, lambda)*/
477       PetscCall(SNESNewtonALComputeFunction(snes, X, Q));
478       /* R = F(X, lambda) */
479       PetscCall(SNESComputeFunction(snes, X, R));
480       PetscCall(VecNormBegin(R, NORM_2, &fnorm));
481       PetscCall(VecNormBegin(X, NORM_2, &xnorm));
482       PetscCall(VecNormBegin(deltaX, NORM_2, &ynorm));
483       PetscCall(VecNormEnd(R, NORM_2, &fnorm));
484       PetscCall(VecNormEnd(X, NORM_2, &xnorm));
485       PetscCall(VecNormEnd(deltaX, NORM_2, &ynorm));
486 
487       if (PetscLogPrintInfo) PetscCall(SNESNewtonALCheckArcLength(snes, DeltaX, data->lambda_update, stepSize));
488 
489       /* Monitor convergence */
490       PetscCall(PetscObjectSAWsTakeAccess((PetscObject)snes));
491       snes->iter++;
492       snes->norm  = fnorm;
493       snes->ynorm = ynorm;
494       snes->xnorm = xnorm;
495       PetscCall(PetscObjectSAWsGrantAccess((PetscObject)snes));
496       PetscCall(SNESLogConvergenceHistory(snes, snes->norm, lits));
497       PetscCall(SNESConverged(snes, snes->iter, xnorm, ynorm, fnorm));
498       PetscCall(SNESMonitor(snes, snes->iter, snes->norm));
499       if (snes->reason) break;
500       if (j == maxits - 1) {
501         snes->reason = SNES_DIVERGED_MAX_IT;
502         break;
503       }
504     }
505     if (snes->reason < 0) break;
506     if (data->lambda >= data->lambda_max) {
507       break;
508     } else if (maxincs > 0 && i == maxincs - 1) {
509       snes->reason = SNES_DIVERGED_MAX_IT;
510       break;
511     } else {
512       snes->reason = SNES_CONVERGED_ITERATING;
513     }
514   }
515   /* Reset RHS vector, if changed */
516   if (data->copied_rhs) {
517     PetscCall(VecCopy(data->vec_rhs_orig, snes->vec_rhs));
518     data->copied_rhs = PETSC_FALSE;
519   }
520   snes->max_its = maxits; /* reset snes->max_its */
521   PetscFunctionReturn(PETSC_SUCCESS);
522 }
523 
524 /*
525    SNESSetUp_NEWTONAL - Sets up the internal data structures for the later use
526    of the SNESNEWTONAL nonlinear solver.
527 
528    Input Parameter:
529 .  snes - the SNES context
530 .  x - the solution vector
531 
532    Application Interface Routine: SNESSetUp()
533  */
534 static PetscErrorCode SNESSetUp_NEWTONAL(SNES snes)
535 {
536   PetscFunctionBegin;
537   PetscCall(SNESSetWorkVecs(snes, 4));
538   PetscCall(SNESSetUpMatrices(snes));
539   PetscFunctionReturn(PETSC_SUCCESS);
540 }
541 
542 /*
543    SNESSetFromOptions_NEWTONAL - Sets various parameters for the SNESNEWTONAL method.
544 
545    Input Parameter:
546 .  snes - the SNES context
547 
548    Application Interface Routine: SNESSetFromOptions()
549 */
550 static PetscErrorCode SNESSetFromOptions_NEWTONAL(SNES snes, PetscOptionItems PetscOptionsObject)
551 {
552   SNES_NEWTONAL             *data            = (SNES_NEWTONAL *)snes->data;
553   SNESNewtonALCorrectionType correction_type = data->correction_type;
554 
555   PetscFunctionBegin;
556   PetscOptionsHeadBegin(PetscOptionsObject, "SNES Newton Arc Length options");
557   PetscCall(PetscOptionsReal("-snes_newtonal_step_size", "Initial arc length increment step size", "SNESNewtonAL", data->step_size, &data->step_size, NULL));
558   PetscCall(PetscOptionsInt("-snes_newtonal_max_continuation_steps", "Maximum number of increment steps", "SNESNewtonAL", data->max_continuation_steps, &data->max_continuation_steps, NULL));
559   PetscCall(PetscOptionsReal("-snes_newtonal_psisq", "Regularization parameter for arc length continuation, 0 for cylindrical", "SNESNewtonAL", data->psisq, &data->psisq, NULL));
560   PetscCall(PetscOptionsReal("-snes_newtonal_lambda_min", "Minimum value of the load parameter lambda", "SNESNewtonAL", data->lambda_min, &data->lambda_min, NULL));
561   PetscCall(PetscOptionsReal("-snes_newtonal_lambda_max", "Maximum value of the load parameter lambda", "SNESNewtonAL", data->lambda_max, &data->lambda_max, NULL));
562   PetscCall(PetscOptionsBool("-snes_newtonal_scale_rhs", "Scale the constant vector passed to `SNESSolve` by the load parameter lambda", "SNESNewtonAL", data->scale_rhs, &data->scale_rhs, NULL));
563   PetscCall(PetscOptionsEnum("-snes_newtonal_correction_type", "Type of correction to use in the arc-length continuation method", "SNESNewtonALCorrectionType", SNESNewtonALCorrectionTypes, (PetscEnum)correction_type, (PetscEnum *)&correction_type, NULL));
564   PetscCall(SNESNewtonALSetCorrectionType(snes, correction_type));
565   PetscOptionsHeadEnd();
566   PetscFunctionReturn(PETSC_SUCCESS);
567 }
568 
569 static PetscErrorCode SNESReset_NEWTONAL(SNES snes)
570 {
571   SNES_NEWTONAL *al = (SNES_NEWTONAL *)snes->data;
572 
573   PetscFunctionBegin;
574   PetscCall(VecDestroy(&al->vec_rhs_orig));
575   PetscFunctionReturn(PETSC_SUCCESS);
576 }
577 
578 /*
579    SNESDestroy_NEWTONAL - Destroys the private SNES_NEWTONAL context that was created
580    with SNESCreate_NEWTONAL().
581 
582    Input Parameter:
583 .  snes - the SNES context
584 
585    Application Interface Routine: SNESDestroy()
586  */
587 static PetscErrorCode SNESDestroy_NEWTONAL(SNES snes)
588 {
589   PetscFunctionBegin;
590   PetscCall(PetscObjectComposeFunction((PetscObject)snes, "SNESNewtonALSetCorrectionType_C", NULL));
591   PetscCall(PetscObjectComposeFunction((PetscObject)snes, "SNESNewtonALGetLoadParameter_C", NULL));
592   PetscCall(PetscObjectComposeFunction((PetscObject)snes, "SNESNewtonALSetFunction_C", NULL));
593   PetscCall(PetscObjectComposeFunction((PetscObject)snes, "SNESNewtonALGetFunction_C", NULL));
594   PetscCall(PetscObjectComposeFunction((PetscObject)snes, "SNESNewtonALComputeFunction_C", NULL));
595   PetscCall(PetscFree(snes->data));
596   PetscFunctionReturn(PETSC_SUCCESS);
597 }
598 
599 /*MC
600   SNESNEWTONAL - Newton based nonlinear solver that uses a arc-length continuation method to solve the nonlinear system.
601 
602   Options Database Keys:
603 +   -snes_newtonal_step_size <1.0>              - Initial arc length increment step size
604 .   -snes_newtonal_max_continuation_steps <100> - Maximum number of continuation steps, or negative for no limit (not recommended)
605 .   -snes_newtonal_psisq <1.0>                  - Regularization parameter for arc length continuation, 0 for cylindrical. Larger values generally lead to more steps.
606 .   -snes_newtonal_lambda_min <0.0>             - Minimum value of the load parameter lambda
607 .   -snes_newtonal_lambda_max <1.0>             - Maximum value of the load parameter lambda
608 .   -snes_newtonal_scale_rhs <true>             - Scale the constant vector passed to `SNESSolve` by the load parameter lambda
609 -   -snes_newtonal_correction_type <exact>      - Type of correction to use in the arc-length continuation method, `exact` or `normal`
610 
611   Level: intermediate
612 
613   Note:
614   The exact correction scheme with partial updates is detailed in {cite}`Ritto-CorreaCamotim2008` and the implementation of the
615   normal correction scheme is based on {cite}`LeonPaulinoPereiraMenezesLages_2011`.
616 
617 .seealso: [](ch_snes), `SNESCreate()`, `SNES`, `SNESSetType()`, `SNESNEWTONAL`, `SNESNewtonALSetFunction()`, `SNESNewtonALGetFunction()`, `SNESNewtonALGetLoadParameter()`
618 M*/
619 PETSC_EXTERN PetscErrorCode SNESCreate_NEWTONAL(SNES snes)
620 {
621   SNES_NEWTONAL *arclengthParameters;
622 
623   PetscFunctionBegin;
624   snes->ops->setup          = SNESSetUp_NEWTONAL;
625   snes->ops->solve          = SNESSolve_NEWTONAL;
626   snes->ops->destroy        = SNESDestroy_NEWTONAL;
627   snes->ops->setfromoptions = SNESSetFromOptions_NEWTONAL;
628   snes->ops->reset          = SNESReset_NEWTONAL;
629 
630   snes->usesksp = PETSC_TRUE;
631   snes->usesnpc = PETSC_FALSE;
632 
633   PetscCall(SNESParametersInitialize(snes));
634   PetscObjectParameterSetDefault(snes, max_funcs, 30000);
635   PetscObjectParameterSetDefault(snes, max_its, 50);
636 
637   snes->alwayscomputesfinalresidual = PETSC_TRUE;
638 
639   PetscCall(PetscNew(&arclengthParameters));
640   arclengthParameters->lambda                 = 0.0;
641   arclengthParameters->lambda_update          = 0.0;
642   arclengthParameters->step_size              = 1.0;
643   arclengthParameters->max_continuation_steps = 100;
644   arclengthParameters->psisq                  = 1.0;
645   arclengthParameters->lambda_min             = 0.0;
646   arclengthParameters->lambda_max             = 1.0;
647   arclengthParameters->scale_rhs              = PETSC_TRUE;
648   arclengthParameters->correction_type        = SNES_NEWTONAL_CORRECTION_EXACT;
649   snes->data                                  = (void *)arclengthParameters;
650 
651   PetscCall(PetscObjectComposeFunction((PetscObject)snes, "SNESNewtonALSetCorrectionType_C", SNESNewtonALSetCorrectionType_NEWTONAL));
652   PetscCall(PetscObjectComposeFunction((PetscObject)snes, "SNESNewtonALGetLoadParameter_C", SNESNewtonALGetLoadParameter_NEWTONAL));
653   PetscCall(PetscObjectComposeFunction((PetscObject)snes, "SNESNewtonALSetFunction_C", SNESNewtonALSetFunction_NEWTONAL));
654   PetscCall(PetscObjectComposeFunction((PetscObject)snes, "SNESNewtonALGetFunction_C", SNESNewtonALGetFunction_NEWTONAL));
655   PetscCall(PetscObjectComposeFunction((PetscObject)snes, "SNESNewtonALComputeFunction_C", SNESNewtonALComputeFunction_NEWTONAL));
656   PetscFunctionReturn(PETSC_SUCCESS);
657 }
658