xref: /petsc/src/ts/tutorials/extchemfield.c (revision ff87db43c5082475ccdd828c5a732608447f441c)
1 static const char help[] = "Integrate chemistry using TChem.\n";
2 
3 #include <petscts.h>
4 #include <petscdmda.h>
5 
6 #if defined(PETSC_HAVE_TCHEM)
7 #if defined(MAX)
8 #undef MAX
9 #endif
10 #if defined(MIN)
11 #undef MIN
12 #endif
13 #include <TC_params.h>
14 #include <TC_interface.h>
15 #else
16 #error TChem is required for this example.  Reconfigure PETSc using --download-tchem.
17 #endif
18 /*
19 
20     This is an extension of extchem.c to solve the reaction equations independently in each cell of a one dimensional field
21 
22     Obtain the three files into this directory
23 
24        curl http://combustion.berkeley.edu/gri_mech/version30/files30/grimech30.dat > chem.inp
25        curl http://combustion.berkeley.edu/gri_mech/version30/files30/thermo30.dat > therm.dat
26        cp $PETSC_DIR/$PETSC_ARCH/externalpackages/tchem/data/periodictable.dat .
27 
28     Run with
29      ./extchemfield  -ts_arkimex_fully_implicit -ts_max_snes_failures -1 -ts_adapt_monitor -ts_adapt_dt_max 1e-4 -ts_arkimex_type 4 -ts_max_time .005
30 
31      Options for visualizing the solution:
32         Watch certain variables in each cell evolve with time
33         -draw_solution 1 -ts_monitor_lg_solution_variables Temp,H2,O2,H2O,CH4,CO,CO2,C2H2,N2 -lg_use_markers false  -draw_pause -2
34 
35         Watch certain variables in all cells evolve with time
36         -da_refine 4 -ts_monitor_draw_solution -draw_fields_by_name Temp,H2 -draw_vec_mark_points  -draw_pause -2
37 
38         Keep the initial temperature distribution as one monitors the current temperature distribution
39         -ts_monitor_draw_solution_initial -draw_bounds .9,1.7 -draw_fields_by_name Temp
40 
41         Save the images in a .gif (movie) file
42         -draw_save -draw_save_single_file
43 
44         Compute the sensitivies of the solution of the first temperature on the initial conditions
45         -ts_adjoint_solve  -ts_dt 1.e-5 -ts_type cn -ts_adjoint_view_solution draw
46 
47         Turn off diffusion
48         -diffusion no
49 
50         Turn off reactions
51         -reactions no
52 
53     The solution for component i = 0 is the temperature.
54 
55     The solution, i > 0, is the mass fraction, massf[i], of species i, i.e. mass of species i/ total mass of all species
56 
57     The mole fraction molef[i], i > 0, is the number of moles of a species/ total number of moles of all species
58         Define M[i] = mass per mole of species i then
59         molef[i] = massf[i]/(M[i]*(sum_j massf[j]/M[j]))
60 
61     FormMoleFraction(User,massf,molef) converts the mass fraction solution of each species to the mole fraction of each species.
62 
63 */
64 typedef struct _User *User;
65 struct _User {
66   PetscReal pressure;
67   int       Nspec;
68   int       Nreac;
69   PetscReal Tini, dx;
70   PetscReal diffus;
71   DM        dm;
72   PetscBool diffusion, reactions;
73   double   *tchemwork;
74   double   *Jdense; /* Dense array workspace where Tchem computes the Jacobian */
75   PetscInt *rows;
76 };
77 
78 static PetscErrorCode MonitorCell(TS, User, PetscInt);
79 static PetscErrorCode FormRHSFunction(TS, PetscReal, Vec, Vec, void *);
80 static PetscErrorCode FormRHSJacobian(TS, PetscReal, Vec, Mat, Mat, void *);
81 static PetscErrorCode FormInitialSolution(TS, Vec, void *);
82 
83 #define PetscCallTC(ierr) \
84   do { PetscCheck(!ierr, PETSC_COMM_SELF, PETSC_ERR_LIB, "Error in TChem library, return code %d", ierr); } while (0)
85 
86 int main(int argc, char **argv) {
87   TS                ts; /* time integrator */
88   TSAdapt           adapt;
89   Vec               X; /* solution vector */
90   Mat               J; /* Jacobian matrix */
91   PetscInt          steps, ncells, xs, xm, i;
92   PetscReal         ftime, dt;
93   char              chemfile[PETSC_MAX_PATH_LEN] = "chem.inp", thermofile[PETSC_MAX_PATH_LEN] = "therm.dat";
94   struct _User      user;
95   TSConvergedReason reason;
96   PetscBool         showsolutions = PETSC_FALSE;
97   char            **snames, *names;
98   Vec               lambda; /* used with TSAdjoint for sensitivities */
99 
100   PetscFunctionBeginUser;
101   PetscCall(PetscInitialize(&argc, &argv, (char *)0, help));
102   PetscOptionsBegin(PETSC_COMM_WORLD, NULL, "Chemistry solver options", "");
103   PetscCall(PetscOptionsString("-chem", "CHEMKIN input file", "", chemfile, chemfile, sizeof(chemfile), NULL));
104   PetscCall(PetscOptionsString("-thermo", "NASA thermo input file", "", thermofile, thermofile, sizeof(thermofile), NULL));
105   user.pressure = 1.01325e5; /* Pascal */
106   PetscCall(PetscOptionsReal("-pressure", "Pressure of reaction [Pa]", "", user.pressure, &user.pressure, NULL));
107   user.Tini = 1550;
108   PetscCall(PetscOptionsReal("-Tini", "Initial temperature [K]", "", user.Tini, &user.Tini, NULL));
109   user.diffus = 100;
110   PetscCall(PetscOptionsReal("-diffus", "Diffusion constant", "", user.diffus, &user.diffus, NULL));
111   PetscCall(PetscOptionsBool("-draw_solution", "Plot the solution for each cell", "", showsolutions, &showsolutions, NULL));
112   user.diffusion = PETSC_TRUE;
113   PetscCall(PetscOptionsBool("-diffusion", "Have diffusion", "", user.diffusion, &user.diffusion, NULL));
114   user.reactions = PETSC_TRUE;
115   PetscCall(PetscOptionsBool("-reactions", "Have reactions", "", user.reactions, &user.reactions, NULL));
116   PetscOptionsEnd();
117 
118   PetscCallTC(TC_initChem(chemfile, thermofile, 0, 1.0));
119   user.Nspec = TC_getNspec();
120   user.Nreac = TC_getNreac();
121 
122   PetscCall(DMDACreate1d(PETSC_COMM_WORLD, DM_BOUNDARY_PERIODIC, 10, user.Nspec + 1, 1, NULL, &user.dm));
123   PetscCall(DMSetFromOptions(user.dm));
124   PetscCall(DMSetUp(user.dm));
125   PetscCall(DMDAGetInfo(user.dm, NULL, &ncells, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
126   user.dx = 1.0 / ncells; /* Set the coordinates of the cell centers; note final ghost cell is at x coordinate 1.0 */
127   PetscCall(DMDASetUniformCoordinates(user.dm, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0));
128 
129   /* set the names of each field in the DMDA based on the species name */
130   PetscCall(PetscMalloc1((user.Nspec + 1) * LENGTHOFSPECNAME, &names));
131   PetscCall(PetscStrcpy(names, "Temp"));
132   TC_getSnames(user.Nspec, names + LENGTHOFSPECNAME);
133   PetscCall(PetscMalloc1((user.Nspec + 2), &snames));
134   for (i = 0; i < user.Nspec + 1; i++) snames[i] = names + i * LENGTHOFSPECNAME;
135   snames[user.Nspec + 1] = NULL;
136   PetscCall(DMDASetFieldNames(user.dm, (const char *const *)snames));
137   PetscCall(PetscFree(snames));
138   PetscCall(PetscFree(names));
139 
140   PetscCall(DMCreateMatrix(user.dm, &J));
141   PetscCall(DMCreateGlobalVector(user.dm, &X));
142 
143   PetscCall(PetscMalloc3(user.Nspec + 1, &user.tchemwork, PetscSqr(user.Nspec + 1), &user.Jdense, user.Nspec + 1, &user.rows));
144 
145   /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
146      Create timestepping solver context
147      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
148   PetscCall(TSCreate(PETSC_COMM_WORLD, &ts));
149   PetscCall(TSSetDM(ts, user.dm));
150   PetscCall(TSSetType(ts, TSARKIMEX));
151   PetscCall(TSARKIMEXSetFullyImplicit(ts, PETSC_TRUE));
152   PetscCall(TSARKIMEXSetType(ts, TSARKIMEX4));
153   PetscCall(TSSetRHSFunction(ts, NULL, FormRHSFunction, &user));
154   PetscCall(TSSetRHSJacobian(ts, J, J, FormRHSJacobian, &user));
155 
156   ftime = 1.0;
157   PetscCall(TSSetMaxTime(ts, ftime));
158   PetscCall(TSSetExactFinalTime(ts, TS_EXACTFINALTIME_STEPOVER));
159 
160   /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
161      Set initial conditions
162    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
163   PetscCall(FormInitialSolution(ts, X, &user));
164   PetscCall(TSSetSolution(ts, X));
165   dt = 1e-10; /* Initial time step */
166   PetscCall(TSSetTimeStep(ts, dt));
167   PetscCall(TSGetAdapt(ts, &adapt));
168   PetscCall(TSAdaptSetStepLimits(adapt, 1e-12, 1e-4)); /* Also available with -ts_adapt_dt_min/-ts_adapt_dt_max */
169   PetscCall(TSSetMaxSNESFailures(ts, -1));             /* Retry step an unlimited number of times */
170 
171   /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
172      Pass information to graphical monitoring routine
173    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
174   if (showsolutions) {
175     PetscCall(DMDAGetCorners(user.dm, &xs, NULL, NULL, &xm, NULL, NULL));
176     for (i = xs; i < xs + xm; i++) PetscCall(MonitorCell(ts, &user, i));
177   }
178 
179   /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
180      Set runtime options
181    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
182   PetscCall(TSSetFromOptions(ts));
183 
184   /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
185      Set final conditions for sensitivities
186    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
187   PetscCall(DMCreateGlobalVector(user.dm, &lambda));
188   PetscCall(TSSetCostGradients(ts, 1, &lambda, NULL));
189   PetscCall(VecSetValue(lambda, 0, 1.0, INSERT_VALUES));
190   PetscCall(VecAssemblyBegin(lambda));
191   PetscCall(VecAssemblyEnd(lambda));
192 
193   /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
194      Solve ODE
195      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
196   PetscCall(TSSolve(ts, X));
197   PetscCall(TSGetSolveTime(ts, &ftime));
198   PetscCall(TSGetStepNumber(ts, &steps));
199   PetscCall(TSGetConvergedReason(ts, &reason));
200   PetscCall(PetscPrintf(PETSC_COMM_WORLD, "%s at time %g after %" PetscInt_FMT " steps\n", TSConvergedReasons[reason], (double)ftime, steps));
201 
202   {
203     Vec                max;
204     const char *const *names;
205     PetscInt           i;
206     const PetscReal   *bmax;
207 
208     PetscCall(TSMonitorEnvelopeGetBounds(ts, &max, NULL));
209     if (max) {
210       PetscCall(TSMonitorLGGetVariableNames(ts, &names));
211       if (names) {
212         PetscCall(VecGetArrayRead(max, &bmax));
213         PetscCall(PetscPrintf(PETSC_COMM_SELF, "Species - maximum mass fraction\n"));
214         for (i = 1; i < user.Nspec; i++) {
215           if (bmax[i] > .01) PetscCall(PetscPrintf(PETSC_COMM_SELF, "%s %g\n", names[i], (double)bmax[i]));
216         }
217         PetscCall(VecRestoreArrayRead(max, &bmax));
218       }
219     }
220   }
221 
222   /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
223      Free work space.
224    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
225   TC_reset();
226   PetscCall(DMDestroy(&user.dm));
227   PetscCall(MatDestroy(&J));
228   PetscCall(VecDestroy(&X));
229   PetscCall(VecDestroy(&lambda));
230   PetscCall(TSDestroy(&ts));
231   PetscCall(PetscFree3(user.tchemwork, user.Jdense, user.rows));
232   PetscCall(PetscFinalize());
233   return 0;
234 }
235 
236 /*
237    Applies the second order centered difference diffusion operator on a one dimensional periodic domain
238 */
239 static PetscErrorCode FormDiffusionFunction(TS ts, PetscReal t, Vec X, Vec F, void *ptr) {
240   User                user = (User)ptr;
241   PetscScalar       **f;
242   const PetscScalar **x;
243   DM                  dm;
244   PetscInt            i, xs, xm, j, dof;
245   Vec                 Xlocal;
246   PetscReal           idx;
247 
248   PetscFunctionBeginUser;
249   PetscCall(TSGetDM(ts, &dm));
250   PetscCall(DMDAGetInfo(dm, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &dof, NULL, NULL, NULL, NULL, NULL));
251   PetscCall(DMGetLocalVector(dm, &Xlocal));
252   PetscCall(DMGlobalToLocalBegin(dm, X, INSERT_VALUES, Xlocal));
253   PetscCall(DMGlobalToLocalEnd(dm, X, INSERT_VALUES, Xlocal));
254   PetscCall(DMDAVecGetArrayDOFRead(dm, Xlocal, &x));
255   PetscCall(DMDAVecGetArrayDOF(dm, F, &f));
256   PetscCall(DMDAGetCorners(dm, &xs, NULL, NULL, &xm, NULL, NULL));
257 
258   idx = 1.0 * user->diffus / user->dx;
259   for (i = xs; i < xs + xm; i++) {
260     for (j = 0; j < dof; j++) f[i][j] += idx * (x[i + 1][j] - 2.0 * x[i][j] + x[i - 1][j]);
261   }
262   PetscCall(DMDAVecRestoreArrayDOFRead(dm, Xlocal, &x));
263   PetscCall(DMDAVecRestoreArrayDOF(dm, F, &f));
264   PetscCall(DMRestoreLocalVector(dm, &Xlocal));
265   PetscFunctionReturn(0);
266 }
267 
268 /*
269    Produces the second order centered difference diffusion operator on a one dimensional periodic domain
270 */
271 static PetscErrorCode FormDiffusionJacobian(TS ts, PetscReal t, Vec X, Mat Amat, Mat Pmat, void *ptr) {
272   User       user = (User)ptr;
273   DM         dm;
274   PetscInt   i, xs, xm, j, dof;
275   PetscReal  idx, values[3];
276   MatStencil row, col[3];
277 
278   PetscFunctionBeginUser;
279   PetscCall(TSGetDM(ts, &dm));
280   PetscCall(DMDAGetInfo(dm, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &dof, NULL, NULL, NULL, NULL, NULL));
281   PetscCall(DMDAGetCorners(dm, &xs, NULL, NULL, &xm, NULL, NULL));
282 
283   idx       = 1.0 * user->diffus / user->dx;
284   values[0] = idx;
285   values[1] = -2.0 * idx;
286   values[2] = idx;
287   for (i = xs; i < xs + xm; i++) {
288     for (j = 0; j < dof; j++) {
289       row.i    = i;
290       row.c    = j;
291       col[0].i = i - 1;
292       col[0].c = j;
293       col[1].i = i;
294       col[1].c = j;
295       col[2].i = i + 1;
296       col[2].c = j;
297       PetscCall(MatSetValuesStencil(Pmat, 1, &row, 3, col, values, ADD_VALUES));
298     }
299   }
300   PetscCall(MatAssemblyBegin(Pmat, MAT_FINAL_ASSEMBLY));
301   PetscCall(MatAssemblyEnd(Pmat, MAT_FINAL_ASSEMBLY));
302   PetscFunctionReturn(0);
303 }
304 
305 static PetscErrorCode FormRHSFunction(TS ts, PetscReal t, Vec X, Vec F, void *ptr) {
306   User                user = (User)ptr;
307   PetscScalar       **f;
308   const PetscScalar **x;
309   DM                  dm;
310   PetscInt            i, xs, xm;
311 
312   PetscFunctionBeginUser;
313   if (user->reactions) {
314     PetscCall(TSGetDM(ts, &dm));
315     PetscCall(DMDAVecGetArrayDOFRead(dm, X, &x));
316     PetscCall(DMDAVecGetArrayDOF(dm, F, &f));
317     PetscCall(DMDAGetCorners(dm, &xs, NULL, NULL, &xm, NULL, NULL));
318 
319     for (i = xs; i < xs + xm; i++) {
320       PetscCall(PetscArraycpy(user->tchemwork, x[i], user->Nspec + 1));
321       user->tchemwork[0] *= user->Tini; /* Dimensionalize */
322       PetscCallTC(TC_getSrc(user->tchemwork, user->Nspec + 1, f[i]));
323       f[i][0] /= user->Tini; /* Non-dimensionalize */
324     }
325 
326     PetscCall(DMDAVecRestoreArrayDOFRead(dm, X, &x));
327     PetscCall(DMDAVecRestoreArrayDOF(dm, F, &f));
328   } else {
329     PetscCall(VecZeroEntries(F));
330   }
331   if (user->diffusion) PetscCall(FormDiffusionFunction(ts, t, X, F, ptr));
332   PetscFunctionReturn(0);
333 }
334 
335 static PetscErrorCode FormRHSJacobian(TS ts, PetscReal t, Vec X, Mat Amat, Mat Pmat, void *ptr) {
336   User                user = (User)ptr;
337   const PetscScalar **x;
338   PetscInt            M = user->Nspec + 1, i, j, xs, xm;
339   DM                  dm;
340 
341   PetscFunctionBeginUser;
342   if (user->reactions) {
343     PetscCall(TSGetDM(ts, &dm));
344     PetscCall(MatZeroEntries(Pmat));
345     PetscCall(MatSetOption(Pmat, MAT_ROW_ORIENTED, PETSC_FALSE));
346     PetscCall(MatSetOption(Pmat, MAT_IGNORE_ZERO_ENTRIES, PETSC_TRUE));
347     PetscCall(DMDAVecGetArrayDOFRead(dm, X, &x));
348     PetscCall(DMDAGetCorners(dm, &xs, NULL, NULL, &xm, NULL, NULL));
349 
350     for (i = xs; i < xs + xm; i++) {
351       PetscCall(PetscArraycpy(user->tchemwork, x[i], user->Nspec + 1));
352       user->tchemwork[0] *= user->Tini; /* Dimensionalize temperature (first row) because that is what Tchem wants */
353       PetscCall(TC_getJacTYN(user->tchemwork, user->Nspec, user->Jdense, 1));
354 
355       for (j = 0; j < M; j++) user->Jdense[j + 0 * M] /= user->Tini; /* Non-dimensionalize first column */
356       for (j = 0; j < M; j++) user->Jdense[0 + j * M] /= user->Tini; /* Non-dimensionalize first row */
357       for (j = 0; j < M; j++) user->rows[j] = i * M + j;
358       PetscCall(MatSetValues(Pmat, M, user->rows, M, user->rows, user->Jdense, INSERT_VALUES));
359     }
360     PetscCall(DMDAVecRestoreArrayDOFRead(dm, X, &x));
361     PetscCall(MatAssemblyBegin(Pmat, MAT_FINAL_ASSEMBLY));
362     PetscCall(MatAssemblyEnd(Pmat, MAT_FINAL_ASSEMBLY));
363   } else {
364     PetscCall(MatZeroEntries(Pmat));
365   }
366   if (user->diffusion) PetscCall(FormDiffusionJacobian(ts, t, X, Amat, Pmat, ptr));
367   if (Amat != Pmat) {
368     PetscCall(MatAssemblyBegin(Amat, MAT_FINAL_ASSEMBLY));
369     PetscCall(MatAssemblyEnd(Amat, MAT_FINAL_ASSEMBLY));
370   }
371   PetscFunctionReturn(0);
372 }
373 
374 PetscErrorCode FormInitialSolution(TS ts, Vec X, void *ctx) {
375   PetscScalar **x, *xc;
376   struct {
377     const char *name;
378     PetscReal   massfrac;
379   } initial[] = {
380     {"CH4", 0.0948178320887 },
381     {"O2",  0.189635664177  },
382     {"N2",  0.706766236705  },
383     {"AR",  0.00878026702874}
384   };
385   PetscInt i, j, xs, xm;
386   DM       dm;
387 
388   PetscFunctionBeginUser;
389   PetscCall(VecZeroEntries(X));
390   PetscCall(TSGetDM(ts, &dm));
391   PetscCall(DMDAGetCorners(dm, &xs, NULL, NULL, &xm, NULL, NULL));
392 
393   PetscCall(DMDAGetCoordinateArray(dm, &xc));
394   PetscCall(DMDAVecGetArrayDOF(dm, X, &x));
395   for (i = xs; i < xs + xm; i++) {
396     x[i][0] = 1.0 + .05 * PetscSinScalar(2. * PETSC_PI * xc[i]); /* Non-dimensionalized by user->Tini */
397     for (j = 0; j < PETSC_STATIC_ARRAY_LENGTH(initial); j++) {
398       int ispec = TC_getSpos(initial[j].name, strlen(initial[j].name));
399       PetscCheck(ispec >= 0, PETSC_COMM_SELF, PETSC_ERR_USER, "Could not find species %s", initial[j].name);
400       PetscCall(PetscPrintf(PETSC_COMM_SELF, "Species %d: %s %g\n", j, initial[j].name, (double)initial[j].massfrac));
401       x[i][1 + ispec] = initial[j].massfrac;
402     }
403   }
404   PetscCall(DMDAVecRestoreArrayDOF(dm, X, &x));
405   PetscCall(DMDARestoreCoordinateArray(dm, &xc));
406   PetscFunctionReturn(0);
407 }
408 
409 /*
410     Routines for displaying the solutions
411 */
412 typedef struct {
413   PetscInt cell;
414   User     user;
415 } UserLGCtx;
416 
417 static PetscErrorCode FormMoleFraction(UserLGCtx *ctx, Vec massf, Vec *molef) {
418   User                user = ctx->user;
419   PetscReal          *M, tM = 0;
420   PetscInt            i, n  = user->Nspec + 1;
421   PetscScalar        *mof;
422   const PetscScalar **maf;
423 
424   PetscFunctionBeginUser;
425   PetscCall(VecCreateSeq(PETSC_COMM_SELF, n, molef));
426   PetscCall(PetscMalloc1(user->Nspec, &M));
427   TC_getSmass(user->Nspec, M);
428   PetscCall(DMDAVecGetArrayDOFRead(user->dm, massf, &maf));
429   PetscCall(VecGetArray(*molef, &mof));
430   mof[0] = maf[ctx->cell][0]; /* copy over temperature */
431   for (i = 1; i < n; i++) tM += maf[ctx->cell][i] / M[i - 1];
432   for (i = 1; i < n; i++) mof[i] = maf[ctx->cell][i] / (M[i - 1] * tM);
433   PetscCall(DMDAVecRestoreArrayDOFRead(user->dm, massf, &maf));
434   PetscCall(VecRestoreArray(*molef, &mof));
435   PetscCall(PetscFree(M));
436   PetscFunctionReturn(0);
437 }
438 
439 static PetscErrorCode MonitorCellDestroy(UserLGCtx *uctx) {
440   PetscFunctionBeginUser;
441   PetscCall(PetscFree(uctx));
442   PetscFunctionReturn(0);
443 }
444 
445 /*
446    Use TSMonitorLG to monitor the reactions in a particular cell
447 */
448 static PetscErrorCode MonitorCell(TS ts, User user, PetscInt cell) {
449   TSMonitorLGCtx ctx;
450   char         **snames;
451   UserLGCtx     *uctx;
452   char           label[128];
453   PetscReal      temp, *xc;
454   PetscMPIInt    rank;
455 
456   PetscFunctionBeginUser;
457   PetscCall(DMDAGetCoordinateArray(user->dm, &xc));
458   temp = 1.0 + .05 * PetscSinScalar(2. * PETSC_PI * xc[cell]); /* Non-dimensionalized by user->Tini */
459   PetscCall(DMDARestoreCoordinateArray(user->dm, &xc));
460   PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
461   PetscCall(PetscSNPrintf(label, sizeof(label), "Initial Temperature %g Cell %d Rank %d", (double)user->Tini * temp, (int)cell, rank));
462   PetscCall(TSMonitorLGCtxCreate(PETSC_COMM_SELF, NULL, label, PETSC_DECIDE, PETSC_DECIDE, 600, 400, 1, &ctx));
463   PetscCall(DMDAGetFieldNames(user->dm, (const char *const **)&snames));
464   PetscCall(TSMonitorLGCtxSetVariableNames(ctx, (const char *const *)snames));
465   PetscCall(PetscNew(&uctx));
466   uctx->cell = cell;
467   uctx->user = user;
468   PetscCall(TSMonitorLGCtxSetTransform(ctx, (PetscErrorCode(*)(void *, Vec, Vec *))FormMoleFraction, (PetscErrorCode(*)(void *))MonitorCellDestroy, uctx));
469   PetscCall(TSMonitorSet(ts, TSMonitorLGSolution, ctx, (PetscErrorCode(*)(void **))TSMonitorLGCtxDestroy));
470   PetscFunctionReturn(0);
471 }
472