1 /* -----------------------------------------------------------------------------
2  * Programmer(s): David J. Gardner @ LLNL
3  * -----------------------------------------------------------------------------
4  * SUNDIALS Copyright Start
5  * Copyright (c) 2002-2020, Lawrence Livermore National Security
6  * and Southern Methodist University.
7  * All rights reserved.
8  *
9  * See the top-level LICENSE and NOTICE files for details.
10  *
11  * SPDX-License-Identifier: BSD-3-Clause
12  * SUNDIALS Copyright End
13  * -----------------------------------------------------------------------------
14  * This the implementation file for the CVODES nonlinear solver interface.
15  * ---------------------------------------------------------------------------*/
16 
17 /*
18  * When sensitivities are computed using the CV_SIMULTANEOUS approach and the
19  * Newton solver is selected the iteraiton is a  quasi-Newton method on the
20  * combined system (by approximating the Jacobian matrix by its block diagonal)
21  * and thus only solve linear systems with multiple right hand sides (all
22  * sharing the same coefficient matrix - whatever iteration matrix we decide on)
23  * we set-up the linear solver to handle N equations at a time.
24  */
25 
26 #include "cvodes_impl.h"
27 #include "sundials/sundials_math.h"
28 #include "sundials/sundials_nvector_senswrapper.h"
29 
30 /* constant macros */
31 #define ONE RCONST(1.0)
32 
33 /* private functions */
34 static int cvNlsResidualSensSim(N_Vector ycorSim, N_Vector resSim,
35                                 void* cvode_mem);
36 static int cvNlsFPFunctionSensSim(N_Vector ycorSim, N_Vector resSim,
37                                   void* cvode_mem);
38 
39 static int cvNlsLSetupSensSim(booleantype jbad, booleantype* jcur,
40                               void* cvode_mem);
41 static int cvNlsLSolveSensSim(N_Vector deltaSim, void* cvode_mem);
42 static int cvNlsConvTestSensSim(SUNNonlinearSolver NLS,
43                                 N_Vector ycorSim, N_Vector delSim,
44                                 realtype tol, N_Vector ewtSim, void* cvode_mem);
45 
46 /* -----------------------------------------------------------------------------
47  * Exported functions
48  * ---------------------------------------------------------------------------*/
49 
CVodeSetNonlinearSolverSensSim(void * cvode_mem,SUNNonlinearSolver NLS)50 int CVodeSetNonlinearSolverSensSim(void *cvode_mem, SUNNonlinearSolver NLS)
51 {
52   CVodeMem cv_mem;
53   int retval, is;
54 
55   /* Return immediately if CVode memory is NULL */
56   if (cvode_mem == NULL) {
57     cvProcessError(NULL, CV_MEM_NULL, "CVODES",
58                    "CVodeSetNonlinearSolverSensSim", MSGCV_NO_MEM);
59     return(CV_MEM_NULL);
60   }
61   cv_mem = (CVodeMem) cvode_mem;
62 
63   /* Return immediately if NLS memory is NULL */
64   if (NLS == NULL) {
65     cvProcessError(NULL, CV_ILL_INPUT, "CVODES",
66                    "CVodeSetNonlinearSolverSensSim",
67                    "NLS must be non-NULL");
68     return (CV_ILL_INPUT);
69   }
70 
71   /* check for required nonlinear solver functions */
72   if ( NLS->ops->gettype    == NULL ||
73        NLS->ops->solve      == NULL ||
74        NLS->ops->setsysfn   == NULL ) {
75     cvProcessError(cv_mem, CV_ILL_INPUT, "CVODES",
76                    "CVodeSetNonlinearSolverSensSim",
77                    "NLS does not support required operations");
78     return(CV_ILL_INPUT);
79   }
80 
81   /* check that sensitivities were initialized */
82   if (!(cv_mem->cv_sensi)) {
83     cvProcessError(cv_mem, CV_ILL_INPUT, "CVODES",
84                    "CVodeSetNonlinearSolverSensSim",
85                    MSGCV_NO_SENSI);
86     return(CV_ILL_INPUT);
87   }
88 
89   /* check that simultaneous corrector was selected */
90   if (cv_mem->cv_ism != CV_SIMULTANEOUS) {
91     cvProcessError(cv_mem, CV_ILL_INPUT, "CVODES",
92                    "CVodeSetNonlinearSolverSensStg",
93                    "Sensitivity solution method is not CV_SIMULTANEOUS");
94     return(CV_ILL_INPUT);
95   }
96 
97   /* free any existing nonlinear solver */
98   if ((cv_mem->NLSsim != NULL) && (cv_mem->ownNLSsim))
99     retval = SUNNonlinSolFree(cv_mem->NLSsim);
100 
101   /* set SUNNonlinearSolver pointer */
102   cv_mem->NLSsim = NLS;
103 
104   /* Set NLS ownership flag. If this function was called to attach the default
105      NLS, CVODE will set the flag to SUNTRUE after this function returns. */
106   cv_mem->ownNLSsim = SUNFALSE;
107 
108   /* set the nonlinear system function */
109   if (SUNNonlinSolGetType(NLS) == SUNNONLINEARSOLVER_ROOTFIND) {
110     retval = SUNNonlinSolSetSysFn(cv_mem->NLSsim, cvNlsResidualSensSim);
111   } else if (SUNNonlinSolGetType(NLS) ==  SUNNONLINEARSOLVER_FIXEDPOINT) {
112     retval = SUNNonlinSolSetSysFn(cv_mem->NLSsim, cvNlsFPFunctionSensSim);
113   } else {
114     cvProcessError(cv_mem, CV_ILL_INPUT, "CVODES",
115                    "CVodeSetNonlinearSolverSensSim",
116                    "Invalid nonlinear solver type");
117     return(CV_ILL_INPUT);
118   }
119 
120   if (retval != CV_SUCCESS) {
121     cvProcessError(cv_mem, CV_ILL_INPUT, "CVODES",
122                    "CVodeSetNonlinearSolverSensSim",
123                    "Setting nonlinear system function failed");
124     return(CV_ILL_INPUT);
125   }
126 
127   /* set convergence test function */
128   retval = SUNNonlinSolSetConvTestFn(cv_mem->NLSsim, cvNlsConvTestSensSim,
129                                      cvode_mem);
130   if (retval != CV_SUCCESS) {
131     cvProcessError(cv_mem, CV_ILL_INPUT, "CVODES",
132                    "CVodeSetNonlinearSolverSensSim",
133                    "Setting convergence test function failed");
134     return(CV_ILL_INPUT);
135   }
136 
137   /* set max allowed nonlinear iterations */
138   retval = SUNNonlinSolSetMaxIters(cv_mem->NLSsim, NLS_MAXCOR);
139   if (retval != CV_SUCCESS) {
140     cvProcessError(cv_mem, CV_ILL_INPUT, "CVODES",
141                    "CVodeSetNonlinearSolverSensSim",
142                    "Setting maximum number of nonlinear iterations failed");
143     return(CV_ILL_INPUT);
144   }
145 
146   /* create vector wrappers if necessary */
147   if (cv_mem->simMallocDone == SUNFALSE) {
148 
149     cv_mem->zn0Sim = N_VNewEmpty_SensWrapper(cv_mem->cv_Ns+1);
150     if (cv_mem->zn0Sim == NULL) {
151       cvProcessError(cv_mem, CV_MEM_FAIL, "CVODES",
152                      "CVodeSetNonlinearSolverSensSim", MSGCV_MEM_FAIL);
153       return(CV_MEM_FAIL);
154     }
155 
156     cv_mem->ycorSim = N_VNewEmpty_SensWrapper(cv_mem->cv_Ns+1);
157     if (cv_mem->ycorSim == NULL) {
158       N_VDestroy(cv_mem->zn0Sim);
159       cvProcessError(cv_mem, CV_MEM_FAIL, "CVODES",
160                      "CVodeSetNonlinearSolverSensSim", MSGCV_MEM_FAIL);
161       return(CV_MEM_FAIL);
162     }
163 
164     cv_mem->ewtSim = N_VNewEmpty_SensWrapper(cv_mem->cv_Ns+1);
165     if (cv_mem->ewtSim == NULL) {
166       N_VDestroy(cv_mem->zn0Sim);
167       N_VDestroy(cv_mem->ycorSim);
168       cvProcessError(cv_mem, CV_MEM_FAIL, "CVODES",
169                      "CVodeSetNonlinearSolverSensSim", MSGCV_MEM_FAIL);
170       return(CV_MEM_FAIL);
171     }
172 
173     cv_mem->simMallocDone = SUNTRUE;
174   }
175 
176   /* attach vectors to vector wrappers */
177   NV_VEC_SW(cv_mem->zn0Sim,  0) = cv_mem->cv_zn[0];
178   NV_VEC_SW(cv_mem->ycorSim, 0) = cv_mem->cv_acor;
179   NV_VEC_SW(cv_mem->ewtSim,  0) = cv_mem->cv_ewt;
180 
181   for (is=0; is < cv_mem->cv_Ns; is++) {
182     NV_VEC_SW(cv_mem->zn0Sim,  is+1) = cv_mem->cv_znS[0][is];
183     NV_VEC_SW(cv_mem->ycorSim, is+1) = cv_mem->cv_acorS[is];
184     NV_VEC_SW(cv_mem->ewtSim,  is+1) = cv_mem->cv_ewtS[is];
185   }
186 
187   /* Reset the acnrmcur flag to SUNFALSE */
188   cv_mem->cv_acnrmcur = SUNFALSE;
189 
190   return(CV_SUCCESS);
191 }
192 
193 
194 /* -----------------------------------------------------------------------------
195  * Private functions
196  * ---------------------------------------------------------------------------*/
197 
198 
cvNlsInitSensSim(CVodeMem cvode_mem)199 int cvNlsInitSensSim(CVodeMem cvode_mem)
200 {
201   int retval;
202 
203   /* set the linear solver setup wrapper function */
204   if (cvode_mem->cv_lsetup)
205     retval = SUNNonlinSolSetLSetupFn(cvode_mem->NLSsim, cvNlsLSetupSensSim);
206   else
207     retval = SUNNonlinSolSetLSetupFn(cvode_mem->NLSsim, NULL);
208 
209   if (retval != CV_SUCCESS) {
210     cvProcessError(cvode_mem, CV_ILL_INPUT, "CVODES", "cvNlsInitSensSim",
211                    "Setting the linear solver setup function failed");
212     return(CV_NLS_INIT_FAIL);
213   }
214 
215   /* set the linear solver solve wrapper function */
216   if (cvode_mem->cv_lsolve)
217     retval = SUNNonlinSolSetLSolveFn(cvode_mem->NLSsim, cvNlsLSolveSensSim);
218   else
219     retval = SUNNonlinSolSetLSolveFn(cvode_mem->NLSsim, NULL);
220 
221   if (retval != CV_SUCCESS) {
222     cvProcessError(cvode_mem, CV_ILL_INPUT, "CVODES", "cvNlsInitSensSim",
223                    "Setting linear solver solve function failed");
224     return(CV_NLS_INIT_FAIL);
225   }
226 
227   /* initialize nonlinear solver */
228   retval = SUNNonlinSolInitialize(cvode_mem->NLSsim);
229 
230   if (retval != CV_SUCCESS) {
231     cvProcessError(cvode_mem, CV_ILL_INPUT, "CVODES", "cvNlsInitSensSim",
232                    MSGCV_NLS_INIT_FAIL);
233     return(CV_NLS_INIT_FAIL);
234   }
235 
236   return(CV_SUCCESS);
237 }
238 
239 
cvNlsLSetupSensSim(booleantype jbad,booleantype * jcur,void * cvode_mem)240 static int cvNlsLSetupSensSim(booleantype jbad, booleantype* jcur,
241                               void* cvode_mem)
242 {
243   CVodeMem cv_mem;
244   int retval;
245 
246   if (cvode_mem == NULL) {
247     cvProcessError(NULL, CV_MEM_NULL, "CVODES",
248                    "cvNlsLSetupSensSim", MSGCV_NO_MEM);
249     return(CV_MEM_NULL);
250   }
251   cv_mem = (CVodeMem) cvode_mem;
252 
253   /* if the nonlinear solver marked the Jacobian as bad update convfail */
254   if (jbad)
255     cv_mem->convfail = CV_FAIL_BAD_J;
256 
257   /* setup the linear solver */
258   retval = cv_mem->cv_lsetup(cv_mem, cv_mem->convfail, cv_mem->cv_y,
259                              cv_mem->cv_ftemp, &(cv_mem->cv_jcur),
260                              cv_mem->cv_vtemp1, cv_mem->cv_vtemp2,
261                              cv_mem->cv_vtemp3);
262   cv_mem->cv_nsetups++;
263 
264   /* update Jacobian status */
265   *jcur = cv_mem->cv_jcur;
266 
267   cv_mem->cv_forceSetup = SUNFALSE;
268   cv_mem->cv_gamrat     = ONE;
269   cv_mem->cv_gammap     = cv_mem->cv_gamma;
270   cv_mem->cv_crate      = ONE;
271   cv_mem->cv_crateS     = ONE;
272   cv_mem->cv_nstlp      = cv_mem->cv_nst;
273 
274   if (retval < 0) return(CV_LSETUP_FAIL);
275   if (retval > 0) return(SUN_NLS_CONV_RECVR);
276 
277   return(CV_SUCCESS);
278 }
279 
280 
cvNlsLSolveSensSim(N_Vector deltaSim,void * cvode_mem)281 static int cvNlsLSolveSensSim(N_Vector deltaSim, void* cvode_mem)
282 {
283   CVodeMem cv_mem;
284   int retval, is;
285   N_Vector delta;
286   N_Vector *deltaS;
287 
288   if (cvode_mem == NULL) {
289     cvProcessError(NULL, CV_MEM_NULL, "CVODES",
290                    "cvNlsLSolveSensSim", MSGCV_NO_MEM);
291     return(CV_MEM_NULL);
292   }
293   cv_mem = (CVodeMem) cvode_mem;
294 
295   /* extract state delta from the vector wrapper */
296   delta = NV_VEC_SW(deltaSim,0);
297 
298   /* solve the state linear system */
299   retval = cv_mem->cv_lsolve(cv_mem, delta, cv_mem->cv_ewt, cv_mem->cv_y,
300                              cv_mem->cv_ftemp);
301 
302   if (retval < 0) return(CV_LSOLVE_FAIL);
303   if (retval > 0) return(SUN_NLS_CONV_RECVR);
304 
305   /* extract sensitivity deltas from the vector wrapper */
306   deltaS = NV_VECS_SW(deltaSim)+1;
307 
308   /* solve the sensitivity linear systems */
309   for (is=0; is<cv_mem->cv_Ns; is++) {
310     retval = cv_mem->cv_lsolve(cv_mem, deltaS[is], cv_mem->cv_ewtS[is],
311                                cv_mem->cv_y, cv_mem->cv_ftemp);
312 
313     if (retval < 0) return(CV_LSOLVE_FAIL);
314     if (retval > 0) return(SUN_NLS_CONV_RECVR);
315   }
316 
317   return(CV_SUCCESS);
318 }
319 
320 
cvNlsConvTestSensSim(SUNNonlinearSolver NLS,N_Vector ycorSim,N_Vector deltaSim,realtype tol,N_Vector ewtSim,void * cvode_mem)321 static int cvNlsConvTestSensSim(SUNNonlinearSolver NLS,
322                                 N_Vector ycorSim, N_Vector deltaSim,
323                                 realtype tol, N_Vector ewtSim, void* cvode_mem)
324 {
325   CVodeMem cv_mem;
326   int m, retval;
327   realtype del, delS, Del;
328   realtype dcon;
329   N_Vector ycor, delta, ewt;
330   N_Vector *deltaS, *ewtS;
331 
332   if (cvode_mem == NULL) {
333     cvProcessError(NULL, CV_MEM_NULL, "CVODES",
334                    "cvNlsConvTestSensSim", MSGCV_NO_MEM);
335     return(CV_MEM_NULL);
336   }
337   cv_mem = (CVodeMem) cvode_mem;
338 
339   /* extract the current state and sensitivity corrections */
340   ycor  = NV_VEC_SW(ycorSim,0);
341 
342   /* extract state and sensitivity deltas */
343   delta  = NV_VEC_SW(deltaSim,0);
344   deltaS = NV_VECS_SW(deltaSim)+1;
345 
346   /* extract state and sensitivity error weights */
347   ewt  = NV_VEC_SW(ewtSim,0);
348   ewtS = NV_VECS_SW(ewtSim)+1;
349 
350   /* compute the norm of the state and sensitivity corrections */
351   del  = N_VWrmsNorm(delta, ewt);
352   delS = cvSensUpdateNorm(cv_mem, del, deltaS, ewtS);
353 
354   /* norm used in error test */
355   Del = delS;
356 
357   /* get the current nonlinear solver iteration count */
358   retval = SUNNonlinSolGetCurIter(NLS, &m);
359   if (retval != CV_SUCCESS) return(CV_MEM_NULL);
360 
361   /* Test for convergence. If m > 0, an estimate of the convergence
362      rate constant is stored in crate, and used in the test.
363 
364      Recall that, even when errconS=SUNFALSE, all variables are used in the
365      convergence test. Hence, we use Del (and not del). However, acnrm is used
366      in the error test and thus it has different forms depending on errconS
367      (and this explains why we have to carry around del and delS).
368   */
369   if (m > 0) {
370     cv_mem->cv_crate = SUNMAX(CRDOWN * cv_mem->cv_crate, Del/cv_mem->cv_delp);
371   }
372   dcon = Del * SUNMIN(ONE, cv_mem->cv_crate) / tol;
373 
374   /* check if nonlinear system was solved successfully */
375   if (dcon <= ONE) {
376     if (m == 0) {
377       cv_mem->cv_acnrm = (cv_mem->cv_errconS) ? delS : del;
378     } else {
379       cv_mem->cv_acnrm = (cv_mem->cv_errconS) ?
380         N_VWrmsNorm(ycorSim, ewtSim) : N_VWrmsNorm(ycor, ewt);
381     }
382     cv_mem->cv_acnrmcur = SUNTRUE;
383     return(CV_SUCCESS);
384   }
385 
386   /* check if the iteration seems to be diverging */
387   if ((m >= 1) && (Del > RDIV*cv_mem->cv_delp)) return(SUN_NLS_CONV_RECVR);
388 
389   /* Save norm of correction and loop again */
390   cv_mem->cv_delp = Del;
391 
392   /* Not yet converged */
393   return(SUN_NLS_CONTINUE);
394 }
395 
396 
cvNlsResidualSensSim(N_Vector ycorSim,N_Vector resSim,void * cvode_mem)397 static int cvNlsResidualSensSim(N_Vector ycorSim, N_Vector resSim, void* cvode_mem)
398 {
399   CVodeMem cv_mem;
400   int retval;
401   N_Vector ycor, res;
402   N_Vector *ycorS, *resS;
403   realtype cvals[3];
404   N_Vector* XXvecs[3];
405 
406   if (cvode_mem == NULL) {
407     cvProcessError(NULL, CV_MEM_NULL, "CVODES",
408                    "cvNlsResidualSensSim", MSGCV_NO_MEM);
409     return(CV_MEM_NULL);
410   }
411   cv_mem = (CVodeMem) cvode_mem;
412 
413   /* extract state and residual vectors from the vector wrapper */
414   ycor = NV_VEC_SW(ycorSim,0);
415   res  = NV_VEC_SW(resSim,0);
416 
417   /* update the state based on the current correction */
418   N_VLinearSum(ONE, cv_mem->cv_zn[0], ONE, ycor, cv_mem->cv_y);
419 
420   /* evaluate the rhs function */
421   retval = cv_mem->cv_f(cv_mem->cv_tn, cv_mem->cv_y, cv_mem->cv_ftemp,
422                         cv_mem->cv_user_data);
423   cv_mem->cv_nfe++;
424   if (retval < 0) return(CV_RHSFUNC_FAIL);
425   if (retval > 0) return(RHSFUNC_RECVR);
426 
427   /* compute the resiudal */
428   N_VLinearSum(cv_mem->cv_rl1, cv_mem->cv_zn[1], ONE, ycor, res);
429   N_VLinearSum(-cv_mem->cv_gamma, cv_mem->cv_ftemp, ONE, res, res);
430 
431   /* extract sensitivity and residual vectors from the vector wrapper */
432   ycorS = NV_VECS_SW(ycorSim)+1;
433   resS  = NV_VECS_SW(resSim)+1;
434 
435   /* update sensitivities based on the current correction */
436   retval = N_VLinearSumVectorArray(cv_mem->cv_Ns,
437                                    ONE, cv_mem->cv_znS[0],
438                                    ONE, ycorS, cv_mem->cv_yS);
439   if (retval != CV_SUCCESS) return(CV_VECTOROP_ERR);
440 
441   /* evaluate the sensitivity rhs function */
442   retval = cvSensRhsWrapper(cv_mem, cv_mem->cv_tn,
443                             cv_mem->cv_y, cv_mem->cv_ftemp,
444                             cv_mem->cv_yS, cv_mem->cv_ftempS,
445                             cv_mem->cv_vtemp1, cv_mem->cv_vtemp2);
446 
447   if (retval < 0) return(CV_SRHSFUNC_FAIL);
448   if (retval > 0) return(SRHSFUNC_RECVR);
449 
450   /* compute the sensitivity resiudal */
451   cvals[0] = cv_mem->cv_rl1;    XXvecs[0] = cv_mem->cv_znS[1];
452   cvals[1] = ONE;               XXvecs[1] = ycorS;
453   cvals[2] = -cv_mem->cv_gamma; XXvecs[2] = cv_mem->cv_ftempS;
454 
455   retval = N_VLinearCombinationVectorArray(cv_mem->cv_Ns,
456                                            3, cvals, XXvecs, resS);
457   if (retval != CV_SUCCESS) return(CV_VECTOROP_ERR);
458 
459   return(CV_SUCCESS);
460 }
461 
462 
cvNlsFPFunctionSensSim(N_Vector ycorSim,N_Vector resSim,void * cvode_mem)463 static int cvNlsFPFunctionSensSim(N_Vector ycorSim, N_Vector resSim, void* cvode_mem)
464 {
465  CVodeMem cv_mem;
466  int retval, is;
467  N_Vector ycor, res;
468  N_Vector *ycorS, *resS;
469 
470   if (cvode_mem == NULL) {
471     cvProcessError(NULL, CV_MEM_NULL, "CVODES",
472                    "cvNlsFPFunctionSensSim", MSGCV_NO_MEM);
473     return(CV_MEM_NULL);
474   }
475   cv_mem = (CVodeMem) cvode_mem;
476 
477   /* extract state and residual vectors from the vector wrapper */
478   ycor = NV_VEC_SW(ycorSim,0);
479   res  = NV_VEC_SW(resSim,0);
480 
481   /* update the state based on the current correction */
482   N_VLinearSum(ONE, cv_mem->cv_zn[0], ONE, ycor, cv_mem->cv_y);
483 
484   /* evaluate the rhs function */
485   retval = cv_mem->cv_f(cv_mem->cv_tn, cv_mem->cv_y, res,
486                         cv_mem->cv_user_data);
487   cv_mem->cv_nfe++;
488   if (retval < 0) return(CV_RHSFUNC_FAIL);
489   if (retval > 0) return(RHSFUNC_RECVR);
490 
491   /* evaluate fixed point function */
492   N_VLinearSum(cv_mem->cv_h, res, -ONE, cv_mem->cv_zn[1], res);
493   N_VScale(cv_mem->cv_rl1, res, res);
494 
495   /* extract sensitivity and residual vectors from the vector wrapper */
496   ycorS = NV_VECS_SW(ycorSim)+1;
497   resS  = NV_VECS_SW(resSim)+1;
498 
499   /* update the sensitivities based on the current correction */
500   N_VLinearSumVectorArray(cv_mem->cv_Ns,
501                           ONE, cv_mem->cv_znS[0],
502                           ONE, ycorS, cv_mem->cv_yS);
503 
504   /* evaluate the sensitivity rhs function */
505   retval = cvSensRhsWrapper(cv_mem, cv_mem->cv_tn,
506                             cv_mem->cv_y, res,
507                             cv_mem->cv_yS, resS,
508                             cv_mem->cv_vtemp1, cv_mem->cv_vtemp2);
509 
510   if (retval < 0) return(CV_SRHSFUNC_FAIL);
511   if (retval > 0) return(SRHSFUNC_RECVR);
512 
513   /* evaluate sensitivity fixed point function */
514   for (is=0; is<cv_mem->cv_Ns; is++) {
515     N_VLinearSum(cv_mem->cv_h, resS[is], -ONE, cv_mem->cv_znS[1][is], resS[is]);
516     N_VScale(cv_mem->cv_rl1, resS[is], resS[is]);
517   }
518 
519   return(CV_SUCCESS);
520 }
521