xref: /petsc/src/dm/impls/plex/tests/ex49.c (revision 52e7713adc381d206404376c852c61b4333aca68)
190d1c1a4SMatthew G. Knepley static char help[] = "Tests dof numberings for external integrators such as LibCEED.\n\n";
290d1c1a4SMatthew G. Knepley 
390d1c1a4SMatthew G. Knepley #include <petscdmplex.h>
490d1c1a4SMatthew G. Knepley #include <petscds.h>
590d1c1a4SMatthew G. Knepley 
690d1c1a4SMatthew G. Knepley typedef struct {
7*52e7713aSMatthew G. Knepley   PetscBool useFE;
85f06a3ddSJed Brown   PetscInt  check_face;
95f06a3ddSJed Brown   PetscBool closure_tensor;
1090d1c1a4SMatthew G. Knepley } AppCtx;
1190d1c1a4SMatthew G. Knepley 
12d71ae5a4SJacob Faibussowitsch static PetscErrorCode ProcessOptions(MPI_Comm comm, AppCtx *options)
13d71ae5a4SJacob Faibussowitsch {
1490d1c1a4SMatthew G. Knepley   PetscFunctionBeginUser;
15*52e7713aSMatthew G. Knepley   options->useFE          = PETSC_TRUE;
165f06a3ddSJed Brown   options->check_face     = 1;
175f06a3ddSJed Brown   options->closure_tensor = PETSC_FALSE;
1890d1c1a4SMatthew G. Knepley   PetscOptionsBegin(comm, "", "Dof Ordering Options", "DMPLEX");
19*52e7713aSMatthew G. Knepley   PetscCall(PetscOptionsBool("-use_fe", "Use FE or FV discretization", "ex49.c", options->useFE, &options->useFE, NULL));
205f06a3ddSJed Brown   PetscCall(PetscOptionsInt("-check_face", "Face set to report on", "ex49.c", options->check_face, &options->check_face, NULL));
215f06a3ddSJed Brown   PetscCall(PetscOptionsBool("-closure_tensor", "Use DMPlexSetClosurePermutationTensor()", "ex49.c", options->closure_tensor, &options->closure_tensor, NULL));
2290d1c1a4SMatthew G. Knepley   PetscOptionsEnd();
233ba16761SJacob Faibussowitsch   PetscFunctionReturn(PETSC_SUCCESS);
2490d1c1a4SMatthew G. Knepley }
2590d1c1a4SMatthew G. Knepley 
26d71ae5a4SJacob Faibussowitsch static PetscErrorCode CreateMesh(MPI_Comm comm, AppCtx *user, DM *dm)
27d71ae5a4SJacob Faibussowitsch {
2890d1c1a4SMatthew G. Knepley   PetscFunctionBeginUser;
2990d1c1a4SMatthew G. Knepley   PetscCall(DMCreate(comm, dm));
3090d1c1a4SMatthew G. Knepley   PetscCall(DMSetType(*dm, DMPLEX));
3190d1c1a4SMatthew G. Knepley   PetscCall(DMSetFromOptions(*dm));
3290d1c1a4SMatthew G. Knepley   PetscCall(DMSetApplicationContext(*dm, user));
3390d1c1a4SMatthew G. Knepley   PetscCall(DMViewFromOptions(*dm, NULL, "-dm_view"));
343ba16761SJacob Faibussowitsch   PetscFunctionReturn(PETSC_SUCCESS);
3590d1c1a4SMatthew G. Knepley }
3690d1c1a4SMatthew G. Knepley 
37d71ae5a4SJacob Faibussowitsch static PetscErrorCode SetupDiscretization(DM dm, AppCtx *user)
38d71ae5a4SJacob Faibussowitsch {
3990d1c1a4SMatthew G. Knepley   DM       cdm = dm;
4090d1c1a4SMatthew G. Knepley   PetscInt dim;
4190d1c1a4SMatthew G. Knepley 
4290d1c1a4SMatthew G. Knepley   PetscFunctionBeginUser;
4390d1c1a4SMatthew G. Knepley   PetscCall(DMGetDimension(dm, &dim));
44*52e7713aSMatthew G. Knepley   if (user->useFE) {
45*52e7713aSMatthew G. Knepley     PetscFE fe;
46*52e7713aSMatthew G. Knepley 
4790d1c1a4SMatthew G. Knepley     PetscCall(PetscFECreateDefault(PETSC_COMM_SELF, dim, 1, PETSC_FALSE, NULL, -1, &fe));
4890d1c1a4SMatthew G. Knepley     PetscCall(PetscObjectSetName((PetscObject)fe, "scalar"));
4990d1c1a4SMatthew G. Knepley     PetscCall(DMSetField(dm, 0, NULL, (PetscObject)fe));
5090d1c1a4SMatthew G. Knepley     PetscCall(DMSetField(dm, 1, NULL, (PetscObject)fe));
5190d1c1a4SMatthew G. Knepley     PetscCall(PetscFEDestroy(&fe));
52*52e7713aSMatthew G. Knepley   } else {
53*52e7713aSMatthew G. Knepley     PetscFV fv;
54*52e7713aSMatthew G. Knepley 
55*52e7713aSMatthew G. Knepley     PetscCall(PetscFVCreate(PETSC_COMM_SELF, &fv));
56*52e7713aSMatthew G. Knepley     PetscCall(PetscFVSetType(fv, PETSCFVLEASTSQUARES));
57*52e7713aSMatthew G. Knepley     PetscCall(PetscFVSetNumComponents(fv, dim));
58*52e7713aSMatthew G. Knepley     PetscCall(PetscFVSetSpatialDimension(fv, dim));
59*52e7713aSMatthew G. Knepley     PetscCall(PetscFVSetFromOptions(fv));
60*52e7713aSMatthew G. Knepley     PetscCall(PetscFVSetUp(fv));
61*52e7713aSMatthew G. Knepley     PetscCall(PetscObjectSetName((PetscObject)fv, "vector"));
62*52e7713aSMatthew G. Knepley     PetscCall(DMSetField(dm, 0, NULL, (PetscObject)fv));
63*52e7713aSMatthew G. Knepley     PetscCall(DMSetField(dm, 1, NULL, (PetscObject)fv));
64*52e7713aSMatthew G. Knepley     PetscCall(PetscFVDestroy(&fv));
65*52e7713aSMatthew G. Knepley   }
6690d1c1a4SMatthew G. Knepley   PetscCall(DMCreateDS(dm));
6790d1c1a4SMatthew G. Knepley   while (cdm) {
6890d1c1a4SMatthew G. Knepley     PetscCall(DMCopyDisc(dm, cdm));
6990d1c1a4SMatthew G. Knepley     PetscCall(DMGetCoarseDM(cdm, &cdm));
7090d1c1a4SMatthew G. Knepley   }
713ba16761SJacob Faibussowitsch   PetscFunctionReturn(PETSC_SUCCESS);
7290d1c1a4SMatthew G. Knepley }
7390d1c1a4SMatthew G. Knepley 
745f06a3ddSJed Brown static PetscErrorCode CheckOffsets(DM dm, AppCtx *user, const char *domain_name, PetscInt label_value, PetscInt height)
75d71ae5a4SJacob Faibussowitsch {
7690d1c1a4SMatthew G. Knepley   const char            *height_name[] = {"cells", "faces"};
7790d1c1a4SMatthew G. Knepley   DMLabel                domain_label  = NULL;
7890d1c1a4SMatthew G. Knepley   DM                     cdm;
7990d1c1a4SMatthew G. Knepley   PetscInt               Nf, f;
80f2c6b1a2SJed Brown   ISLocalToGlobalMapping ltog;
8190d1c1a4SMatthew G. Knepley 
8290d1c1a4SMatthew G. Knepley   PetscFunctionBeginUser;
8390d1c1a4SMatthew G. Knepley   if (domain_name) PetscCall(DMGetLabel(dm, domain_name, &domain_label));
843e72e933SJed Brown   PetscCall(PetscPrintf(PETSC_COMM_WORLD, "## %s: '%s' {%" PetscInt_FMT "}%s\n", height_name[height], domain_name ? domain_name : "default", label_value, domain_name && !domain_label ? " (null label)" : ""));
853ba16761SJacob Faibussowitsch   if (domain_name && !domain_label) PetscFunctionReturn(PETSC_SUCCESS);
865f06a3ddSJed Brown   if (user->closure_tensor) PetscCall(DMPlexSetClosurePermutationTensor(dm, PETSC_DETERMINE, NULL));
8790d1c1a4SMatthew G. Knepley   // Offsets for cell closures
8890d1c1a4SMatthew G. Knepley   PetscCall(DMGetNumFields(dm, &Nf));
8990d1c1a4SMatthew G. Knepley   for (f = 0; f < Nf; ++f) {
90*52e7713aSMatthew G. Knepley     PetscObject  obj;
91*52e7713aSMatthew G. Knepley     PetscClassId id;
9290d1c1a4SMatthew G. Knepley     char         name[PETSC_MAX_PATH_LEN];
9390d1c1a4SMatthew G. Knepley 
94*52e7713aSMatthew G. Knepley     PetscCall(DMGetField(dm, f, NULL, &obj));
95*52e7713aSMatthew G. Knepley     PetscCall(PetscObjectGetClassId(obj, &id));
96*52e7713aSMatthew G. Knepley     if (id == PETSCFE_CLASSID) {
97*52e7713aSMatthew G. Knepley       IS        offIS;
98*52e7713aSMatthew G. Knepley       PetscInt *offsets, Ncell, Ncl, Nc, n;
99*52e7713aSMatthew G. Knepley 
10090d1c1a4SMatthew G. Knepley       PetscCall(DMPlexGetLocalOffsets(dm, domain_label, label_value, height, f, &Ncell, &Ncl, &Nc, &n, &offsets));
10190d1c1a4SMatthew G. Knepley       PetscCall(ISCreateGeneral(PETSC_COMM_SELF, Ncell * Ncl, offsets, PETSC_OWN_POINTER, &offIS));
10290d1c1a4SMatthew G. Knepley       PetscCall(PetscSNPrintf(name, PETSC_MAX_PATH_LEN, "Field %" PetscInt_FMT " Offsets", f));
10390d1c1a4SMatthew G. Knepley       PetscCall(PetscObjectSetName((PetscObject)offIS, name));
10490d1c1a4SMatthew G. Knepley       PetscCall(ISViewFromOptions(offIS, NULL, "-offsets_view"));
10590d1c1a4SMatthew G. Knepley       PetscCall(ISDestroy(&offIS));
106*52e7713aSMatthew G. Knepley     } else if (id == PETSCFV_CLASSID) {
107*52e7713aSMatthew G. Knepley       IS        offIS;
108*52e7713aSMatthew G. Knepley       PetscInt *offsets, *offsetsNeg, *offsetsPos, Nface, Nc, n;
109*52e7713aSMatthew G. Knepley 
110*52e7713aSMatthew G. Knepley       PetscCall(DMPlexGetLocalOffsetsSupport(dm, domain_label, label_value, &Nface, &Nc, &n, &offsetsNeg, &offsetsPos));
111*52e7713aSMatthew G. Knepley       PetscCall(PetscMalloc1(Nface * Nc * 2, &offsets));
112*52e7713aSMatthew G. Knepley       for (PetscInt f = 0, i = 0; f < Nface; ++f) {
113*52e7713aSMatthew G. Knepley         for (PetscInt c = 0; c < Nc; ++c) offsets[i++] = offsetsNeg[f * Nc + c];
114*52e7713aSMatthew G. Knepley         for (PetscInt c = 0; c < Nc; ++c) offsets[i++] = offsetsPos[f * Nc + c];
115*52e7713aSMatthew G. Knepley       }
116*52e7713aSMatthew G. Knepley       PetscCall(PetscFree(offsetsNeg));
117*52e7713aSMatthew G. Knepley       PetscCall(PetscFree(offsetsPos));
118*52e7713aSMatthew G. Knepley       PetscCall(ISCreateGeneral(PETSC_COMM_SELF, Nface * Nc * 2, offsets, PETSC_OWN_POINTER, &offIS));
119*52e7713aSMatthew G. Knepley       PetscCall(PetscSNPrintf(name, PETSC_MAX_PATH_LEN, "Field %" PetscInt_FMT " Offsets", f));
120*52e7713aSMatthew G. Knepley       PetscCall(PetscObjectSetName((PetscObject)offIS, name));
121*52e7713aSMatthew G. Knepley       PetscCall(ISViewFromOptions(offIS, NULL, "-offsets_view"));
122*52e7713aSMatthew G. Knepley       PetscCall(ISDestroy(&offIS));
123*52e7713aSMatthew G. Knepley     } else SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_PLIB, "Unrecognized type for DM field %" PetscInt_FMT, f);
12490d1c1a4SMatthew G. Knepley   }
125f2c6b1a2SJed Brown   PetscCall(DMGetLocalToGlobalMapping(dm, &ltog));
126f2c6b1a2SJed Brown   PetscCall(ISLocalToGlobalMappingViewFromOptions(ltog, NULL, "-ltog_view"));
127f2c6b1a2SJed Brown 
12890d1c1a4SMatthew G. Knepley   // Offsets for coordinates
12990d1c1a4SMatthew G. Knepley   {
13090d1c1a4SMatthew G. Knepley     Vec                X;
13190d1c1a4SMatthew G. Knepley     PetscSection       s;
13290d1c1a4SMatthew G. Knepley     const PetscScalar *x;
13390d1c1a4SMatthew G. Knepley     const char        *cname;
134*52e7713aSMatthew G. Knepley     PetscInt           cdim, *offsets, Ncell, Ncl, Nc, n;
13590d1c1a4SMatthew G. Knepley     PetscBool          isDG = PETSC_FALSE;
13690d1c1a4SMatthew G. Knepley 
13790d1c1a4SMatthew G. Knepley     PetscCall(DMGetCellCoordinateDM(dm, &cdm));
13890d1c1a4SMatthew G. Knepley     if (!cdm) {
1399371c9d4SSatish Balay       PetscCall(DMGetCoordinateDM(dm, &cdm));
1409371c9d4SSatish Balay       cname = "Coordinates";
14190d1c1a4SMatthew G. Knepley       PetscCall(DMGetCoordinatesLocal(dm, &X));
14290d1c1a4SMatthew G. Knepley     } else {
14390d1c1a4SMatthew G. Knepley       isDG  = PETSC_TRUE;
14490d1c1a4SMatthew G. Knepley       cname = "DG Coordinates";
14590d1c1a4SMatthew G. Knepley       PetscCall(DMGetCellCoordinatesLocal(dm, &X));
14690d1c1a4SMatthew G. Knepley     }
1473ba16761SJacob Faibussowitsch     if (isDG && height) PetscFunctionReturn(PETSC_SUCCESS);
14890d1c1a4SMatthew G. Knepley     if (domain_name) PetscCall(DMGetLabel(cdm, domain_name, &domain_label));
1495f06a3ddSJed Brown     if (user->closure_tensor) PetscCall(DMPlexSetClosurePermutationTensor(cdm, PETSC_DETERMINE, NULL));
15090d1c1a4SMatthew G. Knepley     PetscCall(DMPlexGetLocalOffsets(cdm, domain_label, label_value, height, 0, &Ncell, &Ncl, &Nc, &n, &offsets));
15190d1c1a4SMatthew G. Knepley     PetscCall(DMGetCoordinateDim(dm, &cdim));
15290d1c1a4SMatthew G. Knepley     PetscCheck(Nc == cdim, PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "Geometric dimension %" PetscInt_FMT " should be %" PetscInt_FMT, Nc, cdim);
15390d1c1a4SMatthew G. Knepley     PetscCall(DMGetLocalSection(cdm, &s));
15490d1c1a4SMatthew G. Knepley     PetscCall(VecGetArrayRead(X, &x));
1555f06a3ddSJed Brown     PetscCall(PetscPrintf(PETSC_COMM_WORLD, "%s by element in %s order\n", cname, user->closure_tensor ? "tensor" : "bfs"));
15690d1c1a4SMatthew G. Knepley     for (PetscInt c = 0; c < Ncell; ++c) {
15790d1c1a4SMatthew G. Knepley       for (PetscInt v = 0; v < Ncl; ++v) {
15890d1c1a4SMatthew G. Knepley         PetscInt           off = offsets[c * Ncl + v], dgdof;
15990d1c1a4SMatthew G. Knepley         const PetscScalar *vx  = &x[off];
16090d1c1a4SMatthew G. Knepley 
16190d1c1a4SMatthew G. Knepley         if (isDG) {
16290d1c1a4SMatthew G. Knepley           PetscCall(PetscSectionGetDof(s, c, &dgdof));
16390d1c1a4SMatthew G. Knepley           PetscCheck(Ncl * Nc == dgdof, PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "Offset size %" PetscInt_FMT " should be %" PetscInt_FMT, Ncl * Nc, dgdof);
16490d1c1a4SMatthew G. Knepley         }
165eefd97a2SJed Brown         switch (cdim) {
1663e72e933SJed Brown         case 1:
1673e72e933SJed Brown           PetscCall(PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[%" PetscInt_FMT "] %" PetscInt_FMT " <-- %2" PetscInt_FMT " (% 4.2f)\n", c, v, off, (double)PetscRealPart(vx[0])));
1683e72e933SJed Brown           break;
169d71ae5a4SJacob Faibussowitsch         case 2:
1703e72e933SJed Brown           PetscCall(PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[%" PetscInt_FMT "] %" PetscInt_FMT " <-- %2" PetscInt_FMT " (% 4.2f, % 4.2f)\n", c, v, off, (double)PetscRealPart(vx[0]), (double)PetscRealPart(vx[1])));
171d71ae5a4SJacob Faibussowitsch           break;
172eefd97a2SJed Brown         case 3:
1733e72e933SJed Brown           PetscCall(PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[%" PetscInt_FMT "] %" PetscInt_FMT " <-- %2" PetscInt_FMT " (% 4.2f, % 4.2f, % 4.2f)\n", c, v, off, (double)PetscRealPart(vx[0]), (double)PetscRealPart(vx[1]), (double)PetscRealPart(vx[2])));
174eefd97a2SJed Brown         }
17590d1c1a4SMatthew G. Knepley       }
17690d1c1a4SMatthew G. Knepley     }
1773e72e933SJed Brown     PetscCall(PetscSynchronizedFlush(PETSC_COMM_WORLD, stdout));
17890d1c1a4SMatthew G. Knepley     PetscCall(VecRestoreArrayRead(X, &x));
17990d1c1a4SMatthew G. Knepley     PetscCall(PetscFree(offsets));
180f2c6b1a2SJed Brown     PetscCall(DMGetLocalToGlobalMapping(cdm, &ltog));
181f2c6b1a2SJed Brown     PetscCall(ISLocalToGlobalMappingViewFromOptions(ltog, NULL, "-coord_ltog_view"));
18290d1c1a4SMatthew G. Knepley   }
1833ba16761SJacob Faibussowitsch   PetscFunctionReturn(PETSC_SUCCESS);
18490d1c1a4SMatthew G. Knepley }
18590d1c1a4SMatthew G. Knepley 
186d71ae5a4SJacob Faibussowitsch int main(int argc, char **argv)
187d71ae5a4SJacob Faibussowitsch {
18890d1c1a4SMatthew G. Knepley   DM       dm;
18990d1c1a4SMatthew G. Knepley   AppCtx   user;
1903e72e933SJed Brown   PetscInt depth;
19190d1c1a4SMatthew G. Knepley 
192327415f7SBarry Smith   PetscFunctionBeginUser;
19390d1c1a4SMatthew G. Knepley   PetscCall(PetscInitialize(&argc, &argv, NULL, help));
19490d1c1a4SMatthew G. Knepley   PetscCall(ProcessOptions(PETSC_COMM_WORLD, &user));
19590d1c1a4SMatthew G. Knepley   PetscCall(CreateMesh(PETSC_COMM_WORLD, &user, &dm));
19690d1c1a4SMatthew G. Knepley   PetscCall(SetupDiscretization(dm, &user));
1975f06a3ddSJed Brown   PetscCall(CheckOffsets(dm, &user, NULL, 0, 0));
1983e72e933SJed Brown   PetscCall(DMPlexGetDepth(dm, &depth));
1995f06a3ddSJed Brown   if (depth > 1) PetscCall(CheckOffsets(dm, &user, "Face Sets", user.check_face, 1));
20090d1c1a4SMatthew G. Knepley   PetscCall(DMDestroy(&dm));
20190d1c1a4SMatthew G. Knepley   PetscCall(PetscFinalize());
20290d1c1a4SMatthew G. Knepley   return 0;
20390d1c1a4SMatthew G. Knepley }
20490d1c1a4SMatthew G. Knepley 
20590d1c1a4SMatthew G. Knepley /*TEST
20690d1c1a4SMatthew G. Knepley 
20790d1c1a4SMatthew G. Knepley   test:
20890d1c1a4SMatthew G. Knepley     suffix: 0
20990d1c1a4SMatthew G. Knepley     requires: triangle
21090d1c1a4SMatthew G. Knepley     args: -dm_refine 1 -petscspace_degree 1 -dm_view -offsets_view
21190d1c1a4SMatthew G. Knepley 
21290d1c1a4SMatthew G. Knepley   test:
21390d1c1a4SMatthew G. Knepley     suffix: 1
21490d1c1a4SMatthew G. Knepley     args: -dm_plex_simplex 0 -dm_plex_box_bd periodic,none -dm_plex_box_faces 3,3 -dm_sparse_localize 0 -petscspace_degree 1 \
21590d1c1a4SMatthew G. Knepley           -dm_view -offsets_view
21690d1c1a4SMatthew G. Knepley 
21790d1c1a4SMatthew G. Knepley   test:
21890d1c1a4SMatthew G. Knepley     suffix: cg_2d
21990d1c1a4SMatthew G. Knepley     args: -dm_plex_simplex 0 -dm_plex_box_bd none,none -dm_plex_box_faces 3,3 -petscspace_degree 1 \
22090d1c1a4SMatthew G. Knepley           -dm_view -offsets_view
2213e72e933SJed Brown 
2223e72e933SJed Brown   test:
2233e72e933SJed Brown     suffix: 1d_sfc
2245dca41c3SJed Brown     args: -dm_plex_simplex 0 -dm_plex_dim 1 -dm_plex_shape zbox -dm_plex_box_faces 3 1 -dm_view -coord_ltog_view
2253e72e933SJed Brown 
2263e72e933SJed Brown   test:
2273e72e933SJed Brown     suffix: 2d_sfc
2283e72e933SJed Brown     nsize: 2
2295dca41c3SJed Brown     args: -dm_plex_simplex 0 -dm_plex_dim 2 -dm_plex_shape zbox -dm_plex_box_faces 4,3 -dm_distribute 0 -petscspace_degree 1 -dm_view
2303e72e933SJed Brown 
2314e2e9504SJed Brown   test:
2324e2e9504SJed Brown     suffix: 2d_sfc_periodic
2334e2e9504SJed Brown     nsize: 2
2345dca41c3SJed Brown     args: -dm_plex_simplex 0 -dm_plex_dim 2 -dm_plex_shape zbox -dm_plex_box_faces 4,3 -dm_distribute 0 -petscspace_degree 1 -dm_plex_box_bd periodic,none -dm_view ::ascii_info_detail
2354e2e9504SJed Brown 
2365f06a3ddSJed Brown   testset:
2375dca41c3SJed Brown     args: -dm_plex_simplex 0 -dm_plex_dim 2 -dm_plex_shape zbox -dm_plex_box_faces 3,2 -petscspace_degree 1 -dm_plex_box_bd none,periodic -dm_view ::ascii_info_detail -closure_tensor
2385f06a3ddSJed Brown     nsize: 2
2395f06a3ddSJed Brown     test:
2405f06a3ddSJed Brown       suffix: 2d_sfc_periodic_stranded
2415f06a3ddSJed Brown       args: -dm_distribute 0
2425f06a3ddSJed Brown     test:
2435f06a3ddSJed Brown       suffix: 2d_sfc_periodic_stranded_dist
2445f06a3ddSJed Brown       args: -dm_distribute 1 -petscpartitioner_type simple
2455f06a3ddSJed Brown 
246*52e7713aSMatthew G. Knepley   test:
247*52e7713aSMatthew G. Knepley     suffix: fv_0
248*52e7713aSMatthew G. Knepley     requires: triangle
249*52e7713aSMatthew G. Knepley     args: -dm_refine 1 -use_fe 0 -dm_view -offsets_view
250*52e7713aSMatthew G. Knepley 
25190d1c1a4SMatthew G. Knepley TEST*/
252