xref: /petsc/src/ts/tutorials/ex29.c (revision 4e8208cbcbc709572b8abe32f33c78b69c819375)
1c4762a1bSJed Brown static char help[] = "Time-Dependent Allan-Cahn example in 2D with Varying Coefficients";
2c4762a1bSJed Brown 
3c4762a1bSJed Brown /*
4c4762a1bSJed Brown  This example is mainly here to show how to transfer coefficients between subdomains and levels in
5c4762a1bSJed Brown  multigrid and domain decomposition.
6c4762a1bSJed Brown  */
7c4762a1bSJed Brown 
8c4762a1bSJed Brown #include <petscdm.h>
9c4762a1bSJed Brown #include <petscdmda.h>
10c4762a1bSJed Brown #include <petscsnes.h>
11c4762a1bSJed Brown #include <petscts.h>
12c4762a1bSJed Brown 
13c4762a1bSJed Brown typedef struct {
14c4762a1bSJed Brown   PetscScalar epsilon;
15c4762a1bSJed Brown   PetscScalar beta;
16c4762a1bSJed Brown } Coeff;
17c4762a1bSJed Brown 
18c4762a1bSJed Brown typedef struct {
19c4762a1bSJed Brown   PetscScalar u;
20c4762a1bSJed Brown } Field;
21c4762a1bSJed Brown 
22*2a8381b2SBarry Smith extern PetscErrorCode FormInitialGuess(DM da, PetscCtx ctx, Vec X);
23*2a8381b2SBarry Smith extern PetscErrorCode FormDiffusionCoefficient(DM da, PetscCtx ctx, Vec X);
24c4762a1bSJed Brown extern PetscErrorCode FormIFunctionLocal(DMDALocalInfo *, PetscReal, Field **, Field **, Field **, void *);
25c4762a1bSJed Brown 
26c4762a1bSJed Brown /* hooks */
27c4762a1bSJed Brown 
CoefficientCoarsenHook(DM dm,DM dmc,PetscCtx ctx)28*2a8381b2SBarry Smith static PetscErrorCode CoefficientCoarsenHook(DM dm, DM dmc, PetscCtx ctx)
29d71ae5a4SJacob Faibussowitsch {
30c4762a1bSJed Brown   Vec c, cc, ccl;
31c4762a1bSJed Brown   Mat J;
32c4762a1bSJed Brown   Vec vscale;
33c4762a1bSJed Brown   DM  cdm, cdmc;
34c4762a1bSJed Brown 
357510d9b0SBarry Smith   PetscFunctionBeginUser;
369566063dSJacob Faibussowitsch   PetscCall(PetscObjectQuery((PetscObject)dm, "coefficientdm", (PetscObject *)&cdm));
37c4762a1bSJed Brown 
383c633725SBarry Smith   PetscCheck(cdm, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "The coefficient DM needs to be set up!");
39c4762a1bSJed Brown 
409566063dSJacob Faibussowitsch   PetscCall(DMDACreateCompatibleDMDA(dmc, 2, &cdmc));
419566063dSJacob Faibussowitsch   PetscCall(PetscObjectCompose((PetscObject)dmc, "coefficientdm", (PetscObject)cdmc));
42c4762a1bSJed Brown 
439566063dSJacob Faibussowitsch   PetscCall(DMGetNamedGlobalVector(cdm, "coefficient", &c));
449566063dSJacob Faibussowitsch   PetscCall(DMGetNamedGlobalVector(cdmc, "coefficient", &cc));
459566063dSJacob Faibussowitsch   PetscCall(DMGetNamedLocalVector(cdmc, "coefficient", &ccl));
46c4762a1bSJed Brown 
479566063dSJacob Faibussowitsch   PetscCall(DMCreateInterpolation(cdmc, cdm, &J, &vscale));
489566063dSJacob Faibussowitsch   PetscCall(MatRestrict(J, c, cc));
499566063dSJacob Faibussowitsch   PetscCall(VecPointwiseMult(cc, vscale, cc));
50c4762a1bSJed Brown 
519566063dSJacob Faibussowitsch   PetscCall(MatDestroy(&J));
529566063dSJacob Faibussowitsch   PetscCall(VecDestroy(&vscale));
53c4762a1bSJed Brown 
549566063dSJacob Faibussowitsch   PetscCall(DMGlobalToLocalBegin(cdmc, cc, INSERT_VALUES, ccl));
559566063dSJacob Faibussowitsch   PetscCall(DMGlobalToLocalEnd(cdmc, cc, INSERT_VALUES, ccl));
56c4762a1bSJed Brown 
579566063dSJacob Faibussowitsch   PetscCall(DMRestoreNamedGlobalVector(cdm, "coefficient", &c));
589566063dSJacob Faibussowitsch   PetscCall(DMRestoreNamedGlobalVector(cdmc, "coefficient", &cc));
599566063dSJacob Faibussowitsch   PetscCall(DMRestoreNamedLocalVector(cdmc, "coefficient", &ccl));
60c4762a1bSJed Brown 
619566063dSJacob Faibussowitsch   PetscCall(DMCoarsenHookAdd(dmc, CoefficientCoarsenHook, NULL, NULL));
629566063dSJacob Faibussowitsch   PetscCall(DMDestroy(&cdmc));
633ba16761SJacob Faibussowitsch   PetscFunctionReturn(PETSC_SUCCESS);
64c4762a1bSJed Brown }
65c4762a1bSJed Brown 
66c4762a1bSJed Brown /* This could restrict auxiliary information to the coarse level.
67c4762a1bSJed Brown  */
CoefficientSubDomainRestrictHook(DM dm,DM subdm,PetscCtx ctx)68*2a8381b2SBarry Smith static PetscErrorCode CoefficientSubDomainRestrictHook(DM dm, DM subdm, PetscCtx ctx)
69d71ae5a4SJacob Faibussowitsch {
70c4762a1bSJed Brown   Vec         c, cc;
71c4762a1bSJed Brown   DM          cdm, csubdm;
72c4762a1bSJed Brown   VecScatter *iscat, *oscat, *gscat;
73c4762a1bSJed Brown 
747510d9b0SBarry Smith   PetscFunctionBeginUser;
759566063dSJacob Faibussowitsch   PetscCall(PetscObjectQuery((PetscObject)dm, "coefficientdm", (PetscObject *)&cdm));
76c4762a1bSJed Brown 
773c633725SBarry Smith   PetscCheck(cdm, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "The coefficient DM needs to be set up!");
78c4762a1bSJed Brown 
799566063dSJacob Faibussowitsch   PetscCall(DMDACreateCompatibleDMDA(subdm, 2, &csubdm));
809566063dSJacob Faibussowitsch   PetscCall(PetscObjectCompose((PetscObject)subdm, "coefficientdm", (PetscObject)csubdm));
81c4762a1bSJed Brown 
829566063dSJacob Faibussowitsch   PetscCall(DMGetNamedGlobalVector(cdm, "coefficient", &c));
839566063dSJacob Faibussowitsch   PetscCall(DMGetNamedLocalVector(csubdm, "coefficient", &cc));
84c4762a1bSJed Brown 
859566063dSJacob Faibussowitsch   PetscCall(DMCreateDomainDecompositionScatters(cdm, 1, &csubdm, &iscat, &oscat, &gscat));
86c4762a1bSJed Brown 
879566063dSJacob Faibussowitsch   PetscCall(VecScatterBegin(*gscat, c, cc, INSERT_VALUES, SCATTER_FORWARD));
889566063dSJacob Faibussowitsch   PetscCall(VecScatterEnd(*gscat, c, cc, INSERT_VALUES, SCATTER_FORWARD));
89c4762a1bSJed Brown 
909566063dSJacob Faibussowitsch   PetscCall(VecScatterDestroy(iscat));
919566063dSJacob Faibussowitsch   PetscCall(VecScatterDestroy(oscat));
929566063dSJacob Faibussowitsch   PetscCall(VecScatterDestroy(gscat));
939566063dSJacob Faibussowitsch   PetscCall(PetscFree(iscat));
949566063dSJacob Faibussowitsch   PetscCall(PetscFree(oscat));
959566063dSJacob Faibussowitsch   PetscCall(PetscFree(gscat));
96c4762a1bSJed Brown 
979566063dSJacob Faibussowitsch   PetscCall(DMRestoreNamedGlobalVector(cdm, "coefficient", &c));
989566063dSJacob Faibussowitsch   PetscCall(DMRestoreNamedLocalVector(csubdm, "coefficient", &cc));
99c4762a1bSJed Brown 
1009566063dSJacob Faibussowitsch   PetscCall(DMDestroy(&csubdm));
1013ba16761SJacob Faibussowitsch   PetscFunctionReturn(PETSC_SUCCESS);
102c4762a1bSJed Brown }
103c4762a1bSJed Brown 
main(int argc,char ** argv)104c4762a1bSJed Brown int main(int argc, char **argv)
105c4762a1bSJed Brown {
106c4762a1bSJed Brown   TS  ts;
107c4762a1bSJed Brown   Vec x, c, clocal;
108c4762a1bSJed Brown   DM  da, cda;
109c4762a1bSJed Brown 
110327415f7SBarry Smith   PetscFunctionBeginUser;
111c8025a54SPierre Jolivet   PetscCall(PetscInitialize(&argc, &argv, NULL, help));
1129566063dSJacob Faibussowitsch   PetscCall(TSCreate(PETSC_COMM_WORLD, &ts));
1139566063dSJacob Faibussowitsch   PetscCall(TSSetType(ts, TSARKIMEX));
1149566063dSJacob Faibussowitsch   PetscCall(TSSetProblemType(ts, TS_NONLINEAR));
1159566063dSJacob Faibussowitsch   PetscCall(DMDACreate2d(PETSC_COMM_WORLD, DM_BOUNDARY_NONE, DM_BOUNDARY_NONE, DMDA_STENCIL_STAR, 4, 4, PETSC_DECIDE, PETSC_DECIDE, 1, 1, NULL, NULL, &da));
1169566063dSJacob Faibussowitsch   PetscCall(DMSetFromOptions(da));
1179566063dSJacob Faibussowitsch   PetscCall(DMSetUp(da));
1189566063dSJacob Faibussowitsch   PetscCall(DMDASetUniformCoordinates(da, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0));
119c4762a1bSJed Brown 
1209566063dSJacob Faibussowitsch   PetscCall(DMDASetFieldName(da, 0, "u"));
1219566063dSJacob Faibussowitsch   PetscCall(DMCreateGlobalVector(da, &x));
122c4762a1bSJed Brown 
1239566063dSJacob Faibussowitsch   PetscCall(TSSetDM(ts, da));
124c4762a1bSJed Brown 
1259566063dSJacob Faibussowitsch   PetscCall(FormInitialGuess(da, NULL, x));
1269566063dSJacob Faibussowitsch   PetscCall(DMDATSSetIFunctionLocal(da, INSERT_VALUES, (PetscErrorCode (*)(DMDALocalInfo *, PetscReal, void *, void *, void *, void *))FormIFunctionLocal, NULL));
127c4762a1bSJed Brown 
128c4762a1bSJed Brown   /* set up the coefficient */
129c4762a1bSJed Brown 
1309566063dSJacob Faibussowitsch   PetscCall(DMDACreateCompatibleDMDA(da, 2, &cda));
1319566063dSJacob Faibussowitsch   PetscCall(PetscObjectCompose((PetscObject)da, "coefficientdm", (PetscObject)cda));
132c4762a1bSJed Brown 
1339566063dSJacob Faibussowitsch   PetscCall(DMGetNamedGlobalVector(cda, "coefficient", &c));
1349566063dSJacob Faibussowitsch   PetscCall(DMGetNamedLocalVector(cda, "coefficient", &clocal));
135c4762a1bSJed Brown 
1369566063dSJacob Faibussowitsch   PetscCall(FormDiffusionCoefficient(cda, NULL, c));
137c4762a1bSJed Brown 
1389566063dSJacob Faibussowitsch   PetscCall(DMGlobalToLocalBegin(cda, c, INSERT_VALUES, clocal));
1399566063dSJacob Faibussowitsch   PetscCall(DMGlobalToLocalEnd(cda, c, INSERT_VALUES, clocal));
140c4762a1bSJed Brown 
1419566063dSJacob Faibussowitsch   PetscCall(DMRestoreNamedLocalVector(cda, "coefficient", &clocal));
1429566063dSJacob Faibussowitsch   PetscCall(DMRestoreNamedGlobalVector(cda, "coefficient", &c));
143c4762a1bSJed Brown 
1449566063dSJacob Faibussowitsch   PetscCall(DMCoarsenHookAdd(da, CoefficientCoarsenHook, NULL, NULL));
1459566063dSJacob Faibussowitsch   PetscCall(DMSubDomainHookAdd(da, CoefficientSubDomainRestrictHook, NULL, NULL));
146c4762a1bSJed Brown 
1479566063dSJacob Faibussowitsch   PetscCall(TSSetMaxSteps(ts, 10000));
1489566063dSJacob Faibussowitsch   PetscCall(TSSetMaxTime(ts, 10000.0));
1499566063dSJacob Faibussowitsch   PetscCall(TSSetExactFinalTime(ts, TS_EXACTFINALTIME_STEPOVER));
1509566063dSJacob Faibussowitsch   PetscCall(TSSetTimeStep(ts, 0.05));
1519566063dSJacob Faibussowitsch   PetscCall(TSSetSolution(ts, x));
1529566063dSJacob Faibussowitsch   PetscCall(TSSetFromOptions(ts));
153c4762a1bSJed Brown 
1549566063dSJacob Faibussowitsch   PetscCall(TSSolve(ts, x));
155c4762a1bSJed Brown 
1569566063dSJacob Faibussowitsch   PetscCall(VecDestroy(&x));
1579566063dSJacob Faibussowitsch   PetscCall(TSDestroy(&ts));
1589566063dSJacob Faibussowitsch   PetscCall(DMDestroy(&da));
1599566063dSJacob Faibussowitsch   PetscCall(DMDestroy(&cda));
160c4762a1bSJed Brown 
1619566063dSJacob Faibussowitsch   PetscCall(PetscFinalize());
162b122ec5aSJacob Faibussowitsch   return 0;
163c4762a1bSJed Brown }
164c4762a1bSJed Brown 
165c4762a1bSJed Brown /* ------------------------------------------------------------------- */
166c4762a1bSJed Brown 
FormInitialGuess(DM da,PetscCtx ctx,Vec X)167*2a8381b2SBarry Smith PetscErrorCode FormInitialGuess(DM da, PetscCtx ctx, Vec X)
168d71ae5a4SJacob Faibussowitsch {
169c4762a1bSJed Brown   PetscInt  i, j, Mx, My, xs, ys, xm, ym;
170c4762a1bSJed Brown   Field   **x;
171c4762a1bSJed Brown   PetscReal x0, x1;
172c4762a1bSJed Brown 
173c4762a1bSJed Brown   PetscFunctionBeginUser;
1749566063dSJacob Faibussowitsch   PetscCall(DMDAGetInfo(da, PETSC_IGNORE, &Mx, &My, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE));
175c4762a1bSJed Brown 
1769566063dSJacob Faibussowitsch   PetscCall(DMDAVecGetArray(da, X, &x));
1779566063dSJacob Faibussowitsch   PetscCall(DMDAGetCorners(da, &xs, &ys, NULL, &xm, &ym, NULL));
178c4762a1bSJed Brown 
179c4762a1bSJed Brown   for (j = ys; j < ys + ym; j++) {
180c4762a1bSJed Brown     for (i = xs; i < xs + xm; i++) {
181c4762a1bSJed Brown       x0        = 10.0 * (i - 0.5 * (Mx - 1)) / (Mx - 1);
182c4762a1bSJed Brown       x1        = 10.0 * (j - 0.5 * (Mx - 1)) / (My - 1);
183c4762a1bSJed Brown       x[j][i].u = PetscCosReal(2.0 * PetscSqrtReal(x1 * x1 + x0 * x0));
184c4762a1bSJed Brown     }
185c4762a1bSJed Brown   }
186c4762a1bSJed Brown 
1879566063dSJacob Faibussowitsch   PetscCall(DMDAVecRestoreArray(da, X, &x));
1883ba16761SJacob Faibussowitsch   PetscFunctionReturn(PETSC_SUCCESS);
189c4762a1bSJed Brown }
190c4762a1bSJed Brown 
FormDiffusionCoefficient(DM da,PetscCtx ctx,Vec X)191*2a8381b2SBarry Smith PetscErrorCode FormDiffusionCoefficient(DM da, PetscCtx ctx, Vec X)
192d71ae5a4SJacob Faibussowitsch {
193c4762a1bSJed Brown   PetscInt  i, j, Mx, My, xs, ys, xm, ym;
194c4762a1bSJed Brown   Coeff   **x;
195c4762a1bSJed Brown   PetscReal x1, x0;
196c4762a1bSJed Brown 
197c4762a1bSJed Brown   PetscFunctionBeginUser;
1989566063dSJacob Faibussowitsch   PetscCall(DMDAGetInfo(da, PETSC_IGNORE, &Mx, &My, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE, PETSC_IGNORE));
199c4762a1bSJed Brown 
200c4762a1bSJed Brown   /*
201c4762a1bSJed Brown   ierr = VecSetRandom(X,NULL);
2029566063dSJacob Faibussowitsch   PetscCall(VecMin(X,NULL,&min));
203c4762a1bSJed Brown    */
204c4762a1bSJed Brown 
2059566063dSJacob Faibussowitsch   PetscCall(DMDAVecGetArray(da, X, &x));
2069566063dSJacob Faibussowitsch   PetscCall(DMDAGetCorners(da, &xs, &ys, NULL, &xm, &ym, NULL));
207c4762a1bSJed Brown 
208c4762a1bSJed Brown   for (j = ys; j < ys + ym; j++) {
209c4762a1bSJed Brown     for (i = xs; i < xs + xm; i++) {
210c4762a1bSJed Brown       x0 = 10.0 * (i - 0.5 * (Mx - 1)) / (Mx - 1);
211c4762a1bSJed Brown       x1 = 10.0 * (j - 0.5 * (My - 1)) / (My - 1);
212c4762a1bSJed Brown 
213c4762a1bSJed Brown       x[j][i].epsilon = 0.0;
214c4762a1bSJed Brown       x[j][i].beta    = 0.05 + 0.05 * PetscSqrtReal(x0 * x0 + x1 * x1);
215c4762a1bSJed Brown     }
216c4762a1bSJed Brown   }
217c4762a1bSJed Brown 
2189566063dSJacob Faibussowitsch   PetscCall(DMDAVecRestoreArray(da, X, &x));
2193ba16761SJacob Faibussowitsch   PetscFunctionReturn(PETSC_SUCCESS);
220c4762a1bSJed Brown }
221c4762a1bSJed Brown 
FormIFunctionLocal(DMDALocalInfo * info,PetscReal ptime,Field ** x,Field ** xt,Field ** f,PetscCtx ctx)222*2a8381b2SBarry Smith PetscErrorCode FormIFunctionLocal(DMDALocalInfo *info, PetscReal ptime, Field **x, Field **xt, Field **f, PetscCtx ctx)
223d71ae5a4SJacob Faibussowitsch {
224c4762a1bSJed Brown   PetscInt    i, j;
225c4762a1bSJed Brown   PetscReal   hx, hy, dhx, dhy, hxdhy, hydhx, scale;
226c4762a1bSJed Brown   PetscScalar u, uxx, uyy;
227c4762a1bSJed Brown   PetscScalar ux, uy, bx, by;
228c4762a1bSJed Brown   Vec         C;
229c4762a1bSJed Brown   Coeff     **c;
230c4762a1bSJed Brown   DM          cdm;
231c4762a1bSJed Brown 
232c4762a1bSJed Brown   PetscFunctionBeginUser;
2339566063dSJacob Faibussowitsch   PetscCall(PetscObjectQuery((PetscObject)info->da, "coefficientdm", (PetscObject *)&cdm));
2349566063dSJacob Faibussowitsch   PetscCall(DMGetNamedLocalVector(cdm, "coefficient", &C));
2359566063dSJacob Faibussowitsch   PetscCall(DMDAVecGetArray(cdm, C, &c));
236c4762a1bSJed Brown 
237c4762a1bSJed Brown   hx = 10.0 / ((PetscReal)(info->mx - 1));
238c4762a1bSJed Brown   hy = 10.0 / ((PetscReal)(info->my - 1));
239c4762a1bSJed Brown 
240c4762a1bSJed Brown   dhx = 1. / hx;
241c4762a1bSJed Brown   dhy = 1. / hy;
242c4762a1bSJed Brown 
243c4762a1bSJed Brown   hxdhy = hx / hy;
244c4762a1bSJed Brown   hydhx = hy / hx;
245c4762a1bSJed Brown   scale = hx * hy;
246c4762a1bSJed Brown 
247c4762a1bSJed Brown   for (j = info->ys; j < info->ys + info->ym; j++) {
248c4762a1bSJed Brown     for (i = info->xs; i < info->xs + info->xm; i++) {
249c4762a1bSJed Brown       f[j][i].u = xt[j][i].u * scale;
250c4762a1bSJed Brown 
251c4762a1bSJed Brown       u = x[j][i].u;
252c4762a1bSJed Brown 
253c4762a1bSJed Brown       f[j][i].u += scale * (u * u - 1.) * u;
254c4762a1bSJed Brown 
255c4762a1bSJed Brown       if (i == 0) f[j][i].u += (x[j][i].u - x[j][i + 1].u) * dhx;
256c4762a1bSJed Brown       else if (i == info->mx - 1) f[j][i].u += (x[j][i].u - x[j][i - 1].u) * dhx;
257c4762a1bSJed Brown       else if (j == 0) f[j][i].u += (x[j][i].u - x[j + 1][i].u) * dhy;
258c4762a1bSJed Brown       else if (j == info->my - 1) f[j][i].u += (x[j][i].u - x[j - 1][i].u) * dhy;
259c4762a1bSJed Brown       else {
260c4762a1bSJed Brown         uyy = (2.0 * u - x[j - 1][i].u - x[j + 1][i].u) * hxdhy;
261c4762a1bSJed Brown         uxx = (2.0 * u - x[j][i - 1].u - x[j][i + 1].u) * hydhx;
262c4762a1bSJed Brown 
263c4762a1bSJed Brown         bx = 0.5 * (c[j][i + 1].beta - c[j][i - 1].beta) * dhx;
264c4762a1bSJed Brown         by = 0.5 * (c[j + 1][i].beta - c[j - 1][i].beta) * dhy;
265c4762a1bSJed Brown 
266c4762a1bSJed Brown         ux = 0.5 * (x[j][i + 1].u - x[j][i - 1].u) * dhx;
267c4762a1bSJed Brown         uy = 0.5 * (x[j + 1][i].u - x[j - 1][i].u) * dhy;
268c4762a1bSJed Brown 
269c4762a1bSJed Brown         f[j][i].u += c[j][i].beta * (uxx + uyy) + scale * (bx * ux + by * uy);
270c4762a1bSJed Brown       }
271c4762a1bSJed Brown     }
272c4762a1bSJed Brown   }
2739566063dSJacob Faibussowitsch   PetscCall(PetscLogFlops(11. * info->ym * info->xm));
274c4762a1bSJed Brown 
2759566063dSJacob Faibussowitsch   PetscCall(DMDAVecRestoreArray(cdm, C, &c));
2769566063dSJacob Faibussowitsch   PetscCall(DMRestoreNamedLocalVector(cdm, "coefficient", &C));
2773ba16761SJacob Faibussowitsch   PetscFunctionReturn(PETSC_SUCCESS);
278c4762a1bSJed Brown }
279c4762a1bSJed Brown 
280c4762a1bSJed Brown /*TEST
281c4762a1bSJed Brown 
282c4762a1bSJed Brown     test:
283c4762a1bSJed Brown       args: -da_refine 4 -ts_max_steps 10 -ts_rtol 1e-3 -ts_atol 1e-3 -ts_type arkimex -ts_monitor -snes_monitor -snes_type ngmres -npc_snes_type nasm -npc_snes_nasm_type restrict -da_overlap 4
284c4762a1bSJed Brown       nsize: 16
285c4762a1bSJed Brown       requires: !single
286c4762a1bSJed Brown       output_file: output/ex29.out
287c4762a1bSJed Brown 
288c4762a1bSJed Brown TEST*/
289