Actual source code: linesearchnleqerr.c
1: #include <petsc/private/linesearchimpl.h>
2: #include <petsc/private/snesimpl.h>
4: typedef struct {
5: PetscReal norm_delta_x_prev; /* norm of previous update */
6: PetscReal norm_bar_delta_x_prev; /* norm of previous bar update */
7: PetscReal mu_curr; /* current local Lipschitz estimate */
8: PetscReal lambda_prev; /* previous step length: for some reason SNESLineSearchGetLambda returns 1 instead of the previous step length */
9: } SNESLineSearch_NLEQERR;
11: static PetscBool NLEQERR_cited = PETSC_FALSE;
12: static const char NLEQERR_citation[] = "@book{deuflhard2011,\n"
13: " title = {Newton Methods for Nonlinear Problems},\n"
14: " author = {Peter Deuflhard},\n"
15: " volume = 35,\n"
16: " year = 2011,\n"
17: " isbn = {978-3-642-23898-7},\n"
18: " doi = {10.1007/978-3-642-23899-4},\n"
19: " publisher = {Springer-Verlag},\n"
20: " address = {Berlin, Heidelberg}\n}\n";
22: static PetscErrorCode SNESLineSearchReset_NLEQERR(SNESLineSearch linesearch)
23: {
24: SNESLineSearch_NLEQERR *nleqerr = (SNESLineSearch_NLEQERR *)linesearch->data;
26: PetscFunctionBegin;
27: nleqerr->mu_curr = 0.0;
28: nleqerr->norm_delta_x_prev = -1.0;
29: nleqerr->norm_bar_delta_x_prev = -1.0;
30: PetscFunctionReturn(PETSC_SUCCESS);
31: }
33: static PetscErrorCode SNESLineSearchApply_NLEQERR(SNESLineSearch linesearch)
34: {
35: PetscBool changed_y, changed_w;
36: Vec X, F, Y, W, G;
37: SNES snes;
38: PetscReal fnorm, xnorm, ynorm, gnorm, wnorm;
39: PetscReal lambda, minlambda, stol;
40: PetscViewer monitor;
41: PetscInt max_its, count, snes_iteration;
42: PetscReal theta, mudash, lambdadash;
43: SNESLineSearch_NLEQERR *nleqerr = (SNESLineSearch_NLEQERR *)linesearch->data;
44: KSPConvergedReason kspreason;
46: PetscFunctionBegin;
47: PetscCall(PetscCitationsRegister(NLEQERR_citation, &NLEQERR_cited));
49: PetscCall(SNESLineSearchGetVecs(linesearch, &X, &F, &Y, &W, &G));
50: PetscCall(SNESLineSearchGetNorms(linesearch, &xnorm, &fnorm, &ynorm));
51: PetscCall(SNESLineSearchGetLambda(linesearch, &lambda));
52: PetscCall(SNESLineSearchGetSNES(linesearch, &snes));
53: PetscCall(SNESLineSearchGetDefaultMonitor(linesearch, &monitor));
54: PetscCall(SNESLineSearchGetTolerances(linesearch, &minlambda, NULL, NULL, NULL, NULL, &max_its));
55: PetscCall(SNESGetTolerances(snes, NULL, NULL, &stol, NULL, NULL));
57: /* reset the state of the Lipschitz estimates */
58: PetscCall(SNESGetIterationNumber(snes, &snes_iteration));
59: if (!snes_iteration) PetscCall(SNESLineSearchReset_NLEQERR(linesearch));
61: /* precheck */
62: PetscCall(SNESLineSearchPreCheck(linesearch, X, Y, &changed_y));
63: PetscCall(SNESLineSearchSetReason(linesearch, SNES_LINESEARCH_SUCCEEDED));
65: PetscCall(VecNormBegin(Y, NORM_2, &ynorm));
66: PetscCall(VecNormBegin(X, NORM_2, &xnorm));
67: PetscCall(VecNormEnd(Y, NORM_2, &ynorm));
68: PetscCall(VecNormEnd(X, NORM_2, &xnorm));
70: /* Note: Y is *minus* the Newton step. For whatever reason PETSc doesn't solve with the minus on the RHS. */
72: if (ynorm == 0.0) {
73: if (monitor) {
74: PetscCall(PetscViewerASCIIAddTab(monitor, ((PetscObject)linesearch)->tablevel));
75: PetscCall(PetscViewerASCIIPrintf(monitor, " Line search: Initial direction and size is 0\n"));
76: PetscCall(PetscViewerASCIISubtractTab(monitor, ((PetscObject)linesearch)->tablevel));
77: }
78: PetscCall(VecCopy(X, W));
79: PetscCall(VecCopy(F, G));
80: PetscCall(SNESLineSearchSetNorms(linesearch, xnorm, fnorm, ynorm));
81: PetscCall(SNESLineSearchSetReason(linesearch, SNES_LINESEARCH_FAILED_REDUCT));
82: PetscFunctionReturn(PETSC_SUCCESS);
83: }
85: /* At this point, we've solved the Newton system for delta_x, and we assume that
86: its norm is greater than the solution tolerance (otherwise we wouldn't be in
87: here). So let's go ahead and estimate the Lipschitz constant.
89: W contains bar_delta_x_prev at this point. */
91: if (monitor) {
92: PetscCall(PetscViewerASCIIAddTab(monitor, ((PetscObject)linesearch)->tablevel));
93: PetscCall(PetscViewerASCIIPrintf(monitor, " Line search: norm of Newton step: %14.12e\n", (double)ynorm));
94: PetscCall(PetscViewerASCIISubtractTab(monitor, ((PetscObject)linesearch)->tablevel));
95: }
97: /* this needs information from a previous iteration, so can't do it on the first one */
98: if (nleqerr->norm_delta_x_prev > 0 && nleqerr->norm_bar_delta_x_prev > 0) {
99: PetscCall(VecWAXPY(G, +1.0, Y, W)); /* bar_delta_x - delta_x; +1 because Y is -delta_x */
100: PetscCall(VecNormBegin(G, NORM_2, &gnorm));
101: PetscCall(VecNormEnd(G, NORM_2, &gnorm));
103: nleqerr->mu_curr = nleqerr->lambda_prev * (nleqerr->norm_delta_x_prev * nleqerr->norm_bar_delta_x_prev) / (gnorm * ynorm);
104: lambda = PetscMin(1.0, nleqerr->mu_curr);
106: if (monitor) {
107: PetscCall(PetscViewerASCIIAddTab(monitor, ((PetscObject)linesearch)->tablevel));
108: PetscCall(PetscViewerASCIIPrintf(monitor, " Line search: Lipschitz estimate: %14.12e; lambda: %14.12e\n", (double)nleqerr->mu_curr, (double)lambda));
109: PetscCall(PetscViewerASCIISubtractTab(monitor, ((PetscObject)linesearch)->tablevel));
110: }
111: } else {
112: lambda = linesearch->damping;
113: }
115: /* The main while loop of the algorithm.
116: At the end of this while loop, G should have the accepted new X in it. */
118: count = 0;
119: while (PETSC_TRUE) {
120: if (monitor) {
121: PetscCall(PetscViewerASCIIAddTab(monitor, ((PetscObject)linesearch)->tablevel));
122: PetscCall(PetscViewerASCIIPrintf(monitor, " Line search: entering iteration with lambda: %14.12e\n", (double)lambda));
123: PetscCall(PetscViewerASCIISubtractTab(monitor, ((PetscObject)linesearch)->tablevel));
124: }
126: /* Check that we haven't performed too many iterations */
127: count += 1;
128: if (count >= max_its) {
129: if (monitor) {
130: PetscCall(PetscViewerASCIIAddTab(monitor, ((PetscObject)linesearch)->tablevel));
131: PetscCall(PetscViewerASCIIPrintf(monitor, " Line search: maximum iterations reached\n"));
132: PetscCall(PetscViewerASCIISubtractTab(monitor, ((PetscObject)linesearch)->tablevel));
133: }
134: PetscCall(SNESLineSearchSetReason(linesearch, SNES_LINESEARCH_FAILED_REDUCT));
135: PetscFunctionReturn(PETSC_SUCCESS);
136: }
138: /* Now comes the Regularity Test. */
139: if (lambda <= minlambda) {
140: /* This isn't what is suggested by Deuflhard, but it works better in my experience */
141: if (monitor) {
142: PetscCall(PetscViewerASCIIAddTab(monitor, ((PetscObject)linesearch)->tablevel));
143: PetscCall(PetscViewerASCIIPrintf(monitor, " Line search: lambda has reached lambdamin, taking full Newton step\n"));
144: PetscCall(PetscViewerASCIISubtractTab(monitor, ((PetscObject)linesearch)->tablevel));
145: }
146: lambda = 1.0;
147: PetscCall(VecWAXPY(G, -lambda, Y, X));
149: /* and clean up the state for next time */
150: PetscCall(SNESLineSearchReset_NLEQERR(linesearch));
151: /*
152: The clang static analyzer detected a problem here; once the loop is broken the values
153: nleqerr->norm_delta_x_prev = ynorm;
154: nleqerr->norm_bar_delta_x_prev = wnorm;
155: are set, but wnorm has not even been computed.
156: I don't know if this is the correct fix but by setting ynorm and wnorm to -1.0 at
157: least the linesearch object is kept in the state set by the SNESLineSearchReset_NLEQERR() call above
158: */
159: ynorm = wnorm = -1.0;
160: break;
161: }
163: /* Compute new trial iterate */
164: PetscCall(VecWAXPY(W, -lambda, Y, X));
165: PetscCall(SNESComputeFunction(snes, W, G));
167: /* Solve linear system for bar_delta_x_curr: old Jacobian, new RHS. Note absence of minus sign, compared to Deuflhard, in keeping with PETSc convention */
168: PetscCall(KSPSolve(snes->ksp, G, W));
169: PetscCall(KSPGetConvergedReason(snes->ksp, &kspreason));
170: if (kspreason < 0) PetscCall(PetscInfo(snes, "Solution for \\bar{delta x}^{k+1} failed.\n"));
172: /* W now contains -bar_delta_x_curr. */
174: PetscCall(VecNorm(W, NORM_2, &wnorm));
175: if (monitor) {
176: PetscCall(PetscViewerASCIIAddTab(monitor, ((PetscObject)linesearch)->tablevel));
177: PetscCall(PetscViewerASCIIPrintf(monitor, " Line search: norm of simplified Newton update: %14.12e\n", (double)wnorm));
178: PetscCall(PetscViewerASCIISubtractTab(monitor, ((PetscObject)linesearch)->tablevel));
179: }
181: /* compute the monitoring quantities theta and mudash. */
183: theta = wnorm / ynorm;
185: PetscCall(VecWAXPY(G, -(1.0 - lambda), Y, W));
186: PetscCall(VecNorm(G, NORM_2, &gnorm));
188: mudash = (0.5 * ynorm * lambda * lambda) / gnorm;
190: /* Check for termination of the linesearch */
191: if (theta >= 1.0) {
192: /* need to go around again with smaller lambda */
193: if (monitor) {
194: PetscCall(PetscViewerASCIIAddTab(monitor, ((PetscObject)linesearch)->tablevel));
195: PetscCall(PetscViewerASCIIPrintf(monitor, " Line search: monotonicity check failed, ratio: %14.12e\n", (double)theta));
196: PetscCall(PetscViewerASCIISubtractTab(monitor, ((PetscObject)linesearch)->tablevel));
197: }
198: lambda = PetscMin(mudash, 0.5 * lambda);
199: lambda = PetscMax(lambda, minlambda);
200: /* continue through the loop, i.e. go back to regularity test */
201: } else {
202: /* linesearch terminated */
203: lambdadash = PetscMin(1.0, mudash);
205: if (lambdadash == 1.0 && lambda == 1.0 && wnorm <= stol) {
206: /* store the updated state, X - Y - W, in G:
207: I need to keep W for the next linesearch */
208: PetscCall(VecWAXPY(G, -1.0, Y, X));
209: PetscCall(VecAXPY(G, -1.0, W));
210: break;
211: }
213: /* Deuflhard suggests to add the following:
214: else if (lambdadash >= 4.0 * lambda) {
215: lambda = lambdadash;
216: }
217: to continue through the loop, i.e. go back to regularity test.
218: I deliberately exclude this, as I have practical experience of this
219: getting stuck in infinite loops (on e.g. an Allen--Cahn problem). */
221: else {
222: /* accept iterate without adding on, i.e. don't use bar_delta_x;
223: again, I need to keep W for the next linesearch */
224: PetscCall(VecWAXPY(G, -lambda, Y, X));
225: break;
226: }
227: }
228: }
230: if (linesearch->ops->viproject) PetscCall((*linesearch->ops->viproject)(snes, G));
232: /* W currently contains -bar_delta_u. Scale it so that it contains bar_delta_u. */
233: PetscCall(VecScale(W, -1.0));
235: /* postcheck */
236: PetscCall(SNESLineSearchPostCheck(linesearch, X, Y, G, &changed_y, &changed_w));
237: if (changed_y || changed_w) {
238: PetscCall(SNESLineSearchSetReason(linesearch, SNES_LINESEARCH_FAILED_USER));
239: PetscCall(PetscInfo(snes, "Changing the search direction here doesn't make sense.\n"));
240: PetscFunctionReturn(PETSC_SUCCESS);
241: }
243: /* copy the solution and information from this iteration over */
244: nleqerr->norm_delta_x_prev = ynorm;
245: nleqerr->norm_bar_delta_x_prev = wnorm;
246: nleqerr->lambda_prev = lambda;
248: PetscCall(VecCopy(G, X));
249: PetscCall(SNESComputeFunction(snes, X, F));
250: PetscCall(VecNorm(X, NORM_2, &xnorm));
251: PetscCall(VecNorm(F, NORM_2, &fnorm));
252: PetscCall(SNESLineSearchSetLambda(linesearch, lambda));
253: PetscCall(SNESLineSearchSetNorms(linesearch, xnorm, fnorm, (ynorm < 0 ? PETSC_INFINITY : ynorm)));
254: PetscFunctionReturn(PETSC_SUCCESS);
255: }
257: static PetscErrorCode SNESLineSearchView_NLEQERR(SNESLineSearch linesearch, PetscViewer viewer)
258: {
259: PetscBool iascii;
260: SNESLineSearch_NLEQERR *nleqerr;
262: PetscFunctionBegin;
263: PetscCall(PetscObjectTypeCompare((PetscObject)viewer, PETSCVIEWERASCII, &iascii));
264: nleqerr = (SNESLineSearch_NLEQERR *)linesearch->data;
265: if (iascii) {
266: PetscCall(PetscViewerASCIIPrintf(viewer, " NLEQ-ERR affine-covariant linesearch"));
267: PetscCall(PetscViewerASCIIPrintf(viewer, " current local Lipschitz estimate omega=%e\n", (double)nleqerr->mu_curr));
268: }
269: PetscFunctionReturn(PETSC_SUCCESS);
270: }
272: static PetscErrorCode SNESLineSearchDestroy_NLEQERR(SNESLineSearch linesearch)
273: {
274: PetscFunctionBegin;
275: PetscCall(PetscFree(linesearch->data));
276: PetscFunctionReturn(PETSC_SUCCESS);
277: }
279: /*MC
280: SNESLINESEARCHNLEQERR - Error-oriented affine-covariant globalised Newton algorithm of Deuflhard {cite}`deuflhard2011`
282: This linesearch is intended for Newton-type methods which are affine covariant. Affine covariance
283: means that Newton's method will give the same iterations for F(x) = 0 and AF(x) = 0 for any nonsingular
284: matrix A. This is a fundamental property; the philosophy of this linesearch is that globalisations
285: of Newton's method should carefully preserve it.
287: Options Database Keys:
288: + -snes_linesearch_damping<1.0> - initial step length
289: - -snes_linesearch_minlambda<1e-12> - minimum step length allowed
291: Level: advanced
293: Note:
294: Contributed by Patrick Farrell <patrick.farrell@maths.ox.ac.uk>
296: .seealso: [](ch_snes), `SNESLineSearch`, `SNES`, `SNESLineSearchCreate()`, `SNESLineSearchSetType()`
297: M*/
298: PETSC_EXTERN PetscErrorCode SNESLineSearchCreate_NLEQERR(SNESLineSearch linesearch)
299: {
300: SNESLineSearch_NLEQERR *nleqerr;
302: PetscFunctionBegin;
303: linesearch->ops->apply = SNESLineSearchApply_NLEQERR;
304: linesearch->ops->destroy = SNESLineSearchDestroy_NLEQERR;
305: linesearch->ops->setfromoptions = NULL;
306: linesearch->ops->reset = SNESLineSearchReset_NLEQERR;
307: linesearch->ops->view = SNESLineSearchView_NLEQERR;
308: linesearch->ops->setup = NULL;
310: PetscCall(PetscNew(&nleqerr));
312: linesearch->data = (void *)nleqerr;
313: linesearch->max_its = 40;
314: PetscCall(SNESLineSearchReset_NLEQERR(linesearch));
315: PetscFunctionReturn(PETSC_SUCCESS);
316: }