Actual source code: ex4.c
1: static char help[] = "Solves a simple time-dependent linear PDE (the heat equation).\n\
2: Input parameters include:\n\
3: -m <points>, where <points> = number of grid points\n\
4: -time_dependent_rhs : Treat the problem as having a time-dependent right-hand side\n\
5: -debug : Activate debugging printouts\n\
6: -nox : Deactivate x-window graphics\n\n";
8: /* ------------------------------------------------------------------------
10: This program solves the one-dimensional heat equation (also called the
11: diffusion equation),
12: u_t = u_xx,
13: on the domain 0 <= x <= 1, with the boundary conditions
14: u(t,0) = 0, u(t,1) = 0,
15: and the initial condition
16: u(0,x) = sin(6*pi*x) + 3*sin(2*pi*x).
17: This is a linear, second-order, parabolic equation.
19: We discretize the right-hand side using finite differences with
20: uniform grid spacing h:
21: u_xx = (u_{i+1} - 2u_{i} + u_{i-1})/(h^2)
22: We then demonstrate time evolution using the various TS methods by
23: running the program via
24: mpiexec -n <procs> ./ex4 -ts_type <timestepping solver>
26: We compare the approximate solution with the exact solution, given by
27: u_exact(x,t) = exp(-36*pi*pi*t) * sin(6*pi*x) +
28: 3*exp(-4*pi*pi*t) * sin(2*pi*x)
30: Notes:
31: This code demonstrates the TS solver interface to two variants of
32: linear problems, u_t = f(u,t), namely
33: - time-dependent f: f(u,t) is a function of t
34: - time-independent f: f(u,t) is simply f(u)
36: The uniprocessor version of this code is ts/tutorials/ex3.c
38: ------------------------------------------------------------------------- */
40: /*
41: Include "petscdmda.h" so that we can use distributed arrays (DMDAs) to manage
42: the parallel grid. Include "petscts.h" so that we can use the PETSc TS ODE integrators.
43: Note that this file automatically includes:
44: petscsys.h - base PETSc routines
45: petscis.h - index sets
46: petscvec.h - vectors
47: petscmat.h - matrices
48: petscpc.h - preconditioners
49: petscksp.h - linear solvers
50: petscsnes.h - nonlinear solvers
51: */
53: #include <petscdm.h>
54: #include <petscdmda.h>
55: #include <petscts.h>
56: #include <petscdraw.h>
58: /*
59: User-defined application context - contains data needed by the
60: application-provided call-back routines.
61: */
62: typedef struct {
63: MPI_Comm comm; /* communicator */
64: DM da; /* distributed array data structure */
65: Vec localwork; /* local ghosted work vector */
66: Vec u_local; /* local ghosted approximate solution vector */
67: Vec solution; /* global exact solution vector */
68: PetscInt m; /* total number of grid points */
69: PetscReal h; /* mesh width h = 1/(m-1) */
70: PetscBool debug; /* flag (1 indicates activation of debugging printouts) */
71: PetscViewer viewer1, viewer2; /* viewers for the solution and error */
72: PetscReal norm_2, norm_max; /* error norms */
73: } AppCtx;
75: /*
76: User-defined routines
77: */
78: extern PetscErrorCode InitialConditions(Vec, AppCtx *);
79: extern PetscErrorCode RHSMatrixHeat(TS, PetscReal, Vec, Mat, Mat, void *);
80: extern PetscErrorCode RHSFunctionHeat(TS, PetscReal, Vec, Vec, void *);
81: extern PetscErrorCode Monitor(TS, PetscInt, PetscReal, Vec, void *);
82: extern PetscErrorCode ExactSolution(PetscReal, Vec, AppCtx *);
84: int main(int argc, char **argv)
85: {
86: AppCtx appctx; /* user-defined application context */
87: TS ts; /* timestepping context */
88: Mat A; /* matrix data structure */
89: Vec u; /* approximate solution vector */
90: PetscReal time_total_max = 1.0; /* default max total time */
91: PetscInt time_steps_max = 100; /* default max timesteps */
92: PetscDraw draw; /* drawing context */
93: PetscInt steps, m;
94: PetscMPIInt size;
95: PetscReal dt, ftime;
96: PetscBool flg;
97: TSProblemType tsproblem = TS_LINEAR;
99: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
100: Initialize program and set problem parameters
101: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
103: PetscFunctionBeginUser;
104: PetscCall(PetscInitialize(&argc, &argv, NULL, help));
105: appctx.comm = PETSC_COMM_WORLD;
107: m = 60;
108: PetscCall(PetscOptionsGetInt(NULL, NULL, "-m", &m, NULL));
109: PetscCall(PetscOptionsHasName(NULL, NULL, "-debug", &appctx.debug));
110: appctx.m = m;
111: appctx.h = 1.0 / (m - 1.0);
112: appctx.norm_2 = 0.0;
113: appctx.norm_max = 0.0;
115: PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
116: PetscCall(PetscPrintf(PETSC_COMM_WORLD, "Solving a linear TS problem, number of processors = %d\n", size));
118: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
119: Create vector data structures
120: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
121: /*
122: Create distributed array (DMDA) to manage parallel grid and vectors
123: and to set up the ghost point communication pattern. There are M
124: total grid values spread equally among all the processors.
125: */
127: PetscCall(DMDACreate1d(PETSC_COMM_WORLD, DM_BOUNDARY_NONE, m, 1, 1, NULL, &appctx.da));
128: PetscCall(DMSetFromOptions(appctx.da));
129: PetscCall(DMSetUp(appctx.da));
131: /*
132: Extract global and local vectors from DMDA; we use these to store the
133: approximate solution. Then duplicate these for remaining vectors that
134: have the same types.
135: */
136: PetscCall(DMCreateGlobalVector(appctx.da, &u));
137: PetscCall(DMCreateLocalVector(appctx.da, &appctx.u_local));
139: /*
140: Create local work vector for use in evaluating right-hand-side function;
141: create global work vector for storing exact solution.
142: */
143: PetscCall(VecDuplicate(appctx.u_local, &appctx.localwork));
144: PetscCall(VecDuplicate(u, &appctx.solution));
146: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
147: Set up displays to show graphs of the solution and error
148: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
150: PetscCall(PetscViewerDrawOpen(PETSC_COMM_WORLD, NULL, "", 80, 380, 400, 160, &appctx.viewer1));
151: PetscCall(PetscViewerDrawGetDraw(appctx.viewer1, 0, &draw));
152: PetscCall(PetscDrawSetDoubleBuffer(draw));
153: PetscCall(PetscViewerDrawOpen(PETSC_COMM_WORLD, NULL, "", 80, 0, 400, 160, &appctx.viewer2));
154: PetscCall(PetscViewerDrawGetDraw(appctx.viewer2, 0, &draw));
155: PetscCall(PetscDrawSetDoubleBuffer(draw));
157: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
158: Create timestepping solver context
159: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
161: PetscCall(TSCreate(PETSC_COMM_WORLD, &ts));
163: flg = PETSC_FALSE;
164: PetscCall(PetscOptionsGetBool(NULL, NULL, "-nonlinear", &flg, NULL));
165: PetscCall(TSSetProblemType(ts, flg ? TS_NONLINEAR : TS_LINEAR));
167: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
168: Set optional user-defined monitoring routine
169: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
170: PetscCall(TSMonitorSet(ts, Monitor, &appctx, NULL));
172: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
174: Create matrix data structure; set matrix evaluation routine.
175: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
177: PetscCall(MatCreate(PETSC_COMM_WORLD, &A));
178: PetscCall(MatSetSizes(A, PETSC_DECIDE, PETSC_DECIDE, m, m));
179: PetscCall(MatSetFromOptions(A));
180: PetscCall(MatSetUp(A));
182: flg = PETSC_FALSE;
183: PetscCall(PetscOptionsGetBool(NULL, NULL, "-time_dependent_rhs", &flg, NULL));
184: if (flg) {
185: /*
186: For linear problems with a time-dependent f(u,t) in the equation
187: u_t = f(u,t), the user provides the discretized right-hand side
188: as a time-dependent matrix.
189: */
190: PetscCall(TSSetRHSFunction(ts, NULL, TSComputeRHSFunctionLinear, &appctx));
191: PetscCall(TSSetRHSJacobian(ts, A, A, RHSMatrixHeat, &appctx));
192: } else {
193: /*
194: For linear problems with a time-independent f(u) in the equation
195: u_t = f(u), the user provides the discretized right-hand side
196: as a matrix only once, and then sets a null matrix evaluation
197: routine.
198: */
199: PetscCall(RHSMatrixHeat(ts, 0.0, u, A, A, &appctx));
200: PetscCall(TSSetRHSFunction(ts, NULL, TSComputeRHSFunctionLinear, &appctx));
201: PetscCall(TSSetRHSJacobian(ts, A, A, TSComputeRHSJacobianConstant, &appctx));
202: }
204: if (tsproblem == TS_NONLINEAR) {
205: SNES snes;
206: PetscCall(TSSetRHSFunction(ts, NULL, RHSFunctionHeat, &appctx));
207: PetscCall(TSGetSNES(ts, &snes));
208: PetscCall(SNESSetJacobian(snes, NULL, NULL, SNESComputeJacobianDefault, NULL));
209: }
211: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
212: Set solution vector and initial timestep
213: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
215: dt = appctx.h * appctx.h / 2.0;
216: PetscCall(TSSetTimeStep(ts, dt));
217: PetscCall(TSSetSolution(ts, u));
219: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
220: Customize timestepping solver:
221: - Set the solution method to be the Backward Euler method.
222: - Set timestepping duration info
223: Then set runtime options, which can override these defaults.
224: For example,
225: -ts_max_steps <maxsteps> -ts_max_time <maxtime>
226: to override the defaults set by TSSetMaxSteps()/TSSetMaxTime().
227: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
229: PetscCall(TSSetMaxSteps(ts, time_steps_max));
230: PetscCall(TSSetMaxTime(ts, time_total_max));
231: PetscCall(TSSetExactFinalTime(ts, TS_EXACTFINALTIME_STEPOVER));
232: PetscCall(TSSetFromOptions(ts));
234: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
235: Solve the problem
236: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
238: /*
239: Evaluate initial conditions
240: */
241: PetscCall(InitialConditions(u, &appctx));
243: /*
244: Run the timestepping solver
245: */
246: PetscCall(TSSolve(ts, u));
247: PetscCall(TSGetSolveTime(ts, &ftime));
248: PetscCall(TSGetStepNumber(ts, &steps));
250: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
251: View timestepping solver info
252: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
253: PetscCall(PetscPrintf(PETSC_COMM_WORLD, "Total timesteps %" PetscInt_FMT ", Final time %g\n", steps, (double)ftime));
254: PetscCall(PetscPrintf(PETSC_COMM_WORLD, "Avg. error (2 norm) = %g Avg. error (max norm) = %g\n", (double)(appctx.norm_2 / steps), (double)(appctx.norm_max / steps)));
256: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
257: Free work space. All PETSc objects should be destroyed when they
258: are no longer needed.
259: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
261: PetscCall(TSDestroy(&ts));
262: PetscCall(MatDestroy(&A));
263: PetscCall(VecDestroy(&u));
264: PetscCall(PetscViewerDestroy(&appctx.viewer1));
265: PetscCall(PetscViewerDestroy(&appctx.viewer2));
266: PetscCall(VecDestroy(&appctx.localwork));
267: PetscCall(VecDestroy(&appctx.solution));
268: PetscCall(VecDestroy(&appctx.u_local));
269: PetscCall(DMDestroy(&appctx.da));
271: /*
272: Always call PetscFinalize() before exiting a program. This routine
273: - finalizes the PETSc libraries as well as MPI
274: - provides summary and diagnostic information if certain runtime
275: options are chosen (e.g., -log_view).
276: */
277: PetscCall(PetscFinalize());
278: return 0;
279: }
280: /* --------------------------------------------------------------------- */
281: /*
282: InitialConditions - Computes the solution at the initial time.
284: Input Parameter:
285: u - uninitialized solution vector (global)
286: appctx - user-defined application context
288: Output Parameter:
289: u - vector with solution at initial time (global)
290: */
291: PetscErrorCode InitialConditions(Vec u, AppCtx *appctx)
292: {
293: PetscScalar *u_localptr, h = appctx->h;
294: PetscInt i, mybase, myend;
296: PetscFunctionBeginUser;
297: /*
298: Determine starting point of each processor's range of
299: grid values.
300: */
301: PetscCall(VecGetOwnershipRange(u, &mybase, &myend));
303: /*
304: Get a pointer to vector data.
305: - For default PETSc vectors, VecGetArray() returns a pointer to
306: the data array. Otherwise, the routine is implementation dependent.
307: - You MUST call VecRestoreArray() when you no longer need access to
308: the array.
309: - Note that the Fortran interface to VecGetArray() differs from the
310: C version. See the users manual for details.
311: */
312: PetscCall(VecGetArray(u, &u_localptr));
314: /*
315: We initialize the solution array by simply writing the solution
316: directly into the array locations. Alternatively, we could use
317: VecSetValues() or VecSetValuesLocal().
318: */
319: for (i = mybase; i < myend; i++) u_localptr[i - mybase] = PetscSinScalar(PETSC_PI * i * 6. * h) + 3. * PetscSinScalar(PETSC_PI * i * 2. * h);
321: /*
322: Restore vector
323: */
324: PetscCall(VecRestoreArray(u, &u_localptr));
326: /*
327: Print debugging information if desired
328: */
329: if (appctx->debug) {
330: PetscCall(PetscPrintf(appctx->comm, "initial guess vector\n"));
331: PetscCall(VecView(u, PETSC_VIEWER_STDOUT_WORLD));
332: }
333: PetscFunctionReturn(PETSC_SUCCESS);
334: }
335: /* --------------------------------------------------------------------- */
336: /*
337: ExactSolution - Computes the exact solution at a given time.
339: Input Parameters:
340: t - current time
341: solution - vector in which exact solution will be computed
342: appctx - user-defined application context
344: Output Parameter:
345: solution - vector with the newly computed exact solution
346: */
347: PetscErrorCode ExactSolution(PetscReal t, Vec solution, AppCtx *appctx)
348: {
349: PetscScalar *s_localptr, h = appctx->h, ex1, ex2, sc1, sc2;
350: PetscInt i, mybase, myend;
352: PetscFunctionBeginUser;
353: /*
354: Determine starting and ending points of each processor's
355: range of grid values
356: */
357: PetscCall(VecGetOwnershipRange(solution, &mybase, &myend));
359: /*
360: Get a pointer to vector data.
361: */
362: PetscCall(VecGetArray(solution, &s_localptr));
364: /*
365: Simply write the solution directly into the array locations.
366: Alternatively, we culd use VecSetValues() or VecSetValuesLocal().
367: */
368: ex1 = PetscExpReal(-36. * PETSC_PI * PETSC_PI * t);
369: ex2 = PetscExpReal(-4. * PETSC_PI * PETSC_PI * t);
370: sc1 = PETSC_PI * 6. * h;
371: sc2 = PETSC_PI * 2. * h;
372: for (i = mybase; i < myend; i++) s_localptr[i - mybase] = PetscSinScalar(sc1 * (PetscReal)i) * ex1 + 3. * PetscSinScalar(sc2 * (PetscReal)i) * ex2;
374: /*
375: Restore vector
376: */
377: PetscCall(VecRestoreArray(solution, &s_localptr));
378: PetscFunctionReturn(PETSC_SUCCESS);
379: }
380: /* --------------------------------------------------------------------- */
381: /*
382: Monitor - User-provided routine to monitor the solution computed at
383: each timestep. This example plots the solution and computes the
384: error in two different norms.
386: Input Parameters:
387: ts - the timestep context
388: step - the count of the current step (with 0 meaning the
389: initial condition)
390: time - the current time
391: u - the solution at this timestep
392: ctx - the user-provided context for this monitoring routine.
393: In this case we use the application context which contains
394: information about the problem size, workspace and the exact
395: solution.
396: */
397: PetscErrorCode Monitor(TS ts, PetscInt step, PetscReal time, Vec u, PetscCtx ctx)
398: {
399: AppCtx *appctx = (AppCtx *)ctx; /* user-defined application context */
400: PetscReal norm_2, norm_max;
402: PetscFunctionBeginUser;
403: /*
404: View a graph of the current iterate
405: */
406: PetscCall(VecView(u, appctx->viewer2));
408: /*
409: Compute the exact solution
410: */
411: PetscCall(ExactSolution(time, appctx->solution, appctx));
413: /*
414: Print debugging information if desired
415: */
416: if (appctx->debug) {
417: PetscCall(PetscPrintf(appctx->comm, "Computed solution vector\n"));
418: PetscCall(VecView(u, PETSC_VIEWER_STDOUT_WORLD));
419: PetscCall(PetscPrintf(appctx->comm, "Exact solution vector\n"));
420: PetscCall(VecView(appctx->solution, PETSC_VIEWER_STDOUT_WORLD));
421: }
423: /*
424: Compute the 2-norm and max-norm of the error
425: */
426: PetscCall(VecAXPY(appctx->solution, -1.0, u));
427: PetscCall(VecNorm(appctx->solution, NORM_2, &norm_2));
428: norm_2 = PetscSqrtReal(appctx->h) * norm_2;
429: PetscCall(VecNorm(appctx->solution, NORM_MAX, &norm_max));
430: if (norm_2 < 1e-14) norm_2 = 0;
431: if (norm_max < 1e-14) norm_max = 0;
433: /*
434: PetscPrintf() causes only the first processor in this
435: communicator to print the timestep information.
436: */
437: PetscCall(PetscPrintf(appctx->comm, "Timestep %" PetscInt_FMT ": time = %g 2-norm error = %g max norm error = %g\n", step, (double)time, (double)norm_2, (double)norm_max));
438: appctx->norm_2 += norm_2;
439: appctx->norm_max += norm_max;
441: /*
442: View a graph of the error
443: */
444: PetscCall(VecView(appctx->solution, appctx->viewer1));
446: /*
447: Print debugging information if desired
448: */
449: if (appctx->debug) {
450: PetscCall(PetscPrintf(appctx->comm, "Error vector\n"));
451: PetscCall(VecView(appctx->solution, PETSC_VIEWER_STDOUT_WORLD));
452: }
453: PetscFunctionReturn(PETSC_SUCCESS);
454: }
456: /* --------------------------------------------------------------------- */
457: /*
458: RHSMatrixHeat - User-provided routine to compute the right-hand-side
459: matrix for the heat equation.
461: Input Parameters:
462: ts - the TS context
463: t - current time
464: global_in - global input vector
465: dummy - optional user-defined context, as set by TSetRHSJacobian()
467: Output Parameters:
468: AA - Jacobian matrix
469: BB - optionally different matrix used to construct the preconditioner
471: Notes:
472: RHSMatrixHeat computes entries for the locally owned part of the system.
473: - Currently, all PETSc parallel matrix formats are partitioned by
474: contiguous chunks of rows across the processors.
475: - Each processor needs to insert only elements that it owns
476: locally (but any non-local elements will be sent to the
477: appropriate processor during matrix assembly).
478: - Always specify global row and columns of matrix entries when
479: using MatSetValues(); we could alternatively use MatSetValuesLocal().
480: - Here, we set all entries for a particular row at once.
481: - Note that MatSetValues() uses 0-based row and column numbers
482: in Fortran as well as in C.
483: */
484: PetscErrorCode RHSMatrixHeat(TS ts, PetscReal t, Vec X, Mat AA, Mat BB, PetscCtx ctx)
485: {
486: Mat A = AA; /* Jacobian matrix */
487: AppCtx *appctx = (AppCtx *)ctx; /* user-defined application context */
488: PetscInt i, mstart, mend, idx[3];
489: PetscScalar v[3], stwo = -2. / (appctx->h * appctx->h), sone = -.5 * stwo;
491: PetscFunctionBeginUser;
492: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
493: Compute entries for the locally owned part of the matrix
494: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
496: PetscCall(MatGetOwnershipRange(A, &mstart, &mend));
498: /*
499: Set matrix rows corresponding to boundary data
500: */
502: if (mstart == 0) { /* first processor only */
503: v[0] = 1.0;
504: PetscCall(MatSetValues(A, 1, &mstart, 1, &mstart, v, INSERT_VALUES));
505: mstart++;
506: }
508: if (mend == appctx->m) { /* last processor only */
509: mend--;
510: v[0] = 1.0;
511: PetscCall(MatSetValues(A, 1, &mend, 1, &mend, v, INSERT_VALUES));
512: }
514: /*
515: Set matrix rows corresponding to interior data. We construct the
516: matrix one row at a time.
517: */
518: v[0] = sone;
519: v[1] = stwo;
520: v[2] = sone;
521: for (i = mstart; i < mend; i++) {
522: idx[0] = i - 1;
523: idx[1] = i;
524: idx[2] = i + 1;
525: PetscCall(MatSetValues(A, 1, &i, 3, idx, v, INSERT_VALUES));
526: }
528: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
529: Complete the matrix assembly process and set some options
530: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
531: /*
532: Assemble matrix, using the 2-step process:
533: MatAssemblyBegin(), MatAssemblyEnd()
534: Computations can be done while messages are in transition
535: by placing code between these two statements.
536: */
537: PetscCall(MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY));
538: PetscCall(MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY));
540: /*
541: Set and option to indicate that we will never add a new nonzero location
542: to the matrix. If we do, it will generate an error.
543: */
544: PetscCall(MatSetOption(A, MAT_NEW_NONZERO_LOCATION_ERR, PETSC_TRUE));
545: PetscFunctionReturn(PETSC_SUCCESS);
546: }
548: PetscErrorCode RHSFunctionHeat(TS ts, PetscReal t, Vec globalin, Vec globalout, PetscCtx ctx)
549: {
550: Mat A;
552: PetscFunctionBeginUser;
553: PetscCall(TSGetRHSJacobian(ts, &A, NULL, NULL, &ctx));
554: PetscCall(RHSMatrixHeat(ts, t, globalin, A, NULL, ctx));
555: /* PetscCall(MatView(A,PETSC_VIEWER_STDOUT_WORLD)); */
556: PetscCall(MatMult(A, globalin, globalout));
557: PetscFunctionReturn(PETSC_SUCCESS);
558: }
560: /*TEST
562: test:
563: args: -ts_view -nox
565: test:
566: suffix: 2
567: args: -ts_view -nox
568: nsize: 3
570: test:
571: suffix: 3
572: args: -ts_view -nox -nonlinear
574: test:
575: suffix: 4
576: args: -ts_view -nox -nonlinear
577: nsize: 3
578: timeoutfactor: 3
580: test:
581: suffix: sundials
582: requires: sundials2
583: args: -nox -ts_type sundials -ts_max_steps 5 -nonlinear
584: nsize: 4
586: test:
587: suffix: sundials_dense
588: requires: sundials2
589: args: -nox -ts_type sundials -ts_sundials_use_dense -ts_max_steps 5 -nonlinear
590: nsize: 1
592: TEST*/