xref: /petsc/src/tao/unconstrained/tutorials/burgers_spectral.c (revision 517f4e3460cd8c7e73c19b8bf533f1efe47f30a7)
1 static char help[] = "Solves a simple data assimilation problem with one dimensional Burger's equation using TSAdjoint\n\n";
2 
3 /*
4 
5     Not yet tested in parallel
6 
7 */
8 
9 /* ------------------------------------------------------------------------
10 
11    This program uses the one-dimensional Burger's equation
12        u_t = mu*u_xx - u u_x,
13    on the domain 0 <= x <= 1, with periodic boundary conditions
14 
15    to demonstrate solving a data assimilation problem of finding the initial conditions
16    to produce a given solution at a fixed time.
17 
18    The operators are discretized with the spectral element method
19 
20    See the paper PDE-CONSTRAINED OPTIMIZATION WITH SPECTRAL ELEMENTS USING PETSC AND TAO
21    by OANA MARIN, EMIL CONSTANTINESCU, AND BARRY SMITH for details on the exact solution
22    used
23 
24   ------------------------------------------------------------------------- */
25 
26 #include <petsctao.h>
27 #include <petscts.h>
28 #include <petscdt.h>
29 #include <petscdraw.h>
30 #include <petscdmda.h>
31 
32 /*
33    User-defined application context - contains data needed by the
34    application-provided call-back routines.
35 */
36 
37 typedef struct {
38   PetscInt   n;       /* number of nodes */
39   PetscReal *nodes;   /* GLL nodes */
40   PetscReal *weights; /* GLL weights */
41 } PetscGLL;
42 
43 typedef struct {
44   PetscInt  N;               /* grid points per elements*/
45   PetscInt  E;               /* number of elements */
46   PetscReal tol_L2, tol_max; /* error norms */
47   PetscInt  steps;           /* number of timesteps */
48   PetscReal Tend;            /* endtime */
49   PetscReal mu;              /* viscosity */
50   PetscReal L;               /* total length of domain */
51   PetscReal Le;
52   PetscReal Tadj;
53 } PetscParam;
54 
55 typedef struct {
56   Vec obj;  /* desired end state */
57   Vec grid; /* total grid */
58   Vec grad;
59   Vec ic;
60   Vec curr_sol;
61   Vec true_solution; /* actual initial conditions for the final solution */
62 } PetscData;
63 
64 typedef struct {
65   Vec      grid;  /* total grid */
66   Vec      mass;  /* mass matrix for total integration */
67   Mat      stiff; /* stifness matrix */
68   Mat      keptstiff;
69   Mat      grad;
70   PetscGLL gll;
71 } PetscSEMOperators;
72 
73 typedef struct {
74   DM                da; /* distributed array data structure */
75   PetscSEMOperators SEMop;
76   PetscParam        param;
77   PetscData         dat;
78   TS                ts;
79   PetscReal         initial_dt;
80 } AppCtx;
81 
82 /*
83    User-defined routines
84 */
85 extern PetscErrorCode FormFunctionGradient(Tao, Vec, PetscReal *, Vec, void *);
86 extern PetscErrorCode RHSMatrixLaplaciangllDM(TS, PetscReal, Vec, Mat, Mat, void *);
87 extern PetscErrorCode RHSMatrixAdvectiongllDM(TS, PetscReal, Vec, Mat, Mat, void *);
88 extern PetscErrorCode InitialConditions(Vec, AppCtx *);
89 extern PetscErrorCode TrueSolution(Vec, AppCtx *);
90 extern PetscErrorCode ComputeObjective(PetscReal, Vec, AppCtx *);
91 extern PetscErrorCode MonitorError(Tao, void *);
92 extern PetscErrorCode RHSFunction(TS, PetscReal, Vec, Vec, void *);
93 extern PetscErrorCode RHSJacobian(TS, PetscReal, Vec, Mat, Mat, void *);
94 
95 int main(int argc, char **argv)
96 {
97   AppCtx       appctx; /* user-defined application context */
98   Tao          tao;
99   Vec          u; /* approximate solution vector */
100   PetscInt     i, xs, xm, ind, j, lenglob;
101   PetscReal    x, *wrk_ptr1, *wrk_ptr2;
102   MatNullSpace nsp;
103   PetscMPIInt  size;
104 
105   /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
106      Initialize program and set problem parameters
107      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
108   PetscFunctionBegin;
109 
110   PetscFunctionBeginUser;
111   PetscCall(PetscInitialize(&argc, &argv, (char *)0, help));
112 
113   /*initialize parameters */
114   appctx.param.N     = 10;   /* order of the spectral element */
115   appctx.param.E     = 10;   /* number of elements */
116   appctx.param.L     = 4.0;  /* length of the domain */
117   appctx.param.mu    = 0.01; /* diffusion coefficient */
118   appctx.initial_dt  = 5e-3;
119   appctx.param.steps = PETSC_MAX_INT;
120   appctx.param.Tend  = 4;
121 
122   PetscCall(PetscOptionsGetInt(NULL, NULL, "-N", &appctx.param.N, NULL));
123   PetscCall(PetscOptionsGetInt(NULL, NULL, "-E", &appctx.param.E, NULL));
124   PetscCall(PetscOptionsGetReal(NULL, NULL, "-Tend", &appctx.param.Tend, NULL));
125   PetscCall(PetscOptionsGetReal(NULL, NULL, "-mu", &appctx.param.mu, NULL));
126   appctx.param.Le = appctx.param.L / appctx.param.E;
127 
128   PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
129   PetscCheck((appctx.param.E % size) == 0, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG, "Number of elements must be divisible by number of processes");
130 
131   /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
132      Create GLL data structures
133      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
134   PetscCall(PetscMalloc2(appctx.param.N, &appctx.SEMop.gll.nodes, appctx.param.N, &appctx.SEMop.gll.weights));
135   PetscCall(PetscDTGaussLobattoLegendreQuadrature(appctx.param.N, PETSCGAUSSLOBATTOLEGENDRE_VIA_LINEAR_ALGEBRA, appctx.SEMop.gll.nodes, appctx.SEMop.gll.weights));
136   appctx.SEMop.gll.n = appctx.param.N;
137   lenglob            = appctx.param.E * (appctx.param.N - 1);
138 
139   /*
140      Create distributed array (DMDA) to manage parallel grid and vectors
141      and to set up the ghost point communication pattern.  There are E*(Nl-1)+1
142      total grid values spread equally among all the processors, except first and last
143   */
144 
145   PetscCall(DMDACreate1d(PETSC_COMM_WORLD, DM_BOUNDARY_PERIODIC, lenglob, 1, 1, NULL, &appctx.da));
146   PetscCall(DMSetFromOptions(appctx.da));
147   PetscCall(DMSetUp(appctx.da));
148 
149   /*
150      Extract global and local vectors from DMDA; we use these to store the
151      approximate solution.  Then duplicate these for remaining vectors that
152      have the same types.
153   */
154 
155   PetscCall(DMCreateGlobalVector(appctx.da, &u));
156   PetscCall(VecDuplicate(u, &appctx.dat.ic));
157   PetscCall(VecDuplicate(u, &appctx.dat.true_solution));
158   PetscCall(VecDuplicate(u, &appctx.dat.obj));
159   PetscCall(VecDuplicate(u, &appctx.SEMop.grid));
160   PetscCall(VecDuplicate(u, &appctx.SEMop.mass));
161   PetscCall(VecDuplicate(u, &appctx.dat.curr_sol));
162 
163   PetscCall(DMDAGetCorners(appctx.da, &xs, NULL, NULL, &xm, NULL, NULL));
164   PetscCall(DMDAVecGetArray(appctx.da, appctx.SEMop.grid, &wrk_ptr1));
165   PetscCall(DMDAVecGetArray(appctx.da, appctx.SEMop.mass, &wrk_ptr2));
166 
167   /* Compute function over the locally owned part of the grid */
168 
169   xs = xs / (appctx.param.N - 1);
170   xm = xm / (appctx.param.N - 1);
171 
172   /*
173      Build total grid and mass over entire mesh (multi-elemental)
174   */
175 
176   for (i = xs; i < xs + xm; i++) {
177     for (j = 0; j < appctx.param.N - 1; j++) {
178       x             = (appctx.param.Le / 2.0) * (appctx.SEMop.gll.nodes[j] + 1.0) + appctx.param.Le * i;
179       ind           = i * (appctx.param.N - 1) + j;
180       wrk_ptr1[ind] = x;
181       wrk_ptr2[ind] = .5 * appctx.param.Le * appctx.SEMop.gll.weights[j];
182       if (j == 0) wrk_ptr2[ind] += .5 * appctx.param.Le * appctx.SEMop.gll.weights[j];
183     }
184   }
185   PetscCall(DMDAVecRestoreArray(appctx.da, appctx.SEMop.grid, &wrk_ptr1));
186   PetscCall(DMDAVecRestoreArray(appctx.da, appctx.SEMop.mass, &wrk_ptr2));
187 
188   /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
189    Create matrix data structure; set matrix evaluation routine.
190    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
191   PetscCall(DMSetMatrixPreallocateOnly(appctx.da, PETSC_TRUE));
192   PetscCall(DMCreateMatrix(appctx.da, &appctx.SEMop.stiff));
193   PetscCall(DMCreateMatrix(appctx.da, &appctx.SEMop.grad));
194   /*
195    For linear problems with a time-dependent f(u,t) in the equation
196    u_t = f(u,t), the user provides the discretized right-hand-side
197    as a time-dependent matrix.
198    */
199   PetscCall(RHSMatrixLaplaciangllDM(appctx.ts, 0.0, u, appctx.SEMop.stiff, appctx.SEMop.stiff, &appctx));
200   PetscCall(RHSMatrixAdvectiongllDM(appctx.ts, 0.0, u, appctx.SEMop.grad, appctx.SEMop.grad, &appctx));
201   /*
202        For linear problems with a time-dependent f(u,t) in the equation
203        u_t = f(u,t), the user provides the discretized right-hand-side
204        as a time-dependent matrix.
205     */
206 
207   PetscCall(MatDuplicate(appctx.SEMop.stiff, MAT_COPY_VALUES, &appctx.SEMop.keptstiff));
208 
209   /* attach the null space to the matrix, this probably is not needed but does no harm */
210   PetscCall(MatNullSpaceCreate(PETSC_COMM_WORLD, PETSC_TRUE, 0, NULL, &nsp));
211   PetscCall(MatSetNullSpace(appctx.SEMop.stiff, nsp));
212   PetscCall(MatSetNullSpace(appctx.SEMop.keptstiff, nsp));
213   PetscCall(MatNullSpaceTest(nsp, appctx.SEMop.stiff, NULL));
214   PetscCall(MatNullSpaceDestroy(&nsp));
215   /* attach the null space to the matrix, this probably is not needed but does no harm */
216   PetscCall(MatNullSpaceCreate(PETSC_COMM_WORLD, PETSC_TRUE, 0, NULL, &nsp));
217   PetscCall(MatSetNullSpace(appctx.SEMop.grad, nsp));
218   PetscCall(MatNullSpaceTest(nsp, appctx.SEMop.grad, NULL));
219   PetscCall(MatNullSpaceDestroy(&nsp));
220 
221   /* Create the TS solver that solves the ODE and its adjoint; set its options */
222   PetscCall(TSCreate(PETSC_COMM_WORLD, &appctx.ts));
223   PetscCall(TSSetProblemType(appctx.ts, TS_NONLINEAR));
224   PetscCall(TSSetType(appctx.ts, TSRK));
225   PetscCall(TSSetDM(appctx.ts, appctx.da));
226   PetscCall(TSSetTime(appctx.ts, 0.0));
227   PetscCall(TSSetTimeStep(appctx.ts, appctx.initial_dt));
228   PetscCall(TSSetMaxSteps(appctx.ts, appctx.param.steps));
229   PetscCall(TSSetMaxTime(appctx.ts, appctx.param.Tend));
230   PetscCall(TSSetExactFinalTime(appctx.ts, TS_EXACTFINALTIME_MATCHSTEP));
231   PetscCall(TSSetTolerances(appctx.ts, 1e-7, NULL, 1e-7, NULL));
232   PetscCall(TSSetFromOptions(appctx.ts));
233   /* Need to save initial timestep user may have set with -ts_dt so it can be reset for each new TSSolve() */
234   PetscCall(TSGetTimeStep(appctx.ts, &appctx.initial_dt));
235   PetscCall(TSSetRHSFunction(appctx.ts, NULL, RHSFunction, &appctx));
236   PetscCall(TSSetRHSJacobian(appctx.ts, appctx.SEMop.stiff, appctx.SEMop.stiff, RHSJacobian, &appctx));
237 
238   /* Set Objective and Initial conditions for the problem and compute Objective function (evolution of true_solution to final time */
239   PetscCall(InitialConditions(appctx.dat.ic, &appctx));
240   PetscCall(TrueSolution(appctx.dat.true_solution, &appctx));
241   PetscCall(ComputeObjective(appctx.param.Tend, appctx.dat.obj, &appctx));
242 
243   PetscCall(TSSetSaveTrajectory(appctx.ts));
244   PetscCall(TSSetFromOptions(appctx.ts));
245 
246   /* Create TAO solver and set desired solution method  */
247   PetscCall(TaoCreate(PETSC_COMM_WORLD, &tao));
248   PetscCall(TaoSetMonitor(tao, MonitorError, &appctx, NULL));
249   PetscCall(TaoSetType(tao, TAOBQNLS));
250   PetscCall(TaoSetSolution(tao, appctx.dat.ic));
251   /* Set routine for function and gradient evaluation  */
252   PetscCall(TaoSetObjectiveAndGradient(tao, NULL, FormFunctionGradient, (void *)&appctx));
253   /* Check for any TAO command line options  */
254   PetscCall(TaoSetTolerances(tao, 1e-8, PETSC_DEFAULT, PETSC_DEFAULT));
255   PetscCall(TaoSetFromOptions(tao));
256   PetscCall(TaoSolve(tao));
257 
258   PetscCall(TaoDestroy(&tao));
259   PetscCall(MatDestroy(&appctx.SEMop.stiff));
260   PetscCall(MatDestroy(&appctx.SEMop.keptstiff));
261   PetscCall(MatDestroy(&appctx.SEMop.grad));
262   PetscCall(VecDestroy(&u));
263   PetscCall(VecDestroy(&appctx.dat.ic));
264   PetscCall(VecDestroy(&appctx.dat.true_solution));
265   PetscCall(VecDestroy(&appctx.dat.obj));
266   PetscCall(VecDestroy(&appctx.SEMop.grid));
267   PetscCall(VecDestroy(&appctx.SEMop.mass));
268   PetscCall(VecDestroy(&appctx.dat.curr_sol));
269   PetscCall(PetscFree2(appctx.SEMop.gll.nodes, appctx.SEMop.gll.weights));
270   PetscCall(DMDestroy(&appctx.da));
271   PetscCall(TSDestroy(&appctx.ts));
272 
273   /*
274      Always call PetscFinalize() before exiting a program.  This routine
275        - finalizes the PETSc libraries as well as MPI
276        - provides summary and diagnostic information if certain runtime
277          options are chosen (e.g., -log_view).
278   */
279   PetscCall(PetscFinalize());
280   return 0;
281 }
282 
283 /* --------------------------------------------------------------------- */
284 /*
285    InitialConditions - Computes the initial conditions for the Tao optimization solve (these are also initial conditions for the first TSSolve()
286 
287                        The routine TrueSolution() computes the true solution for the Tao optimization solve which means they are the initial conditions for the objective function
288 
289    Input Parameter:
290    u - uninitialized solution vector (global)
291    appctx - user-defined application context
292 
293    Output Parameter:
294    u - vector with solution at initial time (global)
295 */
296 PetscErrorCode InitialConditions(Vec u, AppCtx *appctx)
297 {
298   PetscScalar       *s;
299   const PetscScalar *xg;
300   PetscInt           i, xs, xn;
301 
302   PetscFunctionBegin;
303   PetscCall(DMDAVecGetArray(appctx->da, u, &s));
304   PetscCall(DMDAVecGetArrayRead(appctx->da, appctx->SEMop.grid, (void *)&xg));
305   PetscCall(DMDAGetCorners(appctx->da, &xs, NULL, NULL, &xn, NULL, NULL));
306   for (i = xs; i < xs + xn; i++) s[i] = 2.0 * appctx->param.mu * PETSC_PI * PetscSinScalar(PETSC_PI * xg[i]) / (2.0 + PetscCosScalar(PETSC_PI * xg[i])) + 0.25 * PetscExpReal(-4.0 * PetscPowReal(xg[i] - 2.0, 2.0));
307   PetscCall(DMDAVecRestoreArray(appctx->da, u, &s));
308   PetscCall(DMDAVecRestoreArrayRead(appctx->da, appctx->SEMop.grid, (void *)&xg));
309   PetscFunctionReturn(PETSC_SUCCESS);
310 }
311 
312 /*
313    TrueSolution() computes the true solution for the Tao optimization solve which means they are the initial conditions for the objective function.
314 
315              InitialConditions() computes the initial conditions for the beginning of the Tao iterations
316 
317    Input Parameter:
318    u - uninitialized solution vector (global)
319    appctx - user-defined application context
320 
321    Output Parameter:
322    u - vector with solution at initial time (global)
323 */
324 PetscErrorCode TrueSolution(Vec u, AppCtx *appctx)
325 {
326   PetscScalar       *s;
327   const PetscScalar *xg;
328   PetscInt           i, xs, xn;
329 
330   PetscFunctionBegin;
331   PetscCall(DMDAVecGetArray(appctx->da, u, &s));
332   PetscCall(DMDAVecGetArrayRead(appctx->da, appctx->SEMop.grid, (void *)&xg));
333   PetscCall(DMDAGetCorners(appctx->da, &xs, NULL, NULL, &xn, NULL, NULL));
334   for (i = xs; i < xs + xn; i++) s[i] = 2.0 * appctx->param.mu * PETSC_PI * PetscSinScalar(PETSC_PI * xg[i]) / (2.0 + PetscCosScalar(PETSC_PI * xg[i]));
335   PetscCall(DMDAVecRestoreArray(appctx->da, u, &s));
336   PetscCall(DMDAVecRestoreArrayRead(appctx->da, appctx->SEMop.grid, (void *)&xg));
337   PetscFunctionReturn(PETSC_SUCCESS);
338 }
339 /* --------------------------------------------------------------------- */
340 /*
341    Sets the desired profile for the final end time
342 
343    Input Parameters:
344    t - final time
345    obj - vector storing the desired profile
346    appctx - user-defined application context
347 
348 */
349 PetscErrorCode ComputeObjective(PetscReal t, Vec obj, AppCtx *appctx)
350 {
351   PetscScalar       *s;
352   const PetscScalar *xg;
353   PetscInt           i, xs, xn;
354 
355   PetscFunctionBegin;
356   PetscCall(DMDAVecGetArray(appctx->da, obj, &s));
357   PetscCall(DMDAVecGetArrayRead(appctx->da, appctx->SEMop.grid, (void *)&xg));
358   PetscCall(DMDAGetCorners(appctx->da, &xs, NULL, NULL, &xn, NULL, NULL));
359   for (i = xs; i < xs + xn; i++) {
360     s[i] = 2.0 * appctx->param.mu * PETSC_PI * PetscSinScalar(PETSC_PI * xg[i]) * PetscExpScalar(-PETSC_PI * PETSC_PI * t * appctx->param.mu) / (2.0 + PetscExpScalar(-PETSC_PI * PETSC_PI * t * appctx->param.mu) * PetscCosScalar(PETSC_PI * xg[i]));
361   }
362   PetscCall(DMDAVecRestoreArray(appctx->da, obj, &s));
363   PetscCall(DMDAVecRestoreArrayRead(appctx->da, appctx->SEMop.grid, (void *)&xg));
364   PetscFunctionReturn(PETSC_SUCCESS);
365 }
366 
367 PetscErrorCode RHSFunction(TS ts, PetscReal t, Vec globalin, Vec globalout, void *ctx)
368 {
369   AppCtx *appctx = (AppCtx *)ctx;
370 
371   PetscFunctionBegin;
372   PetscCall(MatMult(appctx->SEMop.grad, globalin, globalout)); /* grad u */
373   PetscCall(VecPointwiseMult(globalout, globalin, globalout)); /* u grad u */
374   PetscCall(VecScale(globalout, -1.0));
375   PetscCall(MatMultAdd(appctx->SEMop.keptstiff, globalin, globalout, globalout));
376   PetscFunctionReturn(PETSC_SUCCESS);
377 }
378 
379 /*
380 
381       K is the discretiziation of the Laplacian
382       G is the discretization of the gradient
383 
384       Computes Jacobian of      K u + diag(u) G u   which is given by
385               K   + diag(u)G + diag(Gu)
386 */
387 PetscErrorCode RHSJacobian(TS ts, PetscReal t, Vec globalin, Mat A, Mat B, void *ctx)
388 {
389   AppCtx *appctx = (AppCtx *)ctx;
390   Vec     Gglobalin;
391 
392   PetscFunctionBegin;
393   /*    A = diag(u) G */
394 
395   PetscCall(MatCopy(appctx->SEMop.grad, A, SAME_NONZERO_PATTERN));
396   PetscCall(MatDiagonalScale(A, globalin, NULL));
397 
398   /*    A  = A + diag(Gu) */
399   PetscCall(VecDuplicate(globalin, &Gglobalin));
400   PetscCall(MatMult(appctx->SEMop.grad, globalin, Gglobalin));
401   PetscCall(MatDiagonalSet(A, Gglobalin, ADD_VALUES));
402   PetscCall(VecDestroy(&Gglobalin));
403 
404   /*   A  = K - A    */
405   PetscCall(MatScale(A, -1.0));
406   PetscCall(MatAXPY(A, 1.0, appctx->SEMop.keptstiff, SAME_NONZERO_PATTERN));
407   PetscFunctionReturn(PETSC_SUCCESS);
408 }
409 
410 /* --------------------------------------------------------------------- */
411 
412 /*
413    RHSMatrixLaplacian - User-provided routine to compute the right-hand-side
414    matrix for the heat equation.
415 
416    Input Parameters:
417    ts - the TS context
418    t - current time  (ignored)
419    X - current solution (ignored)
420    dummy - optional user-defined context, as set by TSetRHSJacobian()
421 
422    Output Parameters:
423    AA - Jacobian matrix
424    BB - optionally different matrix from which the preconditioner is built
425    str - flag indicating matrix structure
426 
427 */
428 PetscErrorCode RHSMatrixLaplaciangllDM(TS ts, PetscReal t, Vec X, Mat A, Mat BB, void *ctx)
429 {
430   PetscReal **temp;
431   PetscReal   vv;
432   AppCtx     *appctx = (AppCtx *)ctx; /* user-defined application context */
433   PetscInt    i, xs, xn, l, j;
434   PetscInt   *rowsDM;
435 
436   PetscFunctionBegin;
437   /*
438    Creates the element stiffness matrix for the given gll
439    */
440   PetscCall(PetscGaussLobattoLegendreElementLaplacianCreate(appctx->SEMop.gll.n, appctx->SEMop.gll.nodes, appctx->SEMop.gll.weights, &temp));
441   /* workaround for clang analyzer warning: Division by zero */
442   PetscCheck(appctx->param.N > 1, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG, "Spectral element order should be > 1");
443 
444   /* scale by the size of the element */
445   for (i = 0; i < appctx->param.N; i++) {
446     vv = -appctx->param.mu * 2.0 / appctx->param.Le;
447     for (j = 0; j < appctx->param.N; j++) temp[i][j] = temp[i][j] * vv;
448   }
449 
450   PetscCall(MatSetOption(A, MAT_NEW_NONZERO_ALLOCATION_ERR, PETSC_FALSE));
451   PetscCall(DMDAGetCorners(appctx->da, &xs, NULL, NULL, &xn, NULL, NULL));
452 
453   xs = xs / (appctx->param.N - 1);
454   xn = xn / (appctx->param.N - 1);
455 
456   PetscCall(PetscMalloc1(appctx->param.N, &rowsDM));
457   /*
458    loop over local elements
459    */
460   for (j = xs; j < xs + xn; j++) {
461     for (l = 0; l < appctx->param.N; l++) rowsDM[l] = 1 + (j - xs) * (appctx->param.N - 1) + l;
462     PetscCall(MatSetValuesLocal(A, appctx->param.N, rowsDM, appctx->param.N, rowsDM, &temp[0][0], ADD_VALUES));
463   }
464   PetscCall(PetscFree(rowsDM));
465   PetscCall(MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY));
466   PetscCall(MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY));
467   PetscCall(VecReciprocal(appctx->SEMop.mass));
468   PetscCall(MatDiagonalScale(A, appctx->SEMop.mass, 0));
469   PetscCall(VecReciprocal(appctx->SEMop.mass));
470 
471   PetscCall(PetscGaussLobattoLegendreElementLaplacianDestroy(appctx->SEMop.gll.n, appctx->SEMop.gll.nodes, appctx->SEMop.gll.weights, &temp));
472   PetscFunctionReturn(PETSC_SUCCESS);
473 }
474 
475 /*
476    RHSMatrixAdvection - User-provided routine to compute the right-hand-side
477    matrix for the Advection equation.
478 
479    Input Parameters:
480    ts - the TS context
481    t - current time
482    global_in - global input vector
483    dummy - optional user-defined context, as set by TSetRHSJacobian()
484 
485    Output Parameters:
486    AA - Jacobian matrix
487    BB - optionally different preconditioning matrix
488    str - flag indicating matrix structure
489 
490 */
491 PetscErrorCode RHSMatrixAdvectiongllDM(TS ts, PetscReal t, Vec X, Mat A, Mat BB, void *ctx)
492 {
493   PetscReal **temp;
494   AppCtx     *appctx = (AppCtx *)ctx; /* user-defined application context */
495   PetscInt    xs, xn, l, j;
496   PetscInt   *rowsDM;
497 
498   PetscFunctionBegin;
499   /*
500    Creates the advection matrix for the given gll
501    */
502   PetscCall(PetscGaussLobattoLegendreElementAdvectionCreate(appctx->SEMop.gll.n, appctx->SEMop.gll.nodes, appctx->SEMop.gll.weights, &temp));
503   PetscCall(MatSetOption(A, MAT_NEW_NONZERO_ALLOCATION_ERR, PETSC_FALSE));
504 
505   PetscCall(DMDAGetCorners(appctx->da, &xs, NULL, NULL, &xn, NULL, NULL));
506 
507   xs = xs / (appctx->param.N - 1);
508   xn = xn / (appctx->param.N - 1);
509 
510   PetscCall(PetscMalloc1(appctx->param.N, &rowsDM));
511   for (j = xs; j < xs + xn; j++) {
512     for (l = 0; l < appctx->param.N; l++) rowsDM[l] = 1 + (j - xs) * (appctx->param.N - 1) + l;
513     PetscCall(MatSetValuesLocal(A, appctx->param.N, rowsDM, appctx->param.N, rowsDM, &temp[0][0], ADD_VALUES));
514   }
515   PetscCall(PetscFree(rowsDM));
516   PetscCall(MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY));
517   PetscCall(MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY));
518 
519   PetscCall(VecReciprocal(appctx->SEMop.mass));
520   PetscCall(MatDiagonalScale(A, appctx->SEMop.mass, 0));
521   PetscCall(VecReciprocal(appctx->SEMop.mass));
522   PetscCall(PetscGaussLobattoLegendreElementAdvectionDestroy(appctx->SEMop.gll.n, appctx->SEMop.gll.nodes, appctx->SEMop.gll.weights, &temp));
523   PetscFunctionReturn(PETSC_SUCCESS);
524 }
525 /* ------------------------------------------------------------------ */
526 /*
527    FormFunctionGradient - Evaluates the function and corresponding gradient.
528 
529    Input Parameters:
530    tao - the Tao context
531    IC   - the input vector
532    ctx - optional user-defined context, as set when calling TaoSetObjectiveAndGradient()
533 
534    Output Parameters:
535    f   - the newly evaluated function
536    G   - the newly evaluated gradient
537 
538    Notes:
539 
540           The forward equation is
541               M u_t = F(U)
542           which is converted to
543                 u_t = M^{-1} F(u)
544           in the user code since TS has no direct way of providing a mass matrix. The Jacobian of this is
545                  M^{-1} J
546           where J is the Jacobian of F. Now the adjoint equation is
547                 M v_t = J^T v
548           but TSAdjoint does not solve this since it can only solve the transposed system for the
549           Jacobian the user provided. Hence TSAdjoint solves
550                  w_t = J^T M^{-1} w  (where w = M v)
551           since there is no way to indicate the mass matrix as a separate entity to TS. Thus one
552           must be careful in initializing the "adjoint equation" and using the result. This is
553           why
554               G = -2 M(u(T) - u_d)
555           below (instead of -2(u(T) - u_d) and why the result is
556               G = G/appctx->SEMop.mass (that is G = M^{-1}w)
557           below (instead of just the result of the "adjoint solve").
558 
559 */
560 PetscErrorCode FormFunctionGradient(Tao tao, Vec IC, PetscReal *f, Vec G, void *ctx)
561 {
562   AppCtx            *appctx = (AppCtx *)ctx; /* user-defined application context */
563   Vec                temp;
564   PetscInt           its;
565   PetscReal          ff, gnorm, cnorm, xdiff, errex;
566   TaoConvergedReason reason;
567 
568   PetscFunctionBegin;
569   PetscCall(TSSetTime(appctx->ts, 0.0));
570   PetscCall(TSSetStepNumber(appctx->ts, 0));
571   PetscCall(TSSetTimeStep(appctx->ts, appctx->initial_dt));
572   PetscCall(VecCopy(IC, appctx->dat.curr_sol));
573 
574   PetscCall(TSSolve(appctx->ts, appctx->dat.curr_sol));
575 
576   PetscCall(VecWAXPY(G, -1.0, appctx->dat.curr_sol, appctx->dat.obj));
577 
578   /*
579      Compute the L2-norm of the objective function, cost function is f
580   */
581   PetscCall(VecDuplicate(G, &temp));
582   PetscCall(VecPointwiseMult(temp, G, G));
583   PetscCall(VecDot(temp, appctx->SEMop.mass, f));
584 
585   /* local error evaluation   */
586   PetscCall(VecWAXPY(temp, -1.0, appctx->dat.ic, appctx->dat.true_solution));
587   PetscCall(VecPointwiseMult(temp, temp, temp));
588   /* for error evaluation */
589   PetscCall(VecDot(temp, appctx->SEMop.mass, &errex));
590   PetscCall(VecDestroy(&temp));
591   errex = PetscSqrtReal(errex);
592 
593   /*
594      Compute initial conditions for the adjoint integration. See Notes above
595   */
596 
597   PetscCall(VecScale(G, -2.0));
598   PetscCall(VecPointwiseMult(G, G, appctx->SEMop.mass));
599   PetscCall(TSSetCostGradients(appctx->ts, 1, &G, NULL));
600   PetscCall(TSAdjointSolve(appctx->ts));
601   PetscCall(VecPointwiseDivide(G, G, appctx->SEMop.mass));
602 
603   PetscCall(TaoGetSolutionStatus(tao, &its, &ff, &gnorm, &cnorm, &xdiff, &reason));
604   PetscFunctionReturn(PETSC_SUCCESS);
605 }
606 
607 PetscErrorCode MonitorError(Tao tao, void *ctx)
608 {
609   AppCtx   *appctx = (AppCtx *)ctx;
610   Vec       temp;
611   PetscReal nrm;
612 
613   PetscFunctionBegin;
614   PetscCall(VecDuplicate(appctx->dat.ic, &temp));
615   PetscCall(VecWAXPY(temp, -1.0, appctx->dat.ic, appctx->dat.true_solution));
616   PetscCall(VecPointwiseMult(temp, temp, temp));
617   PetscCall(VecDot(temp, appctx->SEMop.mass, &nrm));
618   PetscCall(VecDestroy(&temp));
619   nrm = PetscSqrtReal(nrm);
620   PetscCall(PetscPrintf(PETSC_COMM_WORLD, "Error for initial conditions %g\n", (double)nrm));
621   PetscFunctionReturn(PETSC_SUCCESS);
622 }
623 
624 /*TEST
625 
626     build:
627       requires: !complex
628 
629     test:
630       args: -tao_max_it 5 -tao_gatol 1.e-4
631       requires: !single
632 
633     test:
634       suffix: 2
635       nsize: 2
636       args: -tao_max_it 5 -tao_gatol 1.e-4
637       requires: !single
638 
639 TEST*/
640