xref: /petsc/src/ts/tests/ex27.c (revision 40badf4fbc550ac1f60bd080eaff6de6d55b946d)
1 static char help[] = "Particle Basis Landau Example using nonlinear solve + Implicit Midpoint-like time stepping.";
2 
3 /* TODO
4 
5 1) SNES is sensitive to epsilon (but not to h). Should we do continuation in it?
6 
7 2) Put this timestepper in library, maybe by changing DG
8 
9 3) Add monitor to visualize distributions
10 
11 */
12 
13 /* References
14   [1] https://arxiv.org/abs/1910.03080v2
15 */
16 
17 #include <petscdmplex.h>
18 #include <petscdmswarm.h>
19 #include <petscts.h>
20 #include <petscviewer.h>
21 #include <petscmath.h>
22 
23 typedef struct {
24   /* Velocity space grid and functions */
25   PetscInt  N;         /* The number of partices per spatial cell */
26   PetscReal L;         /* Velocity space is [-L, L]^d */
27   PetscReal h;         /* Spacing for grid 2L / N^{1/d} */
28   PetscReal epsilon;   /* gaussian regularization parameter */
29   PetscReal momentTol; /* Tolerance for checking moment conservation */
30 } AppCtx;
31 
32 static PetscErrorCode ProcessOptions(MPI_Comm comm, AppCtx *options)
33 {
34   PetscErrorCode ierr;
35 
36   PetscFunctionBeginUser;
37   options->N         = 1;
38   options->momentTol = 100.0*PETSC_MACHINE_EPSILON;
39   options->L         = 1.0;
40   options->h         = -1.0;
41   options->epsilon   = -1.0;
42 
43   ierr = PetscOptionsBegin(comm, "", "Collision Options", "DMPLEX");CHKERRQ(ierr);
44   CHKERRQ(PetscOptionsInt("-N", "Number of particles per spatial cell", "ex27.c", options->N, &options->N, NULL));
45   CHKERRQ(PetscOptionsReal("-L", "Velocity-space extent", "ex27.c", options->L, &options->L, NULL));
46   CHKERRQ(PetscOptionsReal("-h", "Velocity-space resolution", "ex27.c", options->h, &options->h, NULL));
47   CHKERRQ(PetscOptionsReal("-epsilon", "Mollifier regularization parameter", "ex27.c", options->epsilon, &options->epsilon, NULL));
48   ierr = PetscOptionsEnd();CHKERRQ(ierr);
49   PetscFunctionReturn(0);
50 }
51 
52 static PetscErrorCode CreateMesh(MPI_Comm comm, DM *dm, AppCtx *user)
53 {
54   PetscFunctionBeginUser;
55   CHKERRQ(DMCreate(comm, dm));
56   CHKERRQ(DMSetType(*dm, DMPLEX));
57   CHKERRQ(DMSetFromOptions(*dm));
58   CHKERRQ(DMViewFromOptions(*dm, NULL, "-dm_view"));
59   PetscFunctionReturn(0);
60 }
61 
62 static PetscErrorCode SetInitialCoordinates(DM sw)
63 {
64   AppCtx        *user;
65   PetscRandom    rnd, rndv;
66   DM             dm;
67   DMPolytopeType ct;
68   PetscBool      simplex;
69   PetscReal     *centroid, *coords, *velocity, *xi0, *v0, *J, *invJ, detJ, *vals;
70   PetscInt       dim, d, cStart, cEnd, c, Np, p;
71 
72   PetscFunctionBeginUser;
73   /* Randomization for coordinates */
74   CHKERRQ(PetscRandomCreate(PetscObjectComm((PetscObject) sw), &rnd));
75   CHKERRQ(PetscRandomSetInterval(rnd, -1.0, 1.0));
76   CHKERRQ(PetscRandomSetFromOptions(rnd));
77   CHKERRQ(PetscRandomCreate(PetscObjectComm((PetscObject) sw), &rndv));
78   CHKERRQ(PetscRandomSetInterval(rndv, -1., 1.));
79   CHKERRQ(PetscRandomSetFromOptions(rndv));
80   CHKERRQ(DMGetApplicationContext(sw, &user));
81   Np   = user->N;
82   CHKERRQ(DMGetDimension(sw, &dim));
83   CHKERRQ(DMSwarmGetCellDM(sw, &dm));
84   CHKERRQ(DMPlexGetHeightStratum(dm, 0, &cStart, &cEnd));
85   CHKERRQ(DMPlexGetCellType(dm, cStart, &ct));
86   simplex = DMPolytopeTypeGetNumVertices(ct) == DMPolytopeTypeGetDim(ct)+1 ? PETSC_TRUE : PETSC_FALSE;
87   CHKERRQ(PetscMalloc5(dim, &centroid, dim, &xi0, dim, &v0, dim*dim, &J, dim*dim, &invJ));
88   for (d = 0; d < dim; ++d) xi0[d] = -1.0;
89   CHKERRQ(DMSwarmGetField(sw, DMSwarmPICField_coor, NULL, NULL, (void **) &coords));
90   CHKERRQ(DMSwarmGetField(sw, "velocity", NULL, NULL, (void **) &velocity));
91   CHKERRQ(DMSwarmGetField(sw, "w_q", NULL, NULL, (void **) &vals));
92   for (c = cStart; c < cEnd; ++c) {
93     if (Np == 1) {
94       CHKERRQ(DMPlexComputeCellGeometryFVM(dm, c, NULL, centroid, NULL));
95       for (d = 0; d < dim; ++d) {
96         coords[c*dim+d] = centroid[d];
97       }
98       vals[c] = 1.0;
99     } else {
100       CHKERRQ(DMPlexComputeCellGeometryFEM(dm, c, NULL, v0, J, invJ, &detJ)); /* affine */
101       for (p = 0; p < Np; ++p) {
102         const PetscInt n   = c*Np + p;
103         PetscReal      sum = 0.0, refcoords[3];
104 
105         for (d = 0; d < dim; ++d) {
106           CHKERRQ(PetscRandomGetValueReal(rnd, &refcoords[d]));
107           sum += refcoords[d];
108         }
109         if (simplex && sum > 0.0) for (d = 0; d < dim; ++d) refcoords[d] -= PetscSqrtReal(dim)*sum;
110         vals[n] = 1.0;
111         CHKERRQ(DMPlexReferenceToCoordinates(dm, c, 1, refcoords, &coords[n*dim]));
112       }
113     }
114   }
115   /* Random velocity IC */
116   for (c = cStart; c < cEnd; ++c) {
117     for (p = 0; p < Np; ++p) {
118       for (d = 0; d < dim; ++d) {
119         PetscReal v_val;
120 
121         CHKERRQ(PetscRandomGetValueReal(rndv, &v_val));
122         velocity[p*dim+d] = v_val;
123       }
124     }
125   }
126   CHKERRQ(DMSwarmRestoreField(sw, DMSwarmPICField_coor, NULL, NULL, (void **) &coords));
127   CHKERRQ(DMSwarmRestoreField(sw, "velocity", NULL, NULL, (void **) &velocity));
128   CHKERRQ(DMSwarmRestoreField(sw, "w_q", NULL, NULL, (void **) &vals));
129   CHKERRQ(PetscFree5(centroid, xi0, v0, J, invJ));
130   CHKERRQ(PetscRandomDestroy(&rnd));
131   CHKERRQ(PetscRandomDestroy(&rndv));
132   PetscFunctionReturn(0);
133 }
134 
135 /* Get velocities from swarm and place in solution vector */
136 static PetscErrorCode SetInitialConditions(DM dmSw, Vec u)
137 {
138   DM             dm;
139   AppCtx        *user;
140   PetscReal     *velocity;
141   PetscScalar   *initialConditions;
142   PetscInt       dim, d, cStart, cEnd, c, Np, p, n;
143 
144   PetscFunctionBeginUser;
145   CHKERRQ(VecGetLocalSize(u, &n));
146   CHKERRQ(DMGetApplicationContext(dmSw, &user));
147   Np   = user->N;
148   CHKERRQ(DMSwarmGetCellDM(dmSw, &dm));
149   CHKERRQ(DMGetDimension(dm, &dim));
150   CHKERRQ(DMPlexGetHeightStratum(dm, 0, &cStart, &cEnd));
151   CHKERRQ(DMSwarmGetField(dmSw, "velocity", NULL, NULL, (void **) &velocity));
152   CHKERRQ(VecGetArray(u, &initialConditions));
153   for (c = cStart; c < cEnd; ++c) {
154     for (p = 0; p < Np; ++p) {
155       const PetscInt n = c*Np + p;
156       for (d = 0; d < dim; d++) {
157         initialConditions[n*dim+d] = velocity[n*dim+d];
158       }
159     }
160   }
161   CHKERRQ(VecRestoreArray(u, &initialConditions));
162   CHKERRQ(DMSwarmRestoreField(dmSw, "velocity", NULL, NULL, (void **) &velocity));
163   PetscFunctionReturn(0);
164 }
165 
166 static PetscErrorCode CreateParticles(DM dm, DM *sw, AppCtx *user)
167 {
168   PetscInt      *cellid;
169   PetscInt       dim, cStart, cEnd, c, Np = user->N, p;
170   PetscBool      view = PETSC_FALSE;
171 
172   PetscFunctionBeginUser;
173   CHKERRQ(DMGetDimension(dm, &dim));
174   CHKERRQ(DMCreate(PetscObjectComm((PetscObject) dm), sw));
175   CHKERRQ(DMSetType(*sw, DMSWARM));
176   CHKERRQ(DMSetDimension(*sw, dim));
177   /* h = 2L/n and N = n^d */
178   if (user->h < 0.) user->h = 2.*user->L / PetscPowReal(user->N, 1./dim);
179   /* From Section 4 in [1], \epsilon = 0.64 h^.98 */
180   if (user->epsilon < 0.) user->epsilon = 0.64*pow(user->h, 1.98);
181   CHKERRQ(PetscOptionsGetBool(NULL, NULL, "-param_view", &view, NULL));
182   if (view) {
183     CHKERRQ(PetscPrintf(PETSC_COMM_SELF, "N: %D L: %g h: %g eps: %g\n", user->N, user->L, user->h, user->epsilon));
184   }
185   CHKERRQ(DMSwarmSetType(*sw, DMSWARM_PIC));
186   CHKERRQ(DMSwarmSetCellDM(*sw, dm));
187   CHKERRQ(DMSwarmRegisterPetscDatatypeField(*sw, "velocity", dim, PETSC_REAL));
188   CHKERRQ(DMSwarmRegisterPetscDatatypeField(*sw, "w_q", 1, PETSC_SCALAR));
189   CHKERRQ(DMSwarmFinalizeFieldRegister(*sw));
190   CHKERRQ(DMPlexGetHeightStratum(dm, 0, &cStart, &cEnd));
191   CHKERRQ(DMSwarmSetLocalSizes(*sw, (cEnd - cStart) * Np, 0));
192   CHKERRQ(DMSetFromOptions(*sw));
193   CHKERRQ(DMSwarmGetField(*sw, DMSwarmPICField_cellid, NULL, NULL, (void **) &cellid));
194   for (c = cStart; c < cEnd; ++c) {
195     for (p = 0; p < Np; ++p) {
196       const PetscInt n = c*Np + p;
197       cellid[n] = c;
198     }
199   }
200   CHKERRQ(DMSwarmRestoreField(*sw, DMSwarmPICField_cellid, NULL, NULL, (void **) &cellid));
201   CHKERRQ(PetscObjectSetName((PetscObject) *sw, "Particles"));
202   CHKERRQ(DMViewFromOptions(*sw, NULL, "-sw_view"));
203   PetscFunctionReturn(0);
204 }
205 
206 /* Internal dmplex function, same as found in dmpleximpl.h */
207 static void DMPlex_WaxpyD_Internal(PetscInt dim, PetscReal a, const PetscReal *x, const PetscReal *y, PetscReal *w)
208 {
209   PetscInt d;
210 
211   for (d = 0; d < dim; ++d) w[d] = a*x[d] + y[d];
212 }
213 
214 /* Internal dmplex function, same as found in dmpleximpl.h */
215 static PetscReal DMPlex_DotD_Internal(PetscInt dim, const PetscScalar *x, const PetscReal *y)
216 {
217   PetscReal sum = 0.0;
218   PetscInt d;
219 
220   for (d = 0; d < dim; ++d) sum += PetscRealPart(x[d])*y[d];
221   return sum;
222 }
223 
224 /* Internal dmplex function, same as found in dmpleximpl.h */
225 static void DMPlex_MultAdd2DReal_Internal(const PetscReal A[], PetscInt ldx, const PetscScalar x[], PetscScalar y[])
226 {
227   PetscScalar z[2];
228   z[0] = x[0]; z[1] = x[ldx];
229   y[0]   += A[0]*z[0] + A[1]*z[1];
230   y[ldx] += A[2]*z[0] + A[3]*z[1];
231   (void)PetscLogFlops(6.0);
232 }
233 
234 /* Internal dmplex function, same as found in dmpleximpl.h to avoid private includes. */
235 static void DMPlex_MultAdd3DReal_Internal(const PetscReal A[], PetscInt ldx, const PetscScalar x[], PetscScalar y[])
236 {
237   PetscScalar z[3];
238   z[0] = x[0]; z[1] = x[ldx]; z[2] = x[ldx*2];
239   y[0]     += A[0]*z[0] + A[1]*z[1] + A[2]*z[2];
240   y[ldx]   += A[3]*z[0] + A[4]*z[1] + A[5]*z[2];
241   y[ldx*2] += A[6]*z[0] + A[7]*z[1] + A[8]*z[2];
242   (void)PetscLogFlops(15.0);
243 }
244 
245 /*
246   Gaussian - The Gaussian function G(x)
247 
248   Input Parameters:
249 +  dim   - The number of dimensions, or size of x
250 .  mu    - The mean, or center
251 .  sigma - The standard deviation, or width
252 -  x     - The evaluation point of the function
253 
254   Output Parameter:
255 . ret - The value G(x)
256 */
257 static PetscReal Gaussian(PetscInt dim, const PetscReal mu[], PetscReal sigma, const PetscReal x[])
258 {
259   PetscReal arg = 0.0;
260   PetscInt  d;
261 
262   for (d = 0; d < dim; ++d) arg += PetscSqr(x[d] - mu[d]);
263   return PetscPowReal(2.0*PETSC_PI*sigma, -dim/2.0) * PetscExpReal(-arg/(2.0*sigma));
264 }
265 
266 /*
267   ComputeGradS - Compute grad_v dS_eps/df
268 
269   Input Parameters:
270 + dim      - The dimension
271 . Np       - The number of particles
272 . vp       - The velocity v_p of the particle at which we evaluate
273 . velocity - The velocity field for all particles
274 . epsilon  - The regularization strength
275 
276   Output Parameter:
277 . integral - The output grad_v dS_eps/df (v_p)
278 
279   Note:
280   This comes from (3.6) in [1], and we are computing
281 $   \nabla_v S_p = \grad \psi_\epsilon(v_p - v) log \sum_q \psi_\epsilon(v - v_q)
282   which is discretized by using a one-point quadrature in each box l at its center v^c_l
283 $   \sum_l h^d \nabla\psi_\epsilon(v_p - v^c_l) \log\left( \sum_q w_q \psi_\epsilon(v^c_l - v_q) \right)
284   where h^d is the volume of each box.
285 */
286 static PetscErrorCode ComputeGradS(PetscInt dim, PetscInt Np, const PetscReal vp[], const PetscReal velocity[], PetscReal integral[], AppCtx *ctx)
287 {
288   PetscReal vc_l[3], L = ctx->L, h = ctx->h, epsilon = ctx->epsilon, init = 0.5*h - L;
289   PetscInt  nx = roundf(2.*L / h);
290   PetscInt  ny = dim > 1 ? nx : 1;
291   PetscInt  nz = dim > 2 ? nx : 1;
292   PetscInt  i, j, k, d, q, dbg = 0;
293 
294   PetscFunctionBeginHot;
295   for (d = 0; d < dim; ++d) integral[d] = 0.0;
296   for (k = 0, vc_l[2] = init; k < nz; ++k, vc_l[2] += h) {
297     for (j = 0, vc_l[1] = init; j < ny; ++j, vc_l[1] += h) {
298       for (i = 0, vc_l[0] = init; i < nx; ++i, vc_l[0] += h) {
299         PetscReal sum = 0.0;
300 
301         if (dbg) CHKERRQ(PetscPrintf(PETSC_COMM_SELF, "(%D %D) vc_l: %g %g\n", i, j, vc_l[0], vc_l[1]));
302         /* \log \sum_k \psi(v - v_k)  */
303         for (q = 0; q < Np; ++q) sum += Gaussian(dim, &velocity[q*dim], epsilon, vc_l);
304         sum = PetscLogReal(sum);
305         for (d = 0; d < dim; ++d) integral[d] += (-1./(epsilon))*PetscAbsReal(vp[d] - vc_l[d])*(Gaussian(dim, vp, epsilon, vc_l)) * sum;
306       }
307     }
308   }
309   PetscFunctionReturn(0);
310 }
311 
312 /* Q = 1/|xi| (I - xi xi^T / |xi|^2), xi = vp - vq */
313 static PetscErrorCode QCompute(PetscInt dim, const PetscReal vp[], const PetscReal vq[], PetscReal Q[])
314 {
315   PetscReal xi[3], xi2, xi3, mag;
316   PetscInt  d, e;
317 
318   PetscFunctionBeginHot;
319   DMPlex_WaxpyD_Internal(dim, -1.0, vq, vp, xi);
320   xi2 = DMPlex_DotD_Internal(dim, xi, xi);
321   mag = PetscSqrtReal(xi2);
322   xi3 = xi2 * mag;
323   for (d = 0; d < dim; ++d) {
324     for (e = 0; e < dim; ++e) {
325       Q[d*dim+e] = -xi[d]*xi[e] / xi3;
326     }
327     Q[d*dim+d] += 1. / mag;
328   }
329   PetscFunctionReturn(0);
330 }
331 
332 static PetscErrorCode RHSFunctionParticles(TS ts, PetscReal t, Vec U, Vec R, void *ctx)
333 {
334   AppCtx            *user = (AppCtx *) ctx;
335   PetscInt           dbg  = 0;
336   DM                 sw;                  /* Particles */
337   Vec                sol;                 /* Solution vector at current time */
338   const PetscScalar *u;                   /* input solution vector */
339   PetscScalar       *r;
340   PetscReal         *velocity;
341   PetscInt           dim, Np, p, q;
342 
343   PetscFunctionBeginUser;
344   CHKERRQ(VecZeroEntries(R));
345   CHKERRQ(TSGetDM(ts, &sw));
346   CHKERRQ(DMGetDimension(sw, &dim));
347   CHKERRQ(VecGetLocalSize(U, &Np));
348   CHKERRQ(TSGetSolution(ts, &sol));
349   CHKERRQ(VecGetArray(sol, &velocity));
350   CHKERRQ(VecGetArray(R, &r));
351   CHKERRQ(VecGetArrayRead(U, &u));
352   Np  /= dim;
353   if (dbg) CHKERRQ(PetscPrintf(PETSC_COMM_WORLD, "Part  ppr     x        y\n"));
354   for (p = 0; p < Np; ++p) {
355     PetscReal gradS_p[3] = {0., 0., 0.};
356 
357     CHKERRQ(ComputeGradS(dim, Np, &velocity[p*dim], velocity, gradS_p, user));
358     for (q = 0; q < Np; ++q) {
359       PetscReal gradS_q[3] = {0., 0., 0.}, GammaS[3] = {0., 0., 0.}, Q[9];
360 
361       if (q == p) continue;
362       CHKERRQ(ComputeGradS(dim, Np, &velocity[q*dim], velocity, gradS_q, user));
363       DMPlex_WaxpyD_Internal(dim, -1.0, gradS_q, gradS_p, GammaS);
364       CHKERRQ(QCompute(dim, &u[p*dim], &u[q*dim], Q));
365       switch (dim) {
366         case 2: DMPlex_MultAdd2DReal_Internal(Q, 1, GammaS, &r[p*dim]);break;
367         case 3: DMPlex_MultAdd3DReal_Internal(Q, 1, GammaS, &r[p*dim]);break;
368         default: SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Do not support dimension %D", dim);
369       }
370     }
371     if (dbg) CHKERRQ(PetscPrintf(PETSC_COMM_WORLD, "Final %4D %10.8lf %10.8lf\n", p, r[p*dim+0], r[p*dim+1]));
372   }
373   CHKERRQ(VecRestoreArrayRead(U, &u));
374   CHKERRQ(VecRestoreArray(R, &r));
375   CHKERRQ(VecRestoreArray(sol, &velocity));
376   CHKERRQ(VecViewFromOptions(R, NULL, "-residual_view"));
377   PetscFunctionReturn(0);
378 }
379 
380 /*
381  TS Post Step Function. Copy the solution back into the swarm for migration. We may also need to reform
382  the solution vector in cases of particle migration, but we forgo that here since there is no velocity space grid
383  to migrate between.
384 */
385 static PetscErrorCode UpdateSwarm(TS ts)
386 {
387   PetscInt idx, n;
388   const PetscScalar *u;
389   PetscScalar *velocity;
390   DM sw;
391   Vec sol;
392 
393   PetscFunctionBeginUser;
394   CHKERRQ(TSGetDM(ts, &sw));
395   CHKERRQ(DMSwarmGetField(sw, "velocity", NULL, NULL, (void **) &velocity));
396   CHKERRQ(TSGetSolution(ts, &sol));
397   CHKERRQ(VecGetArrayRead(sol, &u));
398   CHKERRQ(VecGetLocalSize(sol, &n));
399   for (idx = 0; idx < n; ++idx) velocity[idx] = u[idx];
400   CHKERRQ(VecRestoreArrayRead(sol, &u));
401   CHKERRQ(DMSwarmRestoreField(sw, "velocity", NULL, NULL, (void **) &velocity));
402   PetscFunctionReturn(0);
403 }
404 
405 static PetscErrorCode InitializeSolve(TS ts, Vec u)
406 {
407   DM             dm;
408   AppCtx        *user;
409 
410   PetscFunctionBeginUser;
411   CHKERRQ(TSGetDM(ts, &dm));
412   CHKERRQ(DMGetApplicationContext(dm, &user));
413   CHKERRQ(SetInitialCoordinates(dm));
414   CHKERRQ(SetInitialConditions(dm, u));
415   PetscFunctionReturn(0);
416 }
417 
418 int main(int argc,char **argv)
419 {
420   TS             ts;     /* nonlinear solver */
421   DM             dm, sw; /* Velocity space mesh and Particle Swarm */
422   Vec            u, v;   /* problem vector */
423   MPI_Comm       comm;
424   AppCtx         user;
425   PetscErrorCode ierr;
426 
427   ierr = PetscInitialize(&argc, &argv, NULL, help);if (ierr) return ierr;
428   comm = PETSC_COMM_WORLD;
429   CHKERRQ(ProcessOptions(comm, &user));
430   /* Initialize objects and set initial conditions */
431   CHKERRQ(CreateMesh(comm, &dm, &user));
432   CHKERRQ(CreateParticles(dm, &sw, &user));
433   CHKERRQ(DMSetApplicationContext(sw, &user));
434   CHKERRQ(DMSwarmVectorDefineField(sw, "velocity"));
435   CHKERRQ(TSCreate(comm, &ts));
436   CHKERRQ(TSSetDM(ts, sw));
437   CHKERRQ(TSSetMaxTime(ts, 10.0));
438   CHKERRQ(TSSetTimeStep(ts, 0.1));
439   CHKERRQ(TSSetMaxSteps(ts, 1));
440   CHKERRQ(TSSetExactFinalTime(ts, TS_EXACTFINALTIME_MATCHSTEP));
441   CHKERRQ(TSSetRHSFunction(ts, NULL, RHSFunctionParticles, &user));
442   CHKERRQ(TSSetFromOptions(ts));
443   CHKERRQ(TSSetComputeInitialCondition(ts, InitializeSolve));
444   CHKERRQ(DMSwarmCreateGlobalVectorFromField(sw, "velocity", &v));
445   CHKERRQ(VecDuplicate(v, &u));
446   CHKERRQ(DMSwarmDestroyGlobalVectorFromField(sw, "velocity", &v));
447   CHKERRQ(TSComputeInitialCondition(ts, u));
448   CHKERRQ(TSSetPostStep(ts, UpdateSwarm));
449   CHKERRQ(TSSolve(ts, u));
450   CHKERRQ(VecDestroy(&u));
451   CHKERRQ(TSDestroy(&ts));
452   CHKERRQ(DMDestroy(&sw));
453   CHKERRQ(DMDestroy(&dm));
454   ierr = PetscFinalize();
455   return ierr;
456 }
457 
458 /*TEST
459    build:
460      requires: triangle !single !complex
461    test:
462      suffix: midpoint
463      args: -N 3 -dm_plex_dim 2 -dm_plex_simplex 0 -dm_plex_box_faces 1,1 -dm_plex_box_lower -1,-1 -dm_plex_box_upper 1,1 -dm_view \
464            -ts_type theta -ts_theta_theta 0.5 -ts_dmswarm_monitor_moments -ts_monitor_frequency 1 -snes_fd
465 TEST*/
466