xref: /petsc/src/ts/tutorials/ex10.c (revision 6dd63270497ad23dcf16ae500a87ff2b2a0b7474)
1 /*
2   This example implements the model described in
3 
4     Rauenzahn, Mousseau, Knoll. "Temporal accuracy of the nonequilibrium radiation diffusion
5     equations employing a Saha ionization model" 2005.
6 
7   The paper discusses three examples, the first two are nondimensional with a simple
8   ionization model.  The third example is fully dimensional and uses the Saha ionization
9   model with realistic parameters.
10 */
11 
12 #include <petscts.h>
13 #include <petscdm.h>
14 #include <petscdmda.h>
15 
16 typedef enum {
17   BC_DIRICHLET,
18   BC_NEUMANN,
19   BC_ROBIN
20 } BCType;
21 static const char *const BCTypes[] = {"DIRICHLET", "NEUMANN", "ROBIN", "BCType", "BC_", 0};
22 typedef enum {
23   JACOBIAN_ANALYTIC,
24   JACOBIAN_MATRIXFREE,
25   JACOBIAN_FD_COLORING,
26   JACOBIAN_FD_FULL
27 } JacobianType;
28 static const char *const JacobianTypes[] = {"ANALYTIC", "MATRIXFREE", "FD_COLORING", "FD_FULL", "JacobianType", "FD_", 0};
29 typedef enum {
30   DISCRETIZATION_FD,
31   DISCRETIZATION_FE
32 } DiscretizationType;
33 static const char *const DiscretizationTypes[] = {"FD", "FE", "DiscretizationType", "DISCRETIZATION_", 0};
34 typedef enum {
35   QUADRATURE_GAUSS1,
36   QUADRATURE_GAUSS2,
37   QUADRATURE_GAUSS3,
38   QUADRATURE_GAUSS4,
39   QUADRATURE_LOBATTO2,
40   QUADRATURE_LOBATTO3
41 } QuadratureType;
42 static const char *const QuadratureTypes[] = {"GAUSS1", "GAUSS2", "GAUSS3", "GAUSS4", "LOBATTO2", "LOBATTO3", "QuadratureType", "QUADRATURE_", 0};
43 
44 typedef struct {
45   PetscScalar E; /* radiation energy */
46   PetscScalar T; /* material temperature */
47 } RDNode;
48 
49 typedef struct {
50   PetscReal meter, kilogram, second, Kelvin; /* Fundamental units */
51   PetscReal Joule, Watt;                     /* Derived units */
52 } RDUnit;
53 
54 typedef struct _n_RD *RD;
55 
56 struct _n_RD {
57   void (*MaterialEnergy)(RD, const RDNode *, PetscScalar *, RDNode *);
58   DM                 da;
59   PetscBool          monitor_residual;
60   DiscretizationType discretization;
61   QuadratureType     quadrature;
62   JacobianType       jacobian;
63   PetscInt           initial;
64   BCType             leftbc;
65   PetscBool          view_draw;
66   char               view_binary[PETSC_MAX_PATH_LEN];
67   PetscBool          test_diff;
68   PetscBool          endpoint;
69   PetscBool          bclimit;
70   PetscBool          bcmidpoint;
71   RDUnit             unit;
72 
73   /* model constants, see Table 2 and RDCreate() */
74   PetscReal rho, K_R, K_p, I_H, m_p, m_e, h, k, c, sigma_b, beta, gamma;
75 
76   /* Domain and boundary conditions */
77   PetscReal Eapplied; /* Radiation flux from the left */
78   PetscReal L;        /* Length of domain */
79   PetscReal final_time;
80 };
81 
82 static PetscErrorCode RDDestroy(RD *rd)
83 {
84   PetscFunctionBeginUser;
85   PetscCall(DMDestroy(&(*rd)->da));
86   PetscCall(PetscFree(*rd));
87   PetscFunctionReturn(PETSC_SUCCESS);
88 }
89 
90 /* The paper has a time derivative for material energy (Eq 2) which is a dependent variable (computable from temperature
91  * and density through an uninvertible relation).  Computing this derivative is trivial for trapezoid rule (used in the
92  * paper), but does not generalize nicely to higher order integrators.  Here we use the implicit form which provides
93  * time derivatives of the independent variables (radiation energy and temperature), so we must compute the time
94  * derivative of material energy ourselves (could be done using AD).
95  *
96  * There are multiple ionization models, this interface dispatches to the one currently in use.
97  */
98 static void RDMaterialEnergy(RD rd, const RDNode *n, PetscScalar *Em, RDNode *dEm)
99 {
100   rd->MaterialEnergy(rd, n, Em, dEm);
101 }
102 
103 /* Solves a quadratic equation while propagating tangents */
104 static void QuadraticSolve(PetscScalar a, PetscScalar a_t, PetscScalar b, PetscScalar b_t, PetscScalar c, PetscScalar c_t, PetscScalar *x, PetscScalar *x_t)
105 {
106   PetscScalar disc = b * b - 4. * a * c, disc_t = 2. * b * b_t - 4. * a_t * c - 4. * a * c_t, num = -b + PetscSqrtScalar(disc), /* choose positive sign */
107     num_t = -b_t + 0.5 / PetscSqrtScalar(disc) * disc_t, den = 2. * a, den_t = 2. * a_t;
108   *x   = num / den;
109   *x_t = (num_t * den - num * den_t) / PetscSqr(den);
110 }
111 
112 /* The primary model presented in the paper */
113 static void RDMaterialEnergy_Saha(RD rd, const RDNode *n, PetscScalar *inEm, RDNode *dEm)
114 {
115   PetscScalar Em, alpha, alpha_t, T = n->T, T_t = 1., chi = rd->I_H / (rd->k * T), chi_t = -chi / T * T_t, a = 1., a_t = 0, b = 4. * rd->m_p / rd->rho * PetscPowScalarReal(2. * PETSC_PI * rd->m_e * rd->I_H / PetscSqr(rd->h), 1.5) * PetscExpScalar(-chi) * PetscPowScalarReal(chi, 1.5), /* Eq 7 */
116     b_t = -b * chi_t + 1.5 * b / chi * chi_t, c = -b, c_t = -b_t;
117   QuadraticSolve(a, a_t, b, b_t, c, c_t, &alpha, &alpha_t);      /* Solve Eq 7 for alpha */
118   Em = rd->k * T / rd->m_p * (1.5 * (1. + alpha) + alpha * chi); /* Eq 6 */
119   if (inEm) *inEm = Em;
120   if (dEm) {
121     dEm->E = 0;
122     dEm->T = Em / T * T_t + rd->k * T / rd->m_p * (1.5 * alpha_t + alpha_t * chi + alpha * chi_t);
123   }
124 }
125 /* Reduced ionization model, Eq 30 */
126 static void RDMaterialEnergy_Reduced(RD rd, const RDNode *n, PetscScalar *Em, RDNode *dEm)
127 {
128   PetscScalar alpha, alpha_t, T = n->T, T_t = 1., chi = -0.3 / T, chi_t = -chi / T * T_t, a = 1., a_t = 0., b = PetscExpScalar(chi), b_t = b * chi_t, c = -b, c_t = -b_t;
129   QuadraticSolve(a, a_t, b, b_t, c, c_t, &alpha, &alpha_t);
130   if (Em) *Em = (1. + alpha) * T + 0.3 * alpha;
131   if (dEm) {
132     dEm->E = 0;
133     dEm->T = alpha_t * T + (1. + alpha) * T_t + 0.3 * alpha_t;
134   }
135 }
136 
137 /* Eq 5 */
138 static void RDSigma_R(RD rd, RDNode *n, PetscScalar *sigma_R, RDNode *dsigma_R)
139 {
140   *sigma_R    = rd->K_R * rd->rho * PetscPowScalar(n->T, -rd->gamma);
141   dsigma_R->E = 0;
142   dsigma_R->T = -rd->gamma * (*sigma_R) / n->T;
143 }
144 
145 /* Eq 4 */
146 static void RDDiffusionCoefficient(RD rd, PetscBool limit, RDNode *n, RDNode *nx, PetscScalar *D_R, RDNode *dD_R, RDNode *dxD_R)
147 {
148   PetscScalar sigma_R, denom;
149   RDNode      dsigma_R, ddenom, dxdenom;
150 
151   RDSigma_R(rd, n, &sigma_R, &dsigma_R);
152   denom     = 3. * rd->rho * sigma_R + (int)limit * PetscAbsScalar(nx->E) / n->E;
153   ddenom.E  = -(int)limit * PetscAbsScalar(nx->E) / PetscSqr(n->E);
154   ddenom.T  = 3. * rd->rho * dsigma_R.T;
155   dxdenom.E = (int)limit * (PetscRealPart(nx->E) < 0 ? -1. : 1.) / n->E;
156   dxdenom.T = 0;
157   *D_R      = rd->c / denom;
158   if (dD_R) {
159     dD_R->E = -rd->c / PetscSqr(denom) * ddenom.E;
160     dD_R->T = -rd->c / PetscSqr(denom) * ddenom.T;
161   }
162   if (dxD_R) {
163     dxD_R->E = -rd->c / PetscSqr(denom) * dxdenom.E;
164     dxD_R->T = -rd->c / PetscSqr(denom) * dxdenom.T;
165   }
166 }
167 
168 static PetscErrorCode RDStateView(RD rd, Vec X, Vec Xdot, Vec F)
169 {
170   DMDALocalInfo info;
171   PetscInt      i;
172   const RDNode *x, *xdot, *f;
173   MPI_Comm      comm;
174 
175   PetscFunctionBeginUser;
176   PetscCall(PetscObjectGetComm((PetscObject)rd->da, &comm));
177   PetscCall(DMDAGetLocalInfo(rd->da, &info));
178   PetscCall(DMDAVecGetArrayRead(rd->da, X, (void *)&x));
179   PetscCall(DMDAVecGetArrayRead(rd->da, Xdot, (void *)&xdot));
180   PetscCall(DMDAVecGetArrayRead(rd->da, F, (void *)&f));
181   for (i = info.xs; i < info.xs + info.xm; i++) {
182     PetscCall(PetscSynchronizedPrintf(comm, "x[%" PetscInt_FMT "] (%10.2G,%10.2G) (%10.2G,%10.2G) (%10.2G,%10.2G)\n", i, (double)PetscRealPart(x[i].E), (double)PetscRealPart(x[i].T), (double)PetscRealPart(xdot[i].E), (double)PetscRealPart(xdot[i].T),
183                                       (double)PetscRealPart(f[i].E), (double)PetscRealPart(f[i].T)));
184   }
185   PetscCall(DMDAVecRestoreArrayRead(rd->da, X, (void *)&x));
186   PetscCall(DMDAVecRestoreArrayRead(rd->da, Xdot, (void *)&xdot));
187   PetscCall(DMDAVecRestoreArrayRead(rd->da, F, (void *)&f));
188   PetscCall(PetscSynchronizedFlush(comm, PETSC_STDOUT));
189   PetscFunctionReturn(PETSC_SUCCESS);
190 }
191 
192 static PetscScalar RDRadiation(RD rd, const RDNode *n, RDNode *dn)
193 {
194   PetscScalar sigma_p = rd->K_p * rd->rho * PetscPowScalar(n->T, -rd->beta), sigma_p_T = -rd->beta * sigma_p / n->T, tmp = 4. * rd->sigma_b * PetscSqr(PetscSqr(n->T)) / rd->c - n->E, tmp_E = -1., tmp_T = 4. * rd->sigma_b * 4 * n->T * (PetscSqr(n->T)) / rd->c, rad = sigma_p * rd->c * rd->rho * tmp, rad_E = sigma_p * rd->c * rd->rho * tmp_E, rad_T = rd->c * rd->rho * (sigma_p_T * tmp + sigma_p * tmp_T);
195   if (dn) {
196     dn->E = rad_E;
197     dn->T = rad_T;
198   }
199   return rad;
200 }
201 
202 static PetscScalar RDDiffusion(RD rd, PetscReal hx, const RDNode x[], PetscInt i, RDNode d[])
203 {
204   PetscReal   ihx = 1. / hx;
205   RDNode      n_L, nx_L, n_R, nx_R, dD_L, dxD_L, dD_R, dxD_R, dfluxL[2], dfluxR[2];
206   PetscScalar D_L, D_R, fluxL, fluxR;
207 
208   n_L.E  = 0.5 * (x[i - 1].E + x[i].E);
209   n_L.T  = 0.5 * (x[i - 1].T + x[i].T);
210   nx_L.E = (x[i].E - x[i - 1].E) / hx;
211   nx_L.T = (x[i].T - x[i - 1].T) / hx;
212   RDDiffusionCoefficient(rd, PETSC_TRUE, &n_L, &nx_L, &D_L, &dD_L, &dxD_L);
213   fluxL       = D_L * nx_L.E;
214   dfluxL[0].E = -ihx * D_L + (0.5 * dD_L.E - ihx * dxD_L.E) * nx_L.E;
215   dfluxL[1].E = +ihx * D_L + (0.5 * dD_L.E + ihx * dxD_L.E) * nx_L.E;
216   dfluxL[0].T = (0.5 * dD_L.T - ihx * dxD_L.T) * nx_L.E;
217   dfluxL[1].T = (0.5 * dD_L.T + ihx * dxD_L.T) * nx_L.E;
218 
219   n_R.E  = 0.5 * (x[i].E + x[i + 1].E);
220   n_R.T  = 0.5 * (x[i].T + x[i + 1].T);
221   nx_R.E = (x[i + 1].E - x[i].E) / hx;
222   nx_R.T = (x[i + 1].T - x[i].T) / hx;
223   RDDiffusionCoefficient(rd, PETSC_TRUE, &n_R, &nx_R, &D_R, &dD_R, &dxD_R);
224   fluxR       = D_R * nx_R.E;
225   dfluxR[0].E = -ihx * D_R + (0.5 * dD_R.E - ihx * dxD_R.E) * nx_R.E;
226   dfluxR[1].E = +ihx * D_R + (0.5 * dD_R.E + ihx * dxD_R.E) * nx_R.E;
227   dfluxR[0].T = (0.5 * dD_R.T - ihx * dxD_R.T) * nx_R.E;
228   dfluxR[1].T = (0.5 * dD_R.T + ihx * dxD_R.T) * nx_R.E;
229 
230   if (d) {
231     d[0].E = -ihx * dfluxL[0].E;
232     d[0].T = -ihx * dfluxL[0].T;
233     d[1].E = ihx * (dfluxR[0].E - dfluxL[1].E);
234     d[1].T = ihx * (dfluxR[0].T - dfluxL[1].T);
235     d[2].E = ihx * dfluxR[1].E;
236     d[2].T = ihx * dfluxR[1].T;
237   }
238   return ihx * (fluxR - fluxL);
239 }
240 
241 static PetscErrorCode RDGetLocalArrays(RD rd, TS ts, Vec X, Vec Xdot, PetscReal *Theta, PetscReal *dt, Vec *X0loc, RDNode **x0, Vec *Xloc, RDNode **x, Vec *Xloc_t, RDNode **xdot)
242 {
243   PetscBool istheta;
244 
245   PetscFunctionBeginUser;
246   PetscCall(DMGetLocalVector(rd->da, X0loc));
247   PetscCall(DMGetLocalVector(rd->da, Xloc));
248   PetscCall(DMGetLocalVector(rd->da, Xloc_t));
249 
250   PetscCall(DMGlobalToLocalBegin(rd->da, X, INSERT_VALUES, *Xloc));
251   PetscCall(DMGlobalToLocalEnd(rd->da, X, INSERT_VALUES, *Xloc));
252   PetscCall(DMGlobalToLocalBegin(rd->da, Xdot, INSERT_VALUES, *Xloc_t));
253   PetscCall(DMGlobalToLocalEnd(rd->da, Xdot, INSERT_VALUES, *Xloc_t));
254 
255   /*
256     The following is a hack to subvert TSTHETA which is like an implicit midpoint method to behave more like a trapezoid
257     rule.  These methods have equivalent linear stability, but the nonlinear stability is somewhat different.  The
258     radiation system is inconvenient to write in explicit form because the ionization model is "on the left".
259    */
260   PetscCall(PetscObjectTypeCompare((PetscObject)ts, TSTHETA, &istheta));
261   if (istheta && rd->endpoint) {
262     PetscCall(TSThetaGetTheta(ts, Theta));
263   } else *Theta = 1.;
264 
265   PetscCall(TSGetTimeStep(ts, dt));
266   PetscCall(VecWAXPY(*X0loc, -(*Theta) * (*dt), *Xloc_t, *Xloc)); /* back out the value at the start of this step */
267   if (rd->endpoint) { PetscCall(VecWAXPY(*Xloc, *dt, *Xloc_t, *X0loc)); /* move the abscissa to the end of the step */ }
268 
269   PetscCall(DMDAVecGetArray(rd->da, *X0loc, x0));
270   PetscCall(DMDAVecGetArray(rd->da, *Xloc, x));
271   PetscCall(DMDAVecGetArray(rd->da, *Xloc_t, xdot));
272   PetscFunctionReturn(PETSC_SUCCESS);
273 }
274 
275 static PetscErrorCode RDRestoreLocalArrays(RD rd, Vec *X0loc, RDNode **x0, Vec *Xloc, RDNode **x, Vec *Xloc_t, RDNode **xdot)
276 {
277   PetscFunctionBeginUser;
278   PetscCall(DMDAVecRestoreArray(rd->da, *X0loc, x0));
279   PetscCall(DMDAVecRestoreArray(rd->da, *Xloc, x));
280   PetscCall(DMDAVecRestoreArray(rd->da, *Xloc_t, xdot));
281   PetscCall(DMRestoreLocalVector(rd->da, X0loc));
282   PetscCall(DMRestoreLocalVector(rd->da, Xloc));
283   PetscCall(DMRestoreLocalVector(rd->da, Xloc_t));
284   PetscFunctionReturn(PETSC_SUCCESS);
285 }
286 
287 static PetscErrorCode PETSC_UNUSED RDCheckDomain_Private(RD rd, TS ts, Vec X, PetscBool *in)
288 {
289   PetscInt  minloc;
290   PetscReal min;
291 
292   PetscFunctionBeginUser;
293   PetscCall(VecMin(X, &minloc, &min));
294   if (min < 0) {
295     SNES snes;
296     *in = PETSC_FALSE;
297     PetscCall(TSGetSNES(ts, &snes));
298     PetscCall(SNESSetFunctionDomainError(snes));
299     PetscCall(PetscInfo(ts, "Domain violation at %" PetscInt_FMT " field %" PetscInt_FMT " value %g\n", minloc / 2, minloc % 2, (double)min));
300   } else *in = PETSC_TRUE;
301   PetscFunctionReturn(PETSC_SUCCESS);
302 }
303 
304 /* Energy and temperature must remain positive */
305 #define RDCheckDomain(rd, ts, X) \
306   do { \
307     PetscBool _in; \
308     PetscCall(RDCheckDomain_Private(rd, ts, X, &_in)); \
309     if (!_in) PetscFunctionReturn(PETSC_SUCCESS); \
310   } while (0)
311 
312 static PetscErrorCode RDIFunction_FD(TS ts, PetscReal t, Vec X, Vec Xdot, Vec F, void *ctx)
313 {
314   RD            rd = (RD)ctx;
315   RDNode       *x, *x0, *xdot, *f;
316   Vec           X0loc, Xloc, Xloc_t;
317   PetscReal     hx, Theta, dt;
318   DMDALocalInfo info;
319   PetscInt      i;
320 
321   PetscFunctionBeginUser;
322   PetscCall(RDGetLocalArrays(rd, ts, X, Xdot, &Theta, &dt, &X0loc, &x0, &Xloc, &x, &Xloc_t, &xdot));
323   PetscCall(DMDAVecGetArray(rd->da, F, &f));
324   PetscCall(DMDAGetLocalInfo(rd->da, &info));
325   PetscCall(VecZeroEntries(F));
326 
327   hx = rd->L / (info.mx - 1);
328 
329   for (i = info.xs; i < info.xs + info.xm; i++) {
330     PetscReal   rho = rd->rho;
331     PetscScalar Em_t, rad;
332 
333     rad = (1. - Theta) * RDRadiation(rd, &x0[i], 0) + Theta * RDRadiation(rd, &x[i], 0);
334     if (rd->endpoint) {
335       PetscScalar Em0, Em1;
336       RDMaterialEnergy(rd, &x0[i], &Em0, NULL);
337       RDMaterialEnergy(rd, &x[i], &Em1, NULL);
338       Em_t = (Em1 - Em0) / dt;
339     } else {
340       RDNode dEm;
341       RDMaterialEnergy(rd, &x[i], NULL, &dEm);
342       Em_t = dEm.E * xdot[i].E + dEm.T * xdot[i].T;
343     }
344     /* Residuals are multiplied by the volume element (hx).  */
345     /* The temperature equation does not have boundary conditions */
346     f[i].T = hx * (rho * Em_t + rad);
347 
348     if (i == 0) { /* Left boundary condition */
349       PetscScalar D_R, bcTheta = rd->bcmidpoint ? Theta : 1.;
350       RDNode      n, nx;
351 
352       n.E  = (1. - bcTheta) * x0[0].E + bcTheta * x[0].E;
353       n.T  = (1. - bcTheta) * x0[0].T + bcTheta * x[0].T;
354       nx.E = ((1. - bcTheta) * (x0[1].E - x0[0].E) + bcTheta * (x[1].E - x[0].E)) / hx;
355       nx.T = ((1. - bcTheta) * (x0[1].T - x0[0].T) + bcTheta * (x[1].T - x[0].T)) / hx;
356       switch (rd->leftbc) {
357       case BC_ROBIN:
358         RDDiffusionCoefficient(rd, rd->bclimit, &n, &nx, &D_R, 0, 0);
359         f[0].E = hx * (n.E - 2. * D_R * nx.E - rd->Eapplied);
360         break;
361       case BC_NEUMANN:
362         f[0].E = x[1].E - x[0].E;
363         break;
364       default:
365         SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Case %" PetscInt_FMT, rd->initial);
366       }
367     } else if (i == info.mx - 1) {  /* Right boundary */
368       f[i].E = x[i].E - x[i - 1].E; /* Homogeneous Neumann */
369     } else {
370       PetscScalar diff = (1. - Theta) * RDDiffusion(rd, hx, x0, i, 0) + Theta * RDDiffusion(rd, hx, x, i, 0);
371       f[i].E           = hx * (xdot[i].E - diff - rad);
372     }
373   }
374   PetscCall(RDRestoreLocalArrays(rd, &X0loc, &x0, &Xloc, &x, &Xloc_t, &xdot));
375   PetscCall(DMDAVecRestoreArray(rd->da, F, &f));
376   if (rd->monitor_residual) PetscCall(RDStateView(rd, X, Xdot, F));
377   PetscFunctionReturn(PETSC_SUCCESS);
378 }
379 
380 static PetscErrorCode RDIJacobian_FD(TS ts, PetscReal t, Vec X, Vec Xdot, PetscReal a, Mat A, Mat B, void *ctx)
381 {
382   RD            rd = (RD)ctx;
383   RDNode       *x, *x0, *xdot;
384   Vec           X0loc, Xloc, Xloc_t;
385   PetscReal     hx, Theta, dt;
386   DMDALocalInfo info;
387   PetscInt      i;
388 
389   PetscFunctionBeginUser;
390   PetscCall(RDGetLocalArrays(rd, ts, X, Xdot, &Theta, &dt, &X0loc, &x0, &Xloc, &x, &Xloc_t, &xdot));
391   PetscCall(DMDAGetLocalInfo(rd->da, &info));
392   hx = rd->L / (info.mx - 1);
393   PetscCall(MatZeroEntries(B));
394 
395   for (i = info.xs; i < info.xs + info.xm; i++) {
396     PetscInt                  col[3];
397     PetscReal                 rho = rd->rho;
398     PetscScalar /*Em_t,rad,*/ K[2][6];
399     RDNode                    dEm_t, drad;
400 
401     /*rad = (1.-Theta)* */ RDRadiation(rd, &x0[i], 0); /* + Theta* */
402     RDRadiation(rd, &x[i], &drad);
403 
404     if (rd->endpoint) {
405       PetscScalar Em0, Em1;
406       RDNode      dEm1;
407       RDMaterialEnergy(rd, &x0[i], &Em0, NULL);
408       RDMaterialEnergy(rd, &x[i], &Em1, &dEm1);
409       /*Em_t = (Em1 - Em0) / (Theta*dt);*/
410       dEm_t.E = dEm1.E / (Theta * dt);
411       dEm_t.T = dEm1.T / (Theta * dt);
412     } else {
413       const PetscScalar epsilon = x[i].T * PETSC_SQRT_MACHINE_EPSILON;
414       RDNode            n1;
415       RDNode            dEm, dEm1;
416       PetscScalar       Em_TT;
417 
418       n1.E = x[i].E;
419       n1.T = x[i].T + epsilon;
420       RDMaterialEnergy(rd, &x[i], NULL, &dEm);
421       RDMaterialEnergy(rd, &n1, NULL, &dEm1);
422       /* The Jacobian needs another derivative.  We finite difference here instead of
423        * propagating second derivatives through the ionization model. */
424       Em_TT = (dEm1.T - dEm.T) / epsilon;
425       /*Em_t = dEm.E * xdot[i].E + dEm.T * xdot[i].T;*/
426       dEm_t.E = dEm.E * a;
427       dEm_t.T = dEm.T * a + Em_TT * xdot[i].T;
428     }
429 
430     PetscCall(PetscMemzero(K, sizeof(K)));
431     /* Residuals are multiplied by the volume element (hx).  */
432     if (i == 0) {
433       PetscScalar D, bcTheta = rd->bcmidpoint ? Theta : 1.;
434       RDNode      n, nx;
435       RDNode      dD, dxD;
436 
437       n.E  = (1. - bcTheta) * x0[0].E + bcTheta * x[0].E;
438       n.T  = (1. - bcTheta) * x0[0].T + bcTheta * x[0].T;
439       nx.E = ((1. - bcTheta) * (x0[1].E - x0[0].E) + bcTheta * (x[1].E - x[0].E)) / hx;
440       nx.T = ((1. - bcTheta) * (x0[1].T - x0[0].T) + bcTheta * (x[1].T - x[0].T)) / hx;
441       switch (rd->leftbc) {
442       case BC_ROBIN:
443         RDDiffusionCoefficient(rd, rd->bclimit, &n, &nx, &D, &dD, &dxD);
444         K[0][1 * 2 + 0] = (bcTheta / Theta) * hx * (1. - 2. * D * (-1. / hx) - 2. * nx.E * dD.E + 2. * nx.E * dxD.E / hx);
445         K[0][1 * 2 + 1] = (bcTheta / Theta) * hx * (-2. * nx.E * dD.T);
446         K[0][2 * 2 + 0] = (bcTheta / Theta) * hx * (-2. * D * (1. / hx) - 2. * nx.E * dD.E - 2. * nx.E * dxD.E / hx);
447         break;
448       case BC_NEUMANN:
449         K[0][1 * 2 + 0] = -1. / Theta;
450         K[0][2 * 2 + 0] = 1. / Theta;
451         break;
452       default:
453         SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Case %" PetscInt_FMT, rd->initial);
454       }
455     } else if (i == info.mx - 1) {
456       K[0][0 * 2 + 0] = -1. / Theta;
457       K[0][1 * 2 + 0] = 1. / Theta;
458     } else {
459       /*PetscScalar diff;*/
460       RDNode ddiff[3];
461       /*diff = (1.-Theta)*RDDiffusion(rd,hx,x0,i,0) + Theta* */ RDDiffusion(rd, hx, x, i, ddiff);
462       K[0][0 * 2 + 0] = -hx * ddiff[0].E;
463       K[0][0 * 2 + 1] = -hx * ddiff[0].T;
464       K[0][1 * 2 + 0] = hx * (a - ddiff[1].E - drad.E);
465       K[0][1 * 2 + 1] = hx * (-ddiff[1].T - drad.T);
466       K[0][2 * 2 + 0] = -hx * ddiff[2].E;
467       K[0][2 * 2 + 1] = -hx * ddiff[2].T;
468     }
469 
470     K[1][1 * 2 + 0] = hx * (rho * dEm_t.E + drad.E);
471     K[1][1 * 2 + 1] = hx * (rho * dEm_t.T + drad.T);
472 
473     col[0] = i - 1;
474     col[1] = i;
475     col[2] = i + 1 < info.mx ? i + 1 : -1;
476     PetscCall(MatSetValuesBlocked(B, 1, &i, 3, col, &K[0][0], INSERT_VALUES));
477   }
478   PetscCall(RDRestoreLocalArrays(rd, &X0loc, &x0, &Xloc, &x, &Xloc_t, &xdot));
479   PetscCall(MatAssemblyBegin(B, MAT_FINAL_ASSEMBLY));
480   PetscCall(MatAssemblyEnd(B, MAT_FINAL_ASSEMBLY));
481   if (A != B) {
482     PetscCall(MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY));
483     PetscCall(MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY));
484   }
485   PetscFunctionReturn(PETSC_SUCCESS);
486 }
487 
488 /* Evaluate interpolants and derivatives at a select quadrature point */
489 static void RDEvaluate(PetscReal interp[][2], PetscReal deriv[][2], PetscInt q, const RDNode x[], PetscInt i, RDNode *n, RDNode *nx)
490 {
491   PetscInt j;
492   n->E  = 0;
493   n->T  = 0;
494   nx->E = 0;
495   nx->T = 0;
496   for (j = 0; j < 2; j++) {
497     n->E += interp[q][j] * x[i + j].E;
498     n->T += interp[q][j] * x[i + j].T;
499     nx->E += deriv[q][j] * x[i + j].E;
500     nx->T += deriv[q][j] * x[i + j].T;
501   }
502 }
503 
504 /*
505  Various quadrature rules.  The nonlinear terms are non-polynomial so no standard quadrature will be exact.
506 */
507 static PetscErrorCode RDGetQuadrature(RD rd, PetscReal hx, PetscInt *nq, PetscReal weight[], PetscReal interp[][2], PetscReal deriv[][2])
508 {
509   PetscInt         q, j;
510   const PetscReal *refweight, (*refinterp)[2], (*refderiv)[2];
511 
512   PetscFunctionBeginUser;
513   switch (rd->quadrature) {
514   case QUADRATURE_GAUSS1: {
515     static const PetscReal ww[1]    = {1.};
516     static const PetscReal ii[1][2] = {
517       {0.5, 0.5}
518     };
519     static const PetscReal dd[1][2] = {
520       {-1., 1.}
521     };
522     *nq       = 1;
523     refweight = ww;
524     refinterp = ii;
525     refderiv  = dd;
526   } break;
527   case QUADRATURE_GAUSS2: {
528     static const PetscReal ii[2][2] = {
529       {0.78867513459481287, 0.21132486540518713},
530       {0.21132486540518713, 0.78867513459481287}
531     };
532     static const PetscReal dd[2][2] = {
533       {-1., 1.},
534       {-1., 1.}
535     };
536     static const PetscReal ww[2] = {0.5, 0.5};
537     *nq                          = 2;
538     refweight                    = ww;
539     refinterp                    = ii;
540     refderiv                     = dd;
541   } break;
542   case QUADRATURE_GAUSS3: {
543     static const PetscReal ii[3][2] = {
544       {0.8872983346207417, 0.1127016653792583},
545       {0.5,                0.5               },
546       {0.1127016653792583, 0.8872983346207417}
547     };
548     static const PetscReal dd[3][2] = {
549       {-1, 1},
550       {-1, 1},
551       {-1, 1}
552     };
553     static const PetscReal ww[3] = {5. / 18, 8. / 18, 5. / 18};
554     *nq                          = 3;
555     refweight                    = ww;
556     refinterp                    = ii;
557     refderiv                     = dd;
558   } break;
559   case QUADRATURE_GAUSS4: {
560     static const PetscReal ii[][2] = {
561       {0.93056815579702623,  0.069431844202973658},
562       {0.66999052179242813,  0.33000947820757187 },
563       {0.33000947820757187,  0.66999052179242813 },
564       {0.069431844202973658, 0.93056815579702623 }
565     };
566     static const PetscReal dd[][2] = {
567       {-1, 1},
568       {-1, 1},
569       {-1, 1},
570       {-1, 1}
571     };
572     static const PetscReal ww[] = {0.17392742256872692, 0.3260725774312731, 0.3260725774312731, 0.17392742256872692};
573 
574     *nq       = 4;
575     refweight = ww;
576     refinterp = ii;
577     refderiv  = dd;
578   } break;
579   case QUADRATURE_LOBATTO2: {
580     static const PetscReal ii[2][2] = {
581       {1., 0.},
582       {0., 1.}
583     };
584     static const PetscReal dd[2][2] = {
585       {-1., 1.},
586       {-1., 1.}
587     };
588     static const PetscReal ww[2] = {0.5, 0.5};
589     *nq                          = 2;
590     refweight                    = ww;
591     refinterp                    = ii;
592     refderiv                     = dd;
593   } break;
594   case QUADRATURE_LOBATTO3: {
595     static const PetscReal ii[3][2] = {
596       {1,   0  },
597       {0.5, 0.5},
598       {0,   1  }
599     };
600     static const PetscReal dd[3][2] = {
601       {-1, 1},
602       {-1, 1},
603       {-1, 1}
604     };
605     static const PetscReal ww[3] = {1. / 6, 4. / 6, 1. / 6};
606     *nq                          = 3;
607     refweight                    = ww;
608     refinterp                    = ii;
609     refderiv                     = dd;
610   } break;
611   default:
612     SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Unknown quadrature %d", (int)rd->quadrature);
613   }
614 
615   for (q = 0; q < *nq; q++) {
616     weight[q] = refweight[q] * hx;
617     for (j = 0; j < 2; j++) {
618       interp[q][j] = refinterp[q][j];
619       deriv[q][j]  = refderiv[q][j] / hx;
620     }
621   }
622   PetscFunctionReturn(PETSC_SUCCESS);
623 }
624 
625 /*
626  Finite element version
627 */
628 static PetscErrorCode RDIFunction_FE(TS ts, PetscReal t, Vec X, Vec Xdot, Vec F, void *ctx)
629 {
630   RD            rd = (RD)ctx;
631   RDNode       *x, *x0, *xdot, *f;
632   Vec           X0loc, Xloc, Xloc_t, Floc;
633   PetscReal     hx, Theta, dt, weight[5], interp[5][2], deriv[5][2];
634   DMDALocalInfo info;
635   PetscInt      i, j, q, nq;
636 
637   PetscFunctionBeginUser;
638   PetscCall(RDGetLocalArrays(rd, ts, X, Xdot, &Theta, &dt, &X0loc, &x0, &Xloc, &x, &Xloc_t, &xdot));
639 
640   PetscCall(DMGetLocalVector(rd->da, &Floc));
641   PetscCall(VecZeroEntries(Floc));
642   PetscCall(DMDAVecGetArray(rd->da, Floc, &f));
643   PetscCall(DMDAGetLocalInfo(rd->da, &info));
644 
645   /* Set up shape functions and quadrature for elements (assumes a uniform grid) */
646   hx = rd->L / (info.mx - 1);
647   PetscCall(RDGetQuadrature(rd, hx, &nq, weight, interp, deriv));
648 
649   for (i = info.xs; i < PetscMin(info.xs + info.xm, info.mx - 1); i++) {
650     for (q = 0; q < nq; q++) {
651       PetscReal   rho = rd->rho;
652       PetscScalar Em_t, rad, D_R, D0_R;
653       RDNode      n, n0, nx, n0x, nt, ntx;
654       RDEvaluate(interp, deriv, q, x, i, &n, &nx);
655       RDEvaluate(interp, deriv, q, x0, i, &n0, &n0x);
656       RDEvaluate(interp, deriv, q, xdot, i, &nt, &ntx);
657 
658       rad = (1. - Theta) * RDRadiation(rd, &n0, 0) + Theta * RDRadiation(rd, &n, 0);
659       if (rd->endpoint) {
660         PetscScalar Em0, Em1;
661         RDMaterialEnergy(rd, &n0, &Em0, NULL);
662         RDMaterialEnergy(rd, &n, &Em1, NULL);
663         Em_t = (Em1 - Em0) / dt;
664       } else {
665         RDNode dEm;
666         RDMaterialEnergy(rd, &n, NULL, &dEm);
667         Em_t = dEm.E * nt.E + dEm.T * nt.T;
668       }
669       RDDiffusionCoefficient(rd, PETSC_TRUE, &n0, &n0x, &D0_R, 0, 0);
670       RDDiffusionCoefficient(rd, PETSC_TRUE, &n, &nx, &D_R, 0, 0);
671       for (j = 0; j < 2; j++) {
672         f[i + j].E += (deriv[q][j] * weight[q] * ((1. - Theta) * D0_R * n0x.E + Theta * D_R * nx.E) + interp[q][j] * weight[q] * (nt.E - rad));
673         f[i + j].T += interp[q][j] * weight[q] * (rho * Em_t + rad);
674       }
675     }
676   }
677   if (info.xs == 0) {
678     switch (rd->leftbc) {
679     case BC_ROBIN: {
680       PetscScalar D_R, D_R_bc;
681       PetscReal   ratio, bcTheta = rd->bcmidpoint ? Theta : 1.;
682       RDNode      n, nx;
683 
684       n.E  = (1 - bcTheta) * x0[0].E + bcTheta * x[0].E;
685       n.T  = (1 - bcTheta) * x0[0].T + bcTheta * x[0].T;
686       nx.E = (x[1].E - x[0].E) / hx;
687       nx.T = (x[1].T - x[0].T) / hx;
688       RDDiffusionCoefficient(rd, PETSC_TRUE, &n, &nx, &D_R, 0, 0);
689       RDDiffusionCoefficient(rd, rd->bclimit, &n, &nx, &D_R_bc, 0, 0);
690       ratio = PetscRealPart(D_R / D_R_bc);
691       PetscCheck(ratio <= 1., PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Limited diffusivity is greater than unlimited");
692       PetscCheck(ratio >= 1e-3, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Heavily limited diffusivity");
693       f[0].E += -ratio * 0.5 * (rd->Eapplied - n.E);
694     } break;
695     case BC_NEUMANN:
696       /* homogeneous Neumann is the natural condition */
697       break;
698     default:
699       SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Case %" PetscInt_FMT, rd->initial);
700     }
701   }
702 
703   PetscCall(RDRestoreLocalArrays(rd, &X0loc, &x0, &Xloc, &x, &Xloc_t, &xdot));
704   PetscCall(DMDAVecRestoreArray(rd->da, Floc, &f));
705   PetscCall(VecZeroEntries(F));
706   PetscCall(DMLocalToGlobalBegin(rd->da, Floc, ADD_VALUES, F));
707   PetscCall(DMLocalToGlobalEnd(rd->da, Floc, ADD_VALUES, F));
708   PetscCall(DMRestoreLocalVector(rd->da, &Floc));
709 
710   if (rd->monitor_residual) PetscCall(RDStateView(rd, X, Xdot, F));
711   PetscFunctionReturn(PETSC_SUCCESS);
712 }
713 
714 static PetscErrorCode RDIJacobian_FE(TS ts, PetscReal t, Vec X, Vec Xdot, PetscReal a, Mat A, Mat B, void *ctx)
715 {
716   RD            rd = (RD)ctx;
717   RDNode       *x, *x0, *xdot;
718   Vec           X0loc, Xloc, Xloc_t;
719   PetscReal     hx, Theta, dt, weight[5], interp[5][2], deriv[5][2];
720   DMDALocalInfo info;
721   PetscInt      i, j, k, q, nq;
722   PetscScalar   K[4][4];
723 
724   PetscFunctionBeginUser;
725   PetscCall(RDGetLocalArrays(rd, ts, X, Xdot, &Theta, &dt, &X0loc, &x0, &Xloc, &x, &Xloc_t, &xdot));
726   PetscCall(DMDAGetLocalInfo(rd->da, &info));
727   hx = rd->L / (info.mx - 1);
728   PetscCall(RDGetQuadrature(rd, hx, &nq, weight, interp, deriv));
729   PetscCall(MatZeroEntries(B));
730   for (i = info.xs; i < PetscMin(info.xs + info.xm, info.mx - 1); i++) {
731     PetscInt rc[2];
732 
733     rc[0] = i;
734     rc[1] = i + 1;
735     PetscCall(PetscMemzero(K, sizeof(K)));
736     for (q = 0; q < nq; q++) {
737       PetscScalar              D_R;
738       PETSC_UNUSED PetscScalar rad;
739       RDNode                   n, nx, nt, ntx, drad, dD_R, dxD_R, dEm;
740       RDEvaluate(interp, deriv, q, x, i, &n, &nx);
741       RDEvaluate(interp, deriv, q, xdot, i, &nt, &ntx);
742       rad = RDRadiation(rd, &n, &drad);
743       RDDiffusionCoefficient(rd, PETSC_TRUE, &n, &nx, &D_R, &dD_R, &dxD_R);
744       RDMaterialEnergy(rd, &n, NULL, &dEm);
745       for (j = 0; j < 2; j++) {
746         for (k = 0; k < 2; k++) {
747           K[j * 2 + 0][k * 2 + 0] += (+interp[q][j] * weight[q] * (a - drad.E) * interp[q][k] + deriv[q][j] * weight[q] * ((D_R + dxD_R.E * nx.E) * deriv[q][k] + dD_R.E * nx.E * interp[q][k]));
748           K[j * 2 + 0][k * 2 + 1] += (+interp[q][j] * weight[q] * (-drad.T * interp[q][k]) + deriv[q][j] * weight[q] * (dxD_R.T * deriv[q][k] + dD_R.T * interp[q][k]) * nx.E);
749           K[j * 2 + 1][k * 2 + 0] += interp[q][j] * weight[q] * drad.E * interp[q][k];
750           K[j * 2 + 1][k * 2 + 1] += interp[q][j] * weight[q] * (a * rd->rho * dEm.T + drad.T) * interp[q][k];
751         }
752       }
753     }
754     PetscCall(MatSetValuesBlocked(B, 2, rc, 2, rc, &K[0][0], ADD_VALUES));
755   }
756   if (info.xs == 0) {
757     switch (rd->leftbc) {
758     case BC_ROBIN: {
759       PetscScalar D_R, D_R_bc;
760       PetscReal   ratio;
761       RDNode      n, nx;
762 
763       n.E  = (1 - Theta) * x0[0].E + Theta * x[0].E;
764       n.T  = (1 - Theta) * x0[0].T + Theta * x[0].T;
765       nx.E = (x[1].E - x[0].E) / hx;
766       nx.T = (x[1].T - x[0].T) / hx;
767       RDDiffusionCoefficient(rd, PETSC_TRUE, &n, &nx, &D_R, 0, 0);
768       RDDiffusionCoefficient(rd, rd->bclimit, &n, &nx, &D_R_bc, 0, 0);
769       ratio = PetscRealPart(D_R / D_R_bc);
770       PetscCall(MatSetValue(B, 0, 0, ratio * 0.5, ADD_VALUES));
771     } break;
772     case BC_NEUMANN:
773       /* homogeneous Neumann is the natural condition */
774       break;
775     default:
776       SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Case %" PetscInt_FMT, rd->initial);
777     }
778   }
779 
780   PetscCall(RDRestoreLocalArrays(rd, &X0loc, &x0, &Xloc, &x, &Xloc_t, &xdot));
781   PetscCall(MatAssemblyBegin(B, MAT_FINAL_ASSEMBLY));
782   PetscCall(MatAssemblyEnd(B, MAT_FINAL_ASSEMBLY));
783   if (A != B) {
784     PetscCall(MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY));
785     PetscCall(MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY));
786   }
787   PetscFunctionReturn(PETSC_SUCCESS);
788 }
789 
790 /* Temperature that is in equilibrium with the radiation density */
791 static PetscScalar RDRadiationTemperature(RD rd, PetscScalar E)
792 {
793   return PetscPowScalar(E * rd->c / (4. * rd->sigma_b), 0.25);
794 }
795 
796 static PetscErrorCode RDInitialState(RD rd, Vec X)
797 {
798   DMDALocalInfo info;
799   PetscInt      i;
800   RDNode       *x;
801 
802   PetscFunctionBeginUser;
803   PetscCall(DMDAGetLocalInfo(rd->da, &info));
804   PetscCall(DMDAVecGetArray(rd->da, X, &x));
805   for (i = info.xs; i < info.xs + info.xm; i++) {
806     PetscReal coord = i * rd->L / (info.mx - 1);
807     switch (rd->initial) {
808     case 1:
809       x[i].E = 0.001;
810       x[i].T = RDRadiationTemperature(rd, x[i].E);
811       break;
812     case 2:
813       x[i].E = 0.001 + 100. * PetscExpReal(-PetscSqr(coord / 0.1));
814       x[i].T = RDRadiationTemperature(rd, x[i].E);
815       break;
816     case 3:
817       x[i].E = 7.56e-2 * rd->unit.Joule / PetscPowScalarInt(rd->unit.meter, 3);
818       x[i].T = RDRadiationTemperature(rd, x[i].E);
819       break;
820     default:
821       SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "No initial state %" PetscInt_FMT, rd->initial);
822     }
823   }
824   PetscCall(DMDAVecRestoreArray(rd->da, X, &x));
825   PetscFunctionReturn(PETSC_SUCCESS);
826 }
827 
828 static PetscErrorCode RDView(RD rd, Vec X, PetscViewer viewer)
829 {
830   Vec             Y;
831   const RDNode   *x;
832   PetscScalar    *y;
833   PetscInt        i, m, M;
834   const PetscInt *lx;
835   DM              da;
836   MPI_Comm        comm;
837 
838   PetscFunctionBeginUser;
839   /*
840     Create a DMDA (one dof per node, zero stencil width, same layout) to hold Trad
841     (radiation temperature).  It is not necessary to create a DMDA for this, but this way
842     output and visualization will have meaningful variable names and correct scales.
843   */
844   PetscCall(DMDAGetInfo(rd->da, 0, &M, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0));
845   PetscCall(DMDAGetOwnershipRanges(rd->da, &lx, 0, 0));
846   PetscCall(PetscObjectGetComm((PetscObject)rd->da, &comm));
847   PetscCall(DMDACreate1d(comm, DM_BOUNDARY_NONE, M, 1, 0, lx, &da));
848   PetscCall(DMSetFromOptions(da));
849   PetscCall(DMSetUp(da));
850   PetscCall(DMDASetUniformCoordinates(da, 0., rd->L, 0., 0., 0., 0.));
851   PetscCall(DMDASetFieldName(da, 0, "T_rad"));
852   PetscCall(DMCreateGlobalVector(da, &Y));
853 
854   /* Compute the radiation temperature from the solution at each node */
855   PetscCall(VecGetLocalSize(Y, &m));
856   PetscCall(VecGetArrayRead(X, (const PetscScalar **)&x));
857   PetscCall(VecGetArray(Y, &y));
858   for (i = 0; i < m; i++) y[i] = RDRadiationTemperature(rd, x[i].E);
859   PetscCall(VecRestoreArrayRead(X, (const PetscScalar **)&x));
860   PetscCall(VecRestoreArray(Y, &y));
861 
862   PetscCall(VecView(Y, viewer));
863   PetscCall(VecDestroy(&Y));
864   PetscCall(DMDestroy(&da));
865   PetscFunctionReturn(PETSC_SUCCESS);
866 }
867 
868 static PetscErrorCode RDTestDifferentiation(RD rd)
869 {
870   MPI_Comm    comm;
871   RDNode      n, nx;
872   PetscScalar epsilon;
873 
874   PetscFunctionBeginUser;
875   PetscCall(PetscObjectGetComm((PetscObject)rd->da, &comm));
876   epsilon = 1e-8;
877   {
878     RDNode      dEm, fdEm;
879     PetscScalar T0 = 1000., T1 = T0 * (1. + epsilon), Em0, Em1;
880     n.E = 1.;
881     n.T = T0;
882     rd->MaterialEnergy(rd, &n, &Em0, &dEm);
883     n.E = 1. + epsilon;
884     n.T = T0;
885     rd->MaterialEnergy(rd, &n, &Em1, 0);
886     fdEm.E = (Em1 - Em0) / epsilon;
887     n.E    = 1.;
888     n.T    = T1;
889     rd->MaterialEnergy(rd, &n, &Em1, 0);
890     fdEm.T = (Em1 - Em0) / (T0 * epsilon);
891     PetscCall(PetscPrintf(comm, "dEm {%g,%g}, fdEm {%g,%g}, diff {%g,%g}\n", (double)PetscRealPart(dEm.E), (double)PetscRealPart(dEm.T), (double)PetscRealPart(fdEm.E), (double)PetscRealPart(fdEm.T), (double)PetscRealPart(dEm.E - fdEm.E),
892                           (double)PetscRealPart(dEm.T - fdEm.T)));
893   }
894   {
895     PetscScalar D0, D;
896     RDNode      dD, dxD, fdD, fdxD;
897     n.E  = 1.;
898     n.T  = 1.;
899     nx.E = 1.;
900     n.T  = 1.;
901     RDDiffusionCoefficient(rd, rd->bclimit, &n, &nx, &D0, &dD, &dxD);
902     n.E  = 1. + epsilon;
903     n.T  = 1.;
904     nx.E = 1.;
905     n.T  = 1.;
906     RDDiffusionCoefficient(rd, rd->bclimit, &n, &nx, &D, 0, 0);
907     fdD.E = (D - D0) / epsilon;
908     n.E   = 1;
909     n.T   = 1. + epsilon;
910     nx.E  = 1.;
911     n.T   = 1.;
912     RDDiffusionCoefficient(rd, rd->bclimit, &n, &nx, &D, 0, 0);
913     fdD.T = (D - D0) / epsilon;
914     n.E   = 1;
915     n.T   = 1.;
916     nx.E  = 1. + epsilon;
917     n.T   = 1.;
918     RDDiffusionCoefficient(rd, rd->bclimit, &n, &nx, &D, 0, 0);
919     fdxD.E = (D - D0) / epsilon;
920     n.E    = 1;
921     n.T    = 1.;
922     nx.E   = 1.;
923     n.T    = 1. + epsilon;
924     RDDiffusionCoefficient(rd, rd->bclimit, &n, &nx, &D, 0, 0);
925     fdxD.T = (D - D0) / epsilon;
926     PetscCall(PetscPrintf(comm, "dD {%g,%g}, fdD {%g,%g}, diff {%g,%g}\n", (double)PetscRealPart(dD.E), (double)PetscRealPart(dD.T), (double)PetscRealPart(fdD.E), (double)PetscRealPart(fdD.T), (double)PetscRealPart(dD.E - fdD.E),
927                           (double)PetscRealPart(dD.T - fdD.T)));
928     PetscCall(PetscPrintf(comm, "dxD {%g,%g}, fdxD {%g,%g}, diffx {%g,%g}\n", (double)PetscRealPart(dxD.E), (double)PetscRealPart(dxD.T), (double)PetscRealPart(fdxD.E), (double)PetscRealPart(fdxD.T), (double)PetscRealPart(dxD.E - fdxD.E),
929                           (double)PetscRealPart(dxD.T - fdxD.T)));
930   }
931   {
932     PetscInt    i;
933     PetscReal   hx = 1.;
934     PetscScalar a0;
935     RDNode      n0[3], n1[3], d[3], fd[3];
936 
937     n0[0].E = 1.;
938     n0[0].T = 1.;
939     n0[1].E = 5.;
940     n0[1].T = 3.;
941     n0[2].E = 4.;
942     n0[2].T = 2.;
943     a0      = RDDiffusion(rd, hx, n0, 1, d);
944     for (i = 0; i < 3; i++) {
945       PetscCall(PetscMemcpy(n1, n0, sizeof(n0)));
946       n1[i].E += epsilon;
947       fd[i].E = (RDDiffusion(rd, hx, n1, 1, 0) - a0) / epsilon;
948       PetscCall(PetscMemcpy(n1, n0, sizeof(n0)));
949       n1[i].T += epsilon;
950       fd[i].T = (RDDiffusion(rd, hx, n1, 1, 0) - a0) / epsilon;
951       PetscCall(PetscPrintf(comm, "ddiff[%" PetscInt_FMT "] {%g,%g}, fd {%g %g}, diff {%g,%g}\n", i, (double)PetscRealPart(d[i].E), (double)PetscRealPart(d[i].T), (double)PetscRealPart(fd[i].E), (double)PetscRealPart(fd[i].T),
952                             (double)PetscRealPart(d[i].E - fd[i].E), (double)PetscRealPart(d[i].T - fd[i].T)));
953     }
954   }
955   {
956     PetscScalar rad0, rad;
957     RDNode      drad, fdrad;
958     n.E     = 1.;
959     n.T     = 1.;
960     rad0    = RDRadiation(rd, &n, &drad);
961     n.E     = 1. + epsilon;
962     n.T     = 1.;
963     rad     = RDRadiation(rd, &n, 0);
964     fdrad.E = (rad - rad0) / epsilon;
965     n.E     = 1.;
966     n.T     = 1. + epsilon;
967     rad     = RDRadiation(rd, &n, 0);
968     fdrad.T = (rad - rad0) / epsilon;
969     PetscCall(PetscPrintf(comm, "drad {%g,%g}, fdrad {%g,%g}, diff {%g,%g}\n", (double)PetscRealPart(drad.E), (double)PetscRealPart(drad.T), (double)PetscRealPart(fdrad.E), (double)PetscRealPart(fdrad.T), (double)PetscRealPart(drad.E - drad.E),
970                           (double)PetscRealPart(drad.T - fdrad.T)));
971   }
972   PetscFunctionReturn(PETSC_SUCCESS);
973 }
974 
975 static PetscErrorCode RDCreate(MPI_Comm comm, RD *inrd)
976 {
977   RD        rd;
978   PetscReal meter = 0, kilogram = 0, second = 0, Kelvin = 0, Joule = 0, Watt = 0;
979 
980   PetscFunctionBeginUser;
981   *inrd = 0;
982   PetscCall(PetscNew(&rd));
983 
984   PetscOptionsBegin(comm, NULL, "Options for nonequilibrium radiation-diffusion with RD ionization", NULL);
985   {
986     rd->initial = 1;
987     PetscCall(PetscOptionsInt("-rd_initial", "Initial condition (1=Marshak, 2=Blast, 3=Marshak+)", "", rd->initial, &rd->initial, 0));
988     switch (rd->initial) {
989     case 1:
990     case 2:
991       rd->unit.kilogram = 1.;
992       rd->unit.meter    = 1.;
993       rd->unit.second   = 1.;
994       rd->unit.Kelvin   = 1.;
995       break;
996     case 3:
997       rd->unit.kilogram = 1.e12;
998       rd->unit.meter    = 1.;
999       rd->unit.second   = 1.e9;
1000       rd->unit.Kelvin   = 1.;
1001       break;
1002     default:
1003       SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Unknown initial condition %" PetscInt_FMT, rd->initial);
1004     }
1005     /* Fundamental units */
1006     PetscCall(PetscOptionsReal("-rd_unit_meter", "Length of 1 meter in nondimensional units", "", rd->unit.meter, &rd->unit.meter, 0));
1007     PetscCall(PetscOptionsReal("-rd_unit_kilogram", "Mass of 1 kilogram in nondimensional units", "", rd->unit.kilogram, &rd->unit.kilogram, 0));
1008     PetscCall(PetscOptionsReal("-rd_unit_second", "Time of a second in nondimensional units", "", rd->unit.second, &rd->unit.second, 0));
1009     PetscCall(PetscOptionsReal("-rd_unit_Kelvin", "Temperature of a Kelvin in nondimensional units", "", rd->unit.Kelvin, &rd->unit.Kelvin, 0));
1010     /* Derived units */
1011     rd->unit.Joule = rd->unit.kilogram * PetscSqr(rd->unit.meter / rd->unit.second);
1012     rd->unit.Watt  = rd->unit.Joule / rd->unit.second;
1013     /* Local aliases */
1014     meter    = rd->unit.meter;
1015     kilogram = rd->unit.kilogram;
1016     second   = rd->unit.second;
1017     Kelvin   = rd->unit.Kelvin;
1018     Joule    = rd->unit.Joule;
1019     Watt     = rd->unit.Watt;
1020 
1021     PetscCall(PetscOptionsBool("-rd_monitor_residual", "Display residuals every time they are evaluated", "", rd->monitor_residual, &rd->monitor_residual, NULL));
1022     PetscCall(PetscOptionsEnum("-rd_discretization", "Discretization type", "", DiscretizationTypes, (PetscEnum)rd->discretization, (PetscEnum *)&rd->discretization, NULL));
1023     if (rd->discretization == DISCRETIZATION_FE) {
1024       rd->quadrature = QUADRATURE_GAUSS2;
1025       PetscCall(PetscOptionsEnum("-rd_quadrature", "Finite element quadrature", "", QuadratureTypes, (PetscEnum)rd->quadrature, (PetscEnum *)&rd->quadrature, NULL));
1026     }
1027     PetscCall(PetscOptionsEnum("-rd_jacobian", "Type of finite difference Jacobian", "", JacobianTypes, (PetscEnum)rd->jacobian, (PetscEnum *)&rd->jacobian, NULL));
1028     switch (rd->initial) {
1029     case 1:
1030       rd->leftbc     = BC_ROBIN;
1031       rd->Eapplied   = 4 * rd->unit.Joule / PetscPowRealInt(rd->unit.meter, 3);
1032       rd->L          = 1. * rd->unit.meter;
1033       rd->beta       = 3.0;
1034       rd->gamma      = 3.0;
1035       rd->final_time = 3 * second;
1036       break;
1037     case 2:
1038       rd->leftbc     = BC_NEUMANN;
1039       rd->Eapplied   = 0.;
1040       rd->L          = 1. * rd->unit.meter;
1041       rd->beta       = 3.0;
1042       rd->gamma      = 3.0;
1043       rd->final_time = 1 * second;
1044       break;
1045     case 3:
1046       rd->leftbc     = BC_ROBIN;
1047       rd->Eapplied   = 7.503e6 * rd->unit.Joule / PetscPowRealInt(rd->unit.meter, 3);
1048       rd->L          = 5. * rd->unit.meter;
1049       rd->beta       = 3.5;
1050       rd->gamma      = 3.5;
1051       rd->final_time = 20e-9 * second;
1052       break;
1053     default:
1054       SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Initial %" PetscInt_FMT, rd->initial);
1055     }
1056     PetscCall(PetscOptionsEnum("-rd_leftbc", "Left boundary condition", "", BCTypes, (PetscEnum)rd->leftbc, (PetscEnum *)&rd->leftbc, NULL));
1057     PetscCall(PetscOptionsReal("-rd_E_applied", "Radiation flux at left end of domain", "", rd->Eapplied, &rd->Eapplied, NULL));
1058     PetscCall(PetscOptionsReal("-rd_beta", "Thermal exponent for photon absorption", "", rd->beta, &rd->beta, NULL));
1059     PetscCall(PetscOptionsReal("-rd_gamma", "Thermal exponent for diffusion coefficient", "", rd->gamma, &rd->gamma, NULL));
1060     PetscCall(PetscOptionsBool("-rd_view_draw", "Draw final solution", "", rd->view_draw, &rd->view_draw, NULL));
1061     PetscCall(PetscOptionsBool("-rd_endpoint", "Discretize using endpoints (like trapezoid rule) instead of midpoint", "", rd->endpoint, &rd->endpoint, NULL));
1062     PetscCall(PetscOptionsBool("-rd_bcmidpoint", "Impose the boundary condition at the midpoint (Theta) of the interval", "", rd->bcmidpoint, &rd->bcmidpoint, NULL));
1063     PetscCall(PetscOptionsBool("-rd_bclimit", "Limit diffusion coefficient in definition of Robin boundary condition", "", rd->bclimit, &rd->bclimit, NULL));
1064     PetscCall(PetscOptionsBool("-rd_test_diff", "Test differentiation in constitutive relations", "", rd->test_diff, &rd->test_diff, NULL));
1065     PetscCall(PetscOptionsString("-rd_view_binary", "File name to hold final solution", "", rd->view_binary, rd->view_binary, sizeof(rd->view_binary), NULL));
1066   }
1067   PetscOptionsEnd();
1068 
1069   switch (rd->initial) {
1070   case 1:
1071   case 2:
1072     rd->rho            = 1.;
1073     rd->c              = 1.;
1074     rd->K_R            = 1.;
1075     rd->K_p            = 1.;
1076     rd->sigma_b        = 0.25;
1077     rd->MaterialEnergy = RDMaterialEnergy_Reduced;
1078     break;
1079   case 3:
1080     /* Table 2 */
1081     rd->rho            = 1.17e-3 * kilogram / (meter * meter * meter); /* density */
1082     rd->K_R            = 7.44e18 * PetscPowRealInt(meter, 5) * PetscPowReal(Kelvin, 3.5) * PetscPowRealInt(kilogram, -2);
1083     rd->K_p            = 2.33e20 * PetscPowRealInt(meter, 5) * PetscPowReal(Kelvin, 3.5) * PetscPowRealInt(kilogram, -2);
1084     rd->I_H            = 2.179e-18 * Joule;                                                         /* Hydrogen ionization potential */
1085     rd->m_p            = 1.673e-27 * kilogram;                                                      /* proton mass */
1086     rd->m_e            = 9.109e-31 * kilogram;                                                      /* electron mass */
1087     rd->h              = 6.626e-34 * Joule * second;                                                /* Planck's constant */
1088     rd->k              = 1.381e-23 * Joule / Kelvin;                                                /* Boltzman constant */
1089     rd->c              = 3.00e8 * meter / second;                                                   /* speed of light */
1090     rd->sigma_b        = 5.67e-8 * Watt * PetscPowRealInt(meter, -2) * PetscPowRealInt(Kelvin, -4); /* Stefan-Boltzman constant */
1091     rd->MaterialEnergy = RDMaterialEnergy_Saha;
1092     break;
1093   }
1094 
1095   PetscCall(DMDACreate1d(comm, DM_BOUNDARY_NONE, 20, sizeof(RDNode) / sizeof(PetscScalar), 1, NULL, &rd->da));
1096   PetscCall(DMSetFromOptions(rd->da));
1097   PetscCall(DMSetUp(rd->da));
1098   PetscCall(DMDASetFieldName(rd->da, 0, "E"));
1099   PetscCall(DMDASetFieldName(rd->da, 1, "T"));
1100   PetscCall(DMDASetUniformCoordinates(rd->da, 0., 1., 0., 0., 0., 0.));
1101 
1102   *inrd = rd;
1103   PetscFunctionReturn(PETSC_SUCCESS);
1104 }
1105 
1106 int main(int argc, char *argv[])
1107 {
1108   RD        rd;
1109   TS        ts;
1110   SNES      snes;
1111   Vec       X;
1112   Mat       A, B;
1113   PetscInt  steps;
1114   PetscReal ftime;
1115 
1116   PetscFunctionBeginUser;
1117   PetscCall(PetscInitialize(&argc, &argv, 0, NULL));
1118   PetscCall(RDCreate(PETSC_COMM_WORLD, &rd));
1119   PetscCall(DMCreateGlobalVector(rd->da, &X));
1120   PetscCall(DMSetMatType(rd->da, MATAIJ));
1121   PetscCall(DMCreateMatrix(rd->da, &B));
1122   PetscCall(RDInitialState(rd, X));
1123 
1124   PetscCall(TSCreate(PETSC_COMM_WORLD, &ts));
1125   PetscCall(TSSetProblemType(ts, TS_NONLINEAR));
1126   PetscCall(TSSetType(ts, TSTHETA));
1127   PetscCall(TSSetDM(ts, rd->da));
1128   switch (rd->discretization) {
1129   case DISCRETIZATION_FD:
1130     PetscCall(TSSetIFunction(ts, NULL, RDIFunction_FD, rd));
1131     if (rd->jacobian == JACOBIAN_ANALYTIC) PetscCall(TSSetIJacobian(ts, B, B, RDIJacobian_FD, rd));
1132     break;
1133   case DISCRETIZATION_FE:
1134     PetscCall(TSSetIFunction(ts, NULL, RDIFunction_FE, rd));
1135     if (rd->jacobian == JACOBIAN_ANALYTIC) PetscCall(TSSetIJacobian(ts, B, B, RDIJacobian_FE, rd));
1136     break;
1137   }
1138   PetscCall(TSSetMaxTime(ts, rd->final_time));
1139   PetscCall(TSSetTimeStep(ts, 1e-3));
1140   PetscCall(TSSetExactFinalTime(ts, TS_EXACTFINALTIME_STEPOVER));
1141   PetscCall(TSSetFromOptions(ts));
1142 
1143   A = B;
1144   PetscCall(TSGetSNES(ts, &snes));
1145   switch (rd->jacobian) {
1146   case JACOBIAN_ANALYTIC:
1147     break;
1148   case JACOBIAN_MATRIXFREE:
1149     break;
1150   case JACOBIAN_FD_COLORING: {
1151     PetscCall(SNESSetJacobian(snes, A, B, SNESComputeJacobianDefaultColor, 0));
1152   } break;
1153   case JACOBIAN_FD_FULL:
1154     PetscCall(SNESSetJacobian(snes, A, B, SNESComputeJacobianDefault, ts));
1155     break;
1156   }
1157 
1158   if (rd->test_diff) PetscCall(RDTestDifferentiation(rd));
1159   PetscCall(TSSolve(ts, X));
1160   PetscCall(TSGetSolveTime(ts, &ftime));
1161   PetscCall(TSGetStepNumber(ts, &steps));
1162   PetscCall(PetscPrintf(PETSC_COMM_WORLD, "Steps %" PetscInt_FMT "  final time %g\n", steps, (double)ftime));
1163   if (rd->view_draw) PetscCall(RDView(rd, X, PETSC_VIEWER_DRAW_WORLD));
1164   if (rd->view_binary[0]) {
1165     PetscViewer viewer;
1166     PetscCall(PetscViewerBinaryOpen(PETSC_COMM_WORLD, rd->view_binary, FILE_MODE_WRITE, &viewer));
1167     PetscCall(RDView(rd, X, viewer));
1168     PetscCall(PetscViewerDestroy(&viewer));
1169   }
1170   PetscCall(VecDestroy(&X));
1171   PetscCall(MatDestroy(&B));
1172   PetscCall(RDDestroy(&rd));
1173   PetscCall(TSDestroy(&ts));
1174   PetscCall(PetscFinalize());
1175   return 0;
1176 }
1177 /*TEST
1178 
1179     test:
1180       args: -da_grid_x 20 -rd_initial 1 -rd_discretization fd -rd_jacobian fd_coloring -rd_endpoint -ts_max_time 1 -ts_dt 2e-1 -ts_theta_initial_guess_extrapolate 0 -ts_monitor -snes_monitor_short -ksp_monitor_short
1181       requires: !single
1182 
1183     test:
1184       suffix: 2
1185       args: -da_grid_x 20 -rd_initial 1 -rd_discretization fe -rd_quadrature lobatto2 -rd_jacobian fd_coloring -rd_endpoint -ts_max_time 1 -ts_dt 2e-1 -ts_theta_initial_guess_extrapolate 0 -ts_monitor -snes_monitor_short -ksp_monitor_short
1186       requires: !single
1187 
1188     test:
1189       suffix: 3
1190       nsize: 2
1191       args: -da_grid_x 20 -rd_initial 1 -rd_discretization fd -rd_jacobian analytic -rd_endpoint -ts_max_time 3 -ts_dt 1e-1 -ts_theta_initial_guess_extrapolate 0 -ts_monitor -snes_monitor_short -ksp_monitor_short
1192       requires: !single
1193 
1194 TEST*/
1195