xref: /petsc/src/snes/impls/vi/ss/viss.c (revision 2ff79c18c26c94ed8cb599682f680f231dca6444)
1 #include <../src/snes/impls/vi/ss/vissimpl.h> /*I "petscsnes.h" I*/
2 
3 /*@
4   SNESVIComputeMeritFunction - Evaluates the merit function for the mixed complementarity problem.
5 
6   Input Parameter:
7 . phi - the `Vec` holding the evaluation of the semismooth function
8 
9   Output Parameters:
10 + merit   - the merit function 1/2 ||phi||^2
11 - phinorm - the two-norm of the vector, ||phi||
12 
13   Level: developer
14 
15 .seealso: [](ch_snes), `SNES`, `SNESVINEWTONSSLS`, `SNESVIComputeFunction()`
16 @*/
17 PetscErrorCode SNESVIComputeMeritFunction(Vec phi, PetscReal *merit, PetscReal *phinorm)
18 {
19   PetscFunctionBegin;
20   PetscCall(VecNormBegin(phi, NORM_2, phinorm));
21   PetscCall(VecNormEnd(phi, NORM_2, phinorm));
22   *merit = 0.5 * (*phinorm) * (*phinorm);
23   PetscFunctionReturn(PETSC_SUCCESS);
24 }
25 
26 static inline PetscScalar Phi(PetscScalar a, PetscScalar b)
27 {
28   return a + b - PetscSqrtScalar(a * a + b * b);
29 }
30 
31 static inline PetscScalar DPhi(PetscScalar a, PetscScalar b)
32 {
33   if ((PetscAbsScalar(a) >= 1.e-6) || (PetscAbsScalar(b) >= 1.e-6)) return 1.0 - a / PetscSqrtScalar(a * a + b * b);
34   else return .5;
35 }
36 
37 /*@
38   SNESVIComputeFunction - Provides the function that reformulates a system of nonlinear equations in mixed complementarity form to a system of nonlinear
39   equations in semismooth form.
40 
41   Input Parameters:
42 + snes   - the `SNES` context
43 . X      - current iterate
44 - functx - user defined function context
45 
46   Output Parameter:
47 . phi - the evaluation of semismooth function at `X`
48 
49   Level: developer
50 
51 .seealso: [](ch_snes), `SNES`, `SNESVINEWTONSSLS`, `SNESVIComputeMeritFunction()`
52 @*/
53 PetscErrorCode SNESVIComputeFunction(SNES snes, Vec X, Vec phi, void *functx)
54 {
55   SNES_VINEWTONSSLS *vi = (SNES_VINEWTONSSLS *)snes->data;
56   Vec                Xl = snes->xl, Xu = snes->xu, F = snes->vec_func;
57   PetscScalar       *phi_arr, *f_arr, *l, *u;
58   const PetscScalar *x_arr;
59   PetscInt           i, nlocal;
60 
61   PetscFunctionBegin;
62   PetscCall((*vi->computeuserfunction)(snes, X, F, functx));
63   PetscCall(VecGetLocalSize(X, &nlocal));
64   PetscCall(VecGetArrayRead(X, &x_arr));
65   PetscCall(VecGetArray(F, &f_arr));
66   PetscCall(VecGetArray(Xl, &l));
67   PetscCall(VecGetArray(Xu, &u));
68   PetscCall(VecGetArray(phi, &phi_arr));
69 
70   for (i = 0; i < nlocal; i++) {
71     if ((PetscRealPart(l[i]) <= PETSC_NINFINITY) && (PetscRealPart(u[i]) >= PETSC_INFINITY)) { /* no constraints on variable */
72       phi_arr[i] = f_arr[i];
73     } else if (PetscRealPart(l[i]) <= PETSC_NINFINITY) { /* upper bound on variable only */
74       phi_arr[i] = -Phi(u[i] - x_arr[i], -f_arr[i]);
75     } else if (PetscRealPart(u[i]) >= PETSC_INFINITY) { /* lower bound on variable only */
76       phi_arr[i] = Phi(x_arr[i] - l[i], f_arr[i]);
77     } else if (l[i] == u[i]) {
78       phi_arr[i] = l[i] - x_arr[i];
79     } else { /* both bounds on variable */
80       phi_arr[i] = Phi(x_arr[i] - l[i], -Phi(u[i] - x_arr[i], -f_arr[i]));
81     }
82   }
83 
84   PetscCall(VecRestoreArrayRead(X, &x_arr));
85   PetscCall(VecRestoreArray(F, &f_arr));
86   PetscCall(VecRestoreArray(Xl, &l));
87   PetscCall(VecRestoreArray(Xu, &u));
88   PetscCall(VecRestoreArray(phi, &phi_arr));
89   PetscFunctionReturn(PETSC_SUCCESS);
90 }
91 
92 /*
93    SNESVIComputeBsubdifferentialVectors - Computes the diagonal shift (Da) and row scaling (Db) vectors needed for the
94                                           the semismooth jacobian.
95 */
96 static PetscErrorCode SNESVIComputeBsubdifferentialVectors(SNES snes, Vec X, Vec F, Mat jac, Vec Da, Vec Db)
97 {
98   PetscScalar *l, *u, *x, *f, *da, *db, da1, da2, db1, db2;
99   PetscInt     i, nlocal;
100 
101   PetscFunctionBegin;
102   PetscCall(VecGetArray(X, &x));
103   PetscCall(VecGetArray(F, &f));
104   PetscCall(VecGetArray(snes->xl, &l));
105   PetscCall(VecGetArray(snes->xu, &u));
106   PetscCall(VecGetArray(Da, &da));
107   PetscCall(VecGetArray(Db, &db));
108   PetscCall(VecGetLocalSize(X, &nlocal));
109 
110   for (i = 0; i < nlocal; i++) {
111     if ((PetscRealPart(l[i]) <= PETSC_NINFINITY) && (PetscRealPart(u[i]) >= PETSC_INFINITY)) { /* no constraints on variable */
112       da[i] = 0;
113       db[i] = 1;
114     } else if (PetscRealPart(l[i]) <= PETSC_NINFINITY) { /* upper bound on variable only */
115       da[i] = DPhi(u[i] - x[i], -f[i]);
116       db[i] = DPhi(-f[i], u[i] - x[i]);
117     } else if (PetscRealPart(u[i]) >= PETSC_INFINITY) { /* lower bound on variable only */
118       da[i] = DPhi(x[i] - l[i], f[i]);
119       db[i] = DPhi(f[i], x[i] - l[i]);
120     } else if (l[i] == u[i]) { /* fixed variable */
121       da[i] = 1;
122       db[i] = 0;
123     } else { /* upper and lower bounds on variable */
124       da1   = DPhi(x[i] - l[i], -Phi(u[i] - x[i], -f[i]));
125       db1   = DPhi(-Phi(u[i] - x[i], -f[i]), x[i] - l[i]);
126       da2   = DPhi(u[i] - x[i], -f[i]);
127       db2   = DPhi(-f[i], u[i] - x[i]);
128       da[i] = da1 + db1 * da2;
129       db[i] = db1 * db2;
130     }
131   }
132 
133   PetscCall(VecRestoreArray(X, &x));
134   PetscCall(VecRestoreArray(F, &f));
135   PetscCall(VecRestoreArray(snes->xl, &l));
136   PetscCall(VecRestoreArray(snes->xu, &u));
137   PetscCall(VecRestoreArray(Da, &da));
138   PetscCall(VecRestoreArray(Db, &db));
139   PetscFunctionReturn(PETSC_SUCCESS);
140 }
141 
142 /*
143    SNESVIComputeJacobian - Computes the jacobian of the semismooth function.The Jacobian for the semismooth function is an element of the B-subdifferential of the Fischer-Burmeister function for complementarity problems.
144 
145    Input Parameters:
146 .  Da       - Diagonal shift vector for the semismooth Jacobian.
147 .  Db       - Row scaling vector for the semismooth Jacobian.
148 
149    Output Parameters:
150 .  jac      - semismooth Jacobian
151 .  jac_pre  - optional matrix from which to construct the preconditioner
152 
153    Note:
154    The semismooth jacobian matrix is given by
155    $ jac = Da + Db*jacfun $
156    where `Db` is the row scaling matrix stored as a vector,
157          `Da` is the diagonal perturbation matrix stored as a vector
158    and   `jacfun` is the Jacobian of the original nonlinear function.
159 */
160 static PetscErrorCode SNESVIComputeJacobian(Mat jac, Mat jac_pre, Vec Da, Vec Db)
161 {
162   /* Do row scaling  and add diagonal perturbation */
163   PetscFunctionBegin;
164   PetscCall(MatDiagonalScale(jac, Db, NULL));
165   PetscCall(MatDiagonalSet(jac, Da, ADD_VALUES));
166   if (jac != jac_pre) { /* If jac and jac_pre are different */
167     PetscCall(MatDiagonalScale(jac_pre, Db, NULL));
168     PetscCall(MatDiagonalSet(jac_pre, Da, ADD_VALUES));
169   }
170   PetscFunctionReturn(PETSC_SUCCESS);
171 }
172 
173 // PetscClangLinter pragma disable: -fdoc-sowing-chars
174 /*
175   SNESVIComputeMeritFunctionGradient - Computes the gradient of the merit function psi.
176 
177   Input Parameters:
178    phi - semismooth function.
179    H   - semismooth jacobian
180 
181   Output Parameter:
182    dpsi - merit function gradient
183 
184   Note:
185   The merit function gradient is computed as follows
186   dpsi = H^T*phi
187 */
188 static PetscErrorCode SNESVIComputeMeritFunctionGradient(Mat H, Vec phi, Vec dpsi)
189 {
190   PetscFunctionBegin;
191   PetscCall(MatMultTranspose(H, phi, dpsi));
192   PetscFunctionReturn(PETSC_SUCCESS);
193 }
194 
195 static PetscErrorCode SNESSolve_VINEWTONSSLS(SNES snes)
196 {
197   SNES_VINEWTONSSLS   *vi = (SNES_VINEWTONSSLS *)snes->data;
198   PetscInt             maxits, i, lits;
199   SNESLineSearchReason lssucceed;
200   PetscReal            gnorm, xnorm = 0, ynorm;
201   Vec                  Y, X, F;
202   KSPConvergedReason   kspreason;
203   DM                   dm;
204   DMSNES               sdm;
205 
206   PetscFunctionBegin;
207   PetscCall(SNESGetDM(snes, &dm));
208   PetscCall(DMGetDMSNES(dm, &sdm));
209 
210   vi->computeuserfunction   = sdm->ops->computefunction;
211   sdm->ops->computefunction = SNESVIComputeFunction;
212 
213   snes->numFailures            = 0;
214   snes->numLinearSolveFailures = 0;
215   snes->reason                 = SNES_CONVERGED_ITERATING;
216 
217   maxits = snes->max_its;  /* maximum number of iterations */
218   X      = snes->vec_sol;  /* solution vector */
219   F      = snes->vec_func; /* residual vector */
220   Y      = snes->work[0];  /* work vectors */
221 
222   PetscCall(PetscObjectSAWsTakeAccess((PetscObject)snes));
223   snes->iter = 0;
224   snes->norm = 0.0;
225   PetscCall(PetscObjectSAWsGrantAccess((PetscObject)snes));
226 
227   PetscCall(SNESVIProjectOntoBounds(snes, X));
228   PetscCall(SNESComputeFunction(snes, X, vi->phi));
229   if (snes->domainerror) {
230     snes->reason              = SNES_DIVERGED_FUNCTION_DOMAIN;
231     sdm->ops->computefunction = vi->computeuserfunction;
232     PetscFunctionReturn(PETSC_SUCCESS);
233   }
234   /* Compute Merit function */
235   PetscCall(SNESVIComputeMeritFunction(vi->phi, &vi->merit, &vi->phinorm));
236 
237   PetscCall(VecNormBegin(X, NORM_2, &xnorm)); /* xnorm <- ||x||  */
238   PetscCall(VecNormEnd(X, NORM_2, &xnorm));
239   SNESCheckFunctionNorm(snes, vi->merit);
240 
241   PetscCall(PetscObjectSAWsTakeAccess((PetscObject)snes));
242   snes->norm = vi->phinorm;
243   PetscCall(PetscObjectSAWsGrantAccess((PetscObject)snes));
244   PetscCall(SNESLogConvergenceHistory(snes, vi->phinorm, 0));
245 
246   /* test convergence */
247   PetscCall(SNESConverged(snes, 0, 0.0, 0.0, vi->phinorm));
248   PetscCall(SNESMonitor(snes, 0, vi->phinorm));
249   if (snes->reason) {
250     sdm->ops->computefunction = vi->computeuserfunction;
251     PetscFunctionReturn(PETSC_SUCCESS);
252   }
253 
254   for (i = 0; i < maxits; i++) {
255     /* Call general purpose update function */
256     PetscTryTypeMethod(snes, update, snes->iter);
257 
258     /* Solve J Y = Phi, where J is the semismooth jacobian */
259 
260     /* Get the jacobian -- note that the function must be the original function for snes_fd and snes_fd_color to work for this*/
261     sdm->ops->computefunction = vi->computeuserfunction;
262     PetscCall(SNESComputeJacobian(snes, X, snes->jacobian, snes->jacobian_pre));
263     SNESCheckJacobianDomainerror(snes);
264     sdm->ops->computefunction = SNESVIComputeFunction;
265 
266     /* Get the diagonal shift and row scaling vectors */
267     PetscCall(SNESVIComputeBsubdifferentialVectors(snes, X, F, snes->jacobian, vi->Da, vi->Db));
268     /* Compute the semismooth jacobian */
269     PetscCall(SNESVIComputeJacobian(snes->jacobian, snes->jacobian_pre, vi->Da, vi->Db));
270     /* Compute the merit function gradient */
271     PetscCall(SNESVIComputeMeritFunctionGradient(snes->jacobian, vi->phi, vi->dpsi));
272     PetscCall(KSPSetOperators(snes->ksp, snes->jacobian, snes->jacobian_pre));
273     PetscCall(KSPSolve(snes->ksp, vi->phi, Y));
274     PetscCall(KSPGetConvergedReason(snes->ksp, &kspreason));
275 
276     if (kspreason < 0) {
277       if (++snes->numLinearSolveFailures >= snes->maxLinearSolveFailures) {
278         PetscCall(PetscInfo(snes, "iter=%" PetscInt_FMT ", number linear solve failures %" PetscInt_FMT " greater than current SNES allowed, stopping solve\n", snes->iter, snes->numLinearSolveFailures));
279         snes->reason = SNES_DIVERGED_LINEAR_SOLVE;
280         break;
281       }
282     }
283     PetscCall(KSPGetIterationNumber(snes->ksp, &lits));
284     snes->linear_its += lits;
285     PetscCall(PetscInfo(snes, "iter=%" PetscInt_FMT ", linear solve iterations=%" PetscInt_FMT "\n", snes->iter, lits));
286     /*
287     if (snes->ops->precheck) {
288       PetscBool changed_y = PETSC_FALSE;
289       PetscUseTypeMethod(snes,precheck ,X,Y,snes->precheck,&changed_y);
290     }
291 
292     if (PetscLogPrintInfo) PetscCall(SNESVICheckResidual_Private(snes,snes->jacobian,F,Y,G,W));
293     */
294     /* Compute a (scaled) negative update in the line search routine:
295          Y <- X - lambda*Y
296        and evaluate G = function(Y) (depends on the line search).
297     */
298     PetscCall(VecCopy(Y, snes->vec_sol_update));
299     ynorm = 1;
300     gnorm = vi->phinorm;
301     PetscCall(SNESLineSearchApply(snes->linesearch, X, vi->phi, &gnorm, Y));
302     PetscCall(SNESLineSearchGetReason(snes->linesearch, &lssucceed));
303     PetscCall(SNESLineSearchGetNorms(snes->linesearch, &xnorm, &gnorm, &ynorm));
304     PetscCall(PetscInfo(snes, "fnorm=%18.16e, gnorm=%18.16e, ynorm=%18.16e, lssucceed=%d\n", (double)vi->phinorm, (double)gnorm, (double)ynorm, (int)lssucceed));
305     if (snes->reason == SNES_DIVERGED_FUNCTION_COUNT) break;
306     if (snes->domainerror) {
307       snes->reason              = SNES_DIVERGED_FUNCTION_DOMAIN;
308       sdm->ops->computefunction = vi->computeuserfunction;
309       PetscFunctionReturn(PETSC_SUCCESS);
310     }
311     if (lssucceed) {
312       if (++snes->numFailures >= snes->maxFailures) {
313         PetscBool ismin;
314         snes->reason = SNES_DIVERGED_LINE_SEARCH;
315         PetscCall(SNESVICheckLocalMin_Private(snes, snes->jacobian, vi->phi, X, gnorm, &ismin));
316         if (ismin) snes->reason = SNES_DIVERGED_LOCAL_MIN;
317         break;
318       }
319     }
320     /* Update function and solution vectors */
321     vi->phinorm = gnorm;
322     vi->merit   = 0.5 * vi->phinorm * vi->phinorm;
323     /* Monitor convergence */
324     PetscCall(PetscObjectSAWsTakeAccess((PetscObject)snes));
325     snes->iter  = i + 1;
326     snes->norm  = vi->phinorm;
327     snes->xnorm = xnorm;
328     snes->ynorm = ynorm;
329     PetscCall(PetscObjectSAWsGrantAccess((PetscObject)snes));
330     PetscCall(SNESLogConvergenceHistory(snes, snes->norm, lits));
331     /* Test for convergence, xnorm = || X || */
332     if (snes->ops->converged != SNESConvergedSkip) PetscCall(VecNorm(X, NORM_2, &xnorm));
333     PetscCall(SNESConverged(snes, snes->iter, xnorm, ynorm, vi->phinorm));
334     PetscCall(SNESMonitor(snes, snes->iter, snes->norm));
335     if (snes->reason) break;
336   }
337   sdm->ops->computefunction = vi->computeuserfunction;
338   PetscFunctionReturn(PETSC_SUCCESS);
339 }
340 
341 static PetscErrorCode SNESSetUp_VINEWTONSSLS(SNES snes)
342 {
343   SNES_VINEWTONSSLS *vi = (SNES_VINEWTONSSLS *)snes->data;
344 
345   PetscFunctionBegin;
346   PetscCall(SNESSetUp_VI(snes));
347   PetscCall(VecDuplicate(snes->vec_sol, &vi->dpsi));
348   PetscCall(VecDuplicate(snes->vec_sol, &vi->phi));
349   PetscCall(VecDuplicate(snes->vec_sol, &vi->Da));
350   PetscCall(VecDuplicate(snes->vec_sol, &vi->Db));
351   PetscCall(VecDuplicate(snes->vec_sol, &vi->z));
352   PetscCall(VecDuplicate(snes->vec_sol, &vi->t));
353   PetscFunctionReturn(PETSC_SUCCESS);
354 }
355 
356 static PetscErrorCode SNESReset_VINEWTONSSLS(SNES snes)
357 {
358   SNES_VINEWTONSSLS *vi = (SNES_VINEWTONSSLS *)snes->data;
359 
360   PetscFunctionBegin;
361   PetscCall(SNESReset_VI(snes));
362   PetscCall(VecDestroy(&vi->dpsi));
363   PetscCall(VecDestroy(&vi->phi));
364   PetscCall(VecDestroy(&vi->Da));
365   PetscCall(VecDestroy(&vi->Db));
366   PetscCall(VecDestroy(&vi->z));
367   PetscCall(VecDestroy(&vi->t));
368   PetscFunctionReturn(PETSC_SUCCESS);
369 }
370 
371 static PetscErrorCode SNESSetFromOptions_VINEWTONSSLS(SNES snes, PetscOptionItems PetscOptionsObject)
372 {
373   PetscFunctionBegin;
374   PetscCall(SNESSetFromOptions_VI(snes, PetscOptionsObject));
375   PetscOptionsHeadBegin(PetscOptionsObject, "SNES semismooth method options");
376   PetscOptionsHeadEnd();
377   PetscFunctionReturn(PETSC_SUCCESS);
378 }
379 
380 /*MC
381       SNESVINEWTONSSLS - Semi-smooth solver for variational inequalities based on Newton's method
382 
383    Options Database Keys:
384 +   -snes_type <vinewtonssls,vinewtonrsls> a semi-smooth solver, a reduced space active set method
385 -   -snes_vi_monitor - prints the number of active constraints at each iteration.
386 
387    Level: beginner
388 
389    Notes:
390    This family of algorithms is much like an interior point method.
391 
392    The reduced space active set solvers `SNESVINEWTONRSLS` provide an alternative approach that does not result in extremely ill-conditioned linear systems
393 
394    See {cite}`munson.facchinei.ea:semismooth` and {cite}`benson2006flexible`
395 
396 .seealso: [](ch_snes), `SNESVINEWTONRSLS`, `SNESVISetVariableBounds()`, `SNESVISetComputeVariableBounds()`, `SNESCreate()`, `SNES`, `SNESSetType()`, `SNESVINEWTONRSLS`, `SNESNEWTONTR`, `SNESLineSearchSetType()`, `SNESLineSearchSetPostCheck()`, `SNESLineSearchSetPreCheck()`
397 M*/
398 PETSC_EXTERN PetscErrorCode SNESCreate_VINEWTONSSLS(SNES snes)
399 {
400   SNES_VINEWTONSSLS *vi;
401   SNESLineSearch     linesearch;
402 
403   PetscFunctionBegin;
404   snes->ops->reset          = SNESReset_VINEWTONSSLS;
405   snes->ops->setup          = SNESSetUp_VINEWTONSSLS;
406   snes->ops->solve          = SNESSolve_VINEWTONSSLS;
407   snes->ops->destroy        = SNESDestroy_VI;
408   snes->ops->setfromoptions = SNESSetFromOptions_VINEWTONSSLS;
409   snes->ops->view           = NULL;
410 
411   snes->usesksp = PETSC_TRUE;
412   snes->usesnpc = PETSC_FALSE;
413 
414   PetscCall(SNESGetLineSearch(snes, &linesearch));
415   if (!((PetscObject)linesearch)->type_name) {
416     PetscCall(SNESLineSearchSetType(linesearch, SNESLINESEARCHBT));
417     PetscCall(SNESLineSearchBTSetAlpha(linesearch, 0.0));
418   }
419 
420   snes->alwayscomputesfinalresidual = PETSC_FALSE;
421 
422   PetscCall(SNESParametersInitialize(snes));
423 
424   PetscCall(PetscNew(&vi));
425   snes->data = (void *)vi;
426 
427   PetscCall(PetscObjectComposeFunction((PetscObject)snes, "SNESVISetVariableBounds_C", SNESVISetVariableBounds_VI));
428   PetscCall(PetscObjectComposeFunction((PetscObject)snes, "SNESVISetComputeVariableBounds_C", SNESVISetComputeVariableBounds_VI));
429   PetscFunctionReturn(PETSC_SUCCESS);
430 }
431