xref: /petsc/src/snes/utils/convest.c (revision 98d129c30f3ee9fdddc40fdbc5a989b7be64f888)
1 #include <petscconvest.h> /*I "petscconvest.h" I*/
2 #include <petscdmplex.h>
3 #include <petscds.h>
4 
5 #include <petsc/private/petscconvestimpl.h>
6 
7 static PetscErrorCode zero_private(PetscInt dim, PetscReal time, const PetscReal x[], PetscInt Nc, PetscScalar *u, void *ctx)
8 {
9   PetscInt c;
10   for (c = 0; c < Nc; ++c) u[c] = 0.0;
11   return PETSC_SUCCESS;
12 }
13 
14 /*@
15   PetscConvEstDestroy - Destroys a PETSc convergence estimator `PetscConvEst` object
16 
17   Collective
18 
19   Input Parameter:
20 . ce - The `PetscConvEst` object
21 
22   Level: beginner
23 
24 .seealso: `PetscConvEst`, `PetscConvEstCreate()`, `PetscConvEstGetConvRate()`
25 @*/
26 PetscErrorCode PetscConvEstDestroy(PetscConvEst *ce)
27 {
28   PetscFunctionBegin;
29   if (!*ce) PetscFunctionReturn(PETSC_SUCCESS);
30   PetscValidHeaderSpecific(*ce, PETSC_OBJECT_CLASSID, 1);
31   if (--((PetscObject)*ce)->refct > 0) {
32     *ce = NULL;
33     PetscFunctionReturn(PETSC_SUCCESS);
34   }
35   PetscCall(PetscFree3((*ce)->initGuess, (*ce)->exactSol, (*ce)->ctxs));
36   PetscCall(PetscFree2((*ce)->dofs, (*ce)->errors));
37   PetscCall(PetscHeaderDestroy(ce));
38   PetscFunctionReturn(PETSC_SUCCESS);
39 }
40 
41 /*@
42   PetscConvEstSetFromOptions - Sets a convergence estimator `PetscConvEst` object based on values in the options database
43 
44   Collective
45 
46   Input Parameter:
47 . ce - The `PetscConvEst` object
48 
49   Level: beginner
50 
51 .seealso: `PetscConvEst`, `PetscConvEstCreate()`, `PetscConvEstGetConvRate()`
52 @*/
53 PetscErrorCode PetscConvEstSetFromOptions(PetscConvEst ce)
54 {
55   PetscFunctionBegin;
56   PetscOptionsBegin(PetscObjectComm((PetscObject)ce), "", "Convergence Estimator Options", "PetscConvEst");
57   PetscCall(PetscOptionsInt("-convest_num_refine", "The number of refinements for the convergence check", "PetscConvEst", ce->Nr, &ce->Nr, NULL));
58   PetscCall(PetscOptionsReal("-convest_refine_factor", "The increase in resolution in each dimension", "PetscConvEst", ce->r, &ce->r, NULL));
59   PetscCall(PetscOptionsBool("-convest_monitor", "Monitor the error for each convergence check", "PetscConvEst", ce->monitor, &ce->monitor, NULL));
60   PetscCall(PetscOptionsBool("-convest_no_refine", "Debugging flag to run on the same mesh each time", "PetscConvEst", ce->noRefine, &ce->noRefine, NULL));
61   PetscOptionsEnd();
62   PetscFunctionReturn(PETSC_SUCCESS);
63 }
64 
65 /*@
66   PetscConvEstView - Views a `PetscConvEst` object
67 
68   Collective
69 
70   Input Parameters:
71 + ce     - The `PetscConvEst` object
72 - viewer - The `PetscViewer`
73 
74   Level: beginner
75 
76 .seealso: `PetscConvEst`, `PetscViewer`, `PetscConvEstCreate()`, `PetscConvEstGetConvRate()`
77 @*/
78 PetscErrorCode PetscConvEstView(PetscConvEst ce, PetscViewer viewer)
79 {
80   PetscFunctionBegin;
81   PetscCall(PetscObjectPrintClassNamePrefixType((PetscObject)ce, viewer));
82   PetscCall(PetscViewerASCIIPrintf(viewer, "ConvEst with %" PetscInt_FMT " levels\n", ce->Nr + 1));
83   PetscFunctionReturn(PETSC_SUCCESS);
84 }
85 
86 /*@
87   PetscConvEstGetSolver - Gets the solver used to produce discrete solutions
88 
89   Not Collective
90 
91   Input Parameter:
92 . ce - The `PetscConvEst` object
93 
94   Output Parameter:
95 . solver - The solver
96 
97   Level: intermediate
98 
99 .seealso: `PetscConvEst`, `PetscConvEstSetSolver()`, `PetscConvEstCreate()`, `PetscConvEstGetConvRate()`
100 @*/
101 PetscErrorCode PetscConvEstGetSolver(PetscConvEst ce, PetscObject *solver)
102 {
103   PetscFunctionBegin;
104   PetscValidHeaderSpecific(ce, PETSC_OBJECT_CLASSID, 1);
105   PetscAssertPointer(solver, 2);
106   *solver = ce->solver;
107   PetscFunctionReturn(PETSC_SUCCESS);
108 }
109 
110 /*@
111   PetscConvEstSetSolver - Sets the solver used to produce discrete solutions
112 
113   Not Collective
114 
115   Input Parameters:
116 + ce     - The `PetscConvEst` object
117 - solver - The solver, must be a `KSP`, `SNES`, or `TS` object with an attached `DM`/`DS`, that can compute an exact solution
118 
119   Level: intermediate
120 
121 .seealso: `PetscConvEst`, `PetscConvEstGetSNES()`, `PetscConvEstCreate()`, `PetscConvEstGetConvRate()`
122 @*/
123 PetscErrorCode PetscConvEstSetSolver(PetscConvEst ce, PetscObject solver)
124 {
125   PetscFunctionBegin;
126   PetscValidHeaderSpecific(ce, PETSC_OBJECT_CLASSID, 1);
127   PetscValidHeader(solver, 2);
128   ce->solver = solver;
129   PetscUseTypeMethod(ce, setsolver, solver);
130   PetscFunctionReturn(PETSC_SUCCESS);
131 }
132 
133 /*@
134   PetscConvEstSetUp - After the solver is specified, create data structures needed for estimating convergence
135 
136   Collective
137 
138   Input Parameter:
139 . ce - The `PetscConvEst` object
140 
141   Level: beginner
142 
143 .seealso: `PetscConvEst`, `PetscConvEstCreate()`, `PetscConvEstGetConvRate()`
144 @*/
145 PetscErrorCode PetscConvEstSetUp(PetscConvEst ce)
146 {
147   PetscInt Nf, f, Nds, s;
148 
149   PetscFunctionBegin;
150   PetscCall(DMGetNumFields(ce->idm, &Nf));
151   ce->Nf = PetscMax(Nf, 1);
152   PetscCall(PetscMalloc2((ce->Nr + 1) * ce->Nf, &ce->dofs, (ce->Nr + 1) * ce->Nf, &ce->errors));
153   PetscCall(PetscCalloc3(ce->Nf, &ce->initGuess, ce->Nf, &ce->exactSol, ce->Nf, &ce->ctxs));
154   for (f = 0; f < Nf; ++f) ce->initGuess[f] = zero_private;
155   PetscCall(DMGetNumDS(ce->idm, &Nds));
156   for (s = 0; s < Nds; ++s) {
157     PetscDS         ds;
158     DMLabel         label;
159     IS              fieldIS;
160     const PetscInt *fields;
161     PetscInt        dsNf;
162 
163     PetscCall(DMGetRegionNumDS(ce->idm, s, &label, &fieldIS, &ds, NULL));
164     PetscCall(PetscDSGetNumFields(ds, &dsNf));
165     if (fieldIS) PetscCall(ISGetIndices(fieldIS, &fields));
166     for (f = 0; f < dsNf; ++f) {
167       const PetscInt field = fields[f];
168       PetscCall(PetscDSGetExactSolution(ds, field, &ce->exactSol[field], &ce->ctxs[field]));
169     }
170     if (fieldIS) PetscCall(ISRestoreIndices(fieldIS, &fields));
171   }
172   for (f = 0; f < Nf; ++f) PetscCheck(ce->exactSol[f], PetscObjectComm((PetscObject)ce), PETSC_ERR_ARG_WRONG, "DS must contain exact solution functions in order to estimate convergence, missing for field %" PetscInt_FMT, f);
173   PetscFunctionReturn(PETSC_SUCCESS);
174 }
175 
176 PetscErrorCode PetscConvEstComputeInitialGuess(PetscConvEst ce, PetscInt r, DM dm, Vec u)
177 {
178   PetscFunctionBegin;
179   PetscValidHeaderSpecific(ce, PETSC_OBJECT_CLASSID, 1);
180   if (dm) PetscValidHeaderSpecific(dm, DM_CLASSID, 3);
181   PetscValidHeaderSpecific(u, VEC_CLASSID, 4);
182   PetscUseTypeMethod(ce, initguess, r, dm, u);
183   PetscFunctionReturn(PETSC_SUCCESS);
184 }
185 
186 PetscErrorCode PetscConvEstComputeError(PetscConvEst ce, PetscInt r, DM dm, Vec u, PetscReal errors[])
187 {
188   PetscFunctionBegin;
189   PetscValidHeaderSpecific(ce, PETSC_OBJECT_CLASSID, 1);
190   if (dm) PetscValidHeaderSpecific(dm, DM_CLASSID, 3);
191   PetscValidHeaderSpecific(u, VEC_CLASSID, 4);
192   PetscAssertPointer(errors, 5);
193   PetscUseTypeMethod(ce, computeerror, r, dm, u, errors);
194   PetscFunctionReturn(PETSC_SUCCESS);
195 }
196 
197 /*@
198   PetscConvEstMonitorDefault - Monitors the convergence estimation loop
199 
200   Collective
201 
202   Input Parameters:
203 + ce - The `PetscConvEst` object
204 - r  - The refinement level
205 
206   Options Database Key:
207 . -convest_monitor - Activate the monitor
208 
209   Level: intermediate
210 
211 .seealso: `PetscConvEst`, `PetscConvEstCreate()`, `PetscConvEstGetConvRate()`, `SNESSolve()`, `TSSolve()`
212 @*/
213 PetscErrorCode PetscConvEstMonitorDefault(PetscConvEst ce, PetscInt r)
214 {
215   MPI_Comm comm;
216   PetscInt f;
217 
218   PetscFunctionBegin;
219   if (ce->monitor) {
220     PetscInt  *dofs   = &ce->dofs[r * ce->Nf];
221     PetscReal *errors = &ce->errors[r * ce->Nf];
222 
223     PetscCall(PetscObjectGetComm((PetscObject)ce, &comm));
224     PetscCall(PetscPrintf(comm, "N: "));
225     if (ce->Nf > 1) PetscCall(PetscPrintf(comm, "["));
226     for (f = 0; f < ce->Nf; ++f) {
227       if (f > 0) PetscCall(PetscPrintf(comm, ", "));
228       PetscCall(PetscPrintf(comm, "%7" PetscInt_FMT, dofs[f]));
229     }
230     if (ce->Nf > 1) PetscCall(PetscPrintf(comm, "]"));
231     PetscCall(PetscPrintf(comm, "  "));
232     PetscCall(PetscPrintf(comm, "L_2 Error: "));
233     if (ce->Nf > 1) PetscCall(PetscPrintf(comm, "["));
234     for (f = 0; f < ce->Nf; ++f) {
235       if (f > 0) PetscCall(PetscPrintf(comm, ", "));
236       if (errors[f] < 1.0e-11) PetscCall(PetscPrintf(comm, "< 1e-11"));
237       else PetscCall(PetscPrintf(comm, "%g", (double)errors[f]));
238     }
239     if (ce->Nf > 1) PetscCall(PetscPrintf(comm, "]"));
240     PetscCall(PetscPrintf(comm, "\n"));
241   }
242   PetscFunctionReturn(PETSC_SUCCESS);
243 }
244 
245 static PetscErrorCode PetscConvEstSetSNES_Private(PetscConvEst ce, PetscObject solver)
246 {
247   PetscClassId id;
248 
249   PetscFunctionBegin;
250   PetscCall(PetscObjectGetClassId(ce->solver, &id));
251   PetscCheck(id == SNES_CLASSID, PetscObjectComm((PetscObject)ce), PETSC_ERR_ARG_WRONG, "Solver was not a SNES");
252   PetscCall(SNESGetDM((SNES)ce->solver, &ce->idm));
253   PetscFunctionReturn(PETSC_SUCCESS);
254 }
255 
256 static PetscErrorCode PetscConvEstInitGuessSNES_Private(PetscConvEst ce, PetscInt r, DM dm, Vec u)
257 {
258   PetscFunctionBegin;
259   PetscCall(DMProjectFunction(dm, 0.0, ce->initGuess, ce->ctxs, INSERT_VALUES, u));
260   PetscFunctionReturn(PETSC_SUCCESS);
261 }
262 
263 static PetscErrorCode PetscConvEstComputeErrorSNES_Private(PetscConvEst ce, PetscInt r, DM dm, Vec u, PetscReal errors[])
264 {
265   PetscFunctionBegin;
266   PetscCall(DMComputeL2FieldDiff(dm, 0.0, ce->exactSol, ce->ctxs, u, errors));
267   PetscFunctionReturn(PETSC_SUCCESS);
268 }
269 
270 static PetscErrorCode PetscConvEstSetJacobianNullSpace_Private(PetscConvEst ce, SNES snes)
271 {
272   DM       dm;
273   PetscInt f;
274 
275   PetscFunctionBegin;
276   PetscCall(SNESGetDM(snes, &dm));
277   for (f = 0; f < ce->Nf; ++f) {
278     PetscErrorCode (*nspconstr)(DM, PetscInt, PetscInt, MatNullSpace *);
279 
280     PetscCall(DMGetNullSpaceConstructor(dm, f, &nspconstr));
281     if (nspconstr) {
282       MatNullSpace nullsp;
283       Mat          J;
284 
285       PetscCall((*nspconstr)(dm, f, f, &nullsp));
286       PetscCall(SNESSetUp(snes));
287       PetscCall(SNESGetJacobian(snes, &J, NULL, NULL, NULL));
288       PetscCall(MatSetNullSpace(J, nullsp));
289       PetscCall(MatNullSpaceDestroy(&nullsp));
290       break;
291     }
292   }
293   PetscFunctionReturn(PETSC_SUCCESS);
294 }
295 
296 static PetscErrorCode PetscConvEstGetConvRateSNES_Private(PetscConvEst ce, PetscReal alpha[])
297 {
298   SNES        snes = (SNES)ce->solver;
299   DM         *dm;
300   PetscObject disc;
301   PetscReal  *x, *y, slope, intercept;
302   PetscInt    Nr = ce->Nr, r, f, dim, oldlevel, oldnlev;
303   void       *ctx;
304 
305   PetscFunctionBegin;
306   PetscCheck(ce->r == 2.0, PetscObjectComm((PetscObject)ce), PETSC_ERR_SUP, "Only refinement factor 2 is currently supported (not %g)", (double)ce->r);
307   PetscCall(DMGetDimension(ce->idm, &dim));
308   PetscCall(DMGetApplicationContext(ce->idm, &ctx));
309   PetscCall(DMPlexSetRefinementUniform(ce->idm, PETSC_TRUE));
310   PetscCall(DMGetRefineLevel(ce->idm, &oldlevel));
311   PetscCall(PetscMalloc1((Nr + 1), &dm));
312   /* Loop over meshes */
313   dm[0] = ce->idm;
314   for (r = 0; r <= Nr; ++r) {
315     Vec           u;
316     PetscLogStage stage;
317     char          stageName[PETSC_MAX_PATH_LEN];
318     const char   *dmname, *uname;
319 
320     PetscCall(PetscSNPrintf(stageName, PETSC_MAX_PATH_LEN - 1, "ConvEst Refinement Level %" PetscInt_FMT, r));
321     PetscCall(PetscLogStageGetId(stageName, &stage));
322     if (stage < 0) PetscCall(PetscLogStageRegister(stageName, &stage));
323     PetscCall(PetscLogStagePush(stage));
324     if (r > 0) {
325       if (!ce->noRefine) {
326         PetscCall(DMRefine(dm[r - 1], MPI_COMM_NULL, &dm[r]));
327         PetscCall(DMSetCoarseDM(dm[r], dm[r - 1]));
328       } else {
329         DM cdm, rcdm;
330 
331         PetscCall(DMClone(dm[r - 1], &dm[r]));
332         PetscCall(DMCopyDisc(dm[r - 1], dm[r]));
333         PetscCall(DMGetCoordinateDM(dm[r - 1], &cdm));
334         PetscCall(DMGetCoordinateDM(dm[r], &rcdm));
335         PetscCall(DMCopyDisc(cdm, rcdm));
336       }
337       PetscCall(DMCopyTransform(ce->idm, dm[r]));
338       PetscCall(PetscObjectGetName((PetscObject)dm[r - 1], &dmname));
339       PetscCall(PetscObjectSetName((PetscObject)dm[r], dmname));
340       for (f = 0; f < ce->Nf; ++f) {
341         PetscErrorCode (*nspconstr)(DM, PetscInt, PetscInt, MatNullSpace *);
342 
343         PetscCall(DMGetNullSpaceConstructor(dm[r - 1], f, &nspconstr));
344         PetscCall(DMSetNullSpaceConstructor(dm[r], f, nspconstr));
345       }
346     }
347     PetscCall(DMViewFromOptions(dm[r], NULL, "-conv_dm_view"));
348     /* Create solution */
349     PetscCall(DMCreateGlobalVector(dm[r], &u));
350     PetscCall(DMGetField(dm[r], 0, NULL, &disc));
351     PetscCall(PetscObjectGetName(disc, &uname));
352     PetscCall(PetscObjectSetName((PetscObject)u, uname));
353     /* Setup solver */
354     PetscCall(SNESReset(snes));
355     PetscCall(SNESSetDM(snes, dm[r]));
356     PetscCall(DMPlexSetSNESLocalFEM(dm[r], PETSC_FALSE, ctx));
357     PetscCall(SNESSetFromOptions(snes));
358     /* Set nullspace for Jacobian */
359     PetscCall(PetscConvEstSetJacobianNullSpace_Private(ce, snes));
360     /* Create initial guess */
361     PetscCall(PetscConvEstComputeInitialGuess(ce, r, dm[r], u));
362     PetscCall(SNESSolve(snes, NULL, u));
363     PetscCall(PetscLogEventBegin(ce->event, ce, 0, 0, 0));
364     PetscCall(PetscConvEstComputeError(ce, r, dm[r], u, &ce->errors[r * ce->Nf]));
365     PetscCall(PetscLogEventEnd(ce->event, ce, 0, 0, 0));
366     for (f = 0; f < ce->Nf; ++f) {
367       PetscSection s, fs;
368       PetscInt     lsize;
369 
370       /* Could use DMGetOutputDM() to add in Dirichlet dofs */
371       PetscCall(DMGetLocalSection(dm[r], &s));
372       PetscCall(PetscSectionGetField(s, f, &fs));
373       PetscCall(PetscSectionGetConstrainedStorageSize(fs, &lsize));
374       PetscCall(MPIU_Allreduce(&lsize, &ce->dofs[r * ce->Nf + f], 1, MPIU_INT, MPI_SUM, PetscObjectComm((PetscObject)snes)));
375       PetscCall(PetscLogEventSetDof(ce->event, f, ce->dofs[r * ce->Nf + f]));
376       PetscCall(PetscLogEventSetError(ce->event, f, ce->errors[r * ce->Nf + f]));
377     }
378     /* Monitor */
379     PetscCall(PetscConvEstMonitorDefault(ce, r));
380     if (!r) {
381       /* PCReset() does not wipe out the level structure */
382       KSP ksp;
383       PC  pc;
384 
385       PetscCall(SNESGetKSP(snes, &ksp));
386       PetscCall(KSPGetPC(ksp, &pc));
387       PetscCall(PCMGGetLevels(pc, &oldnlev));
388     }
389     /* Cleanup */
390     PetscCall(VecDestroy(&u));
391     PetscCall(PetscLogStagePop());
392   }
393   for (r = 1; r <= Nr; ++r) PetscCall(DMDestroy(&dm[r]));
394   /* Fit convergence rate */
395   PetscCall(PetscMalloc2(Nr + 1, &x, Nr + 1, &y));
396   for (f = 0; f < ce->Nf; ++f) {
397     for (r = 0; r <= Nr; ++r) {
398       x[r] = PetscLog10Real(ce->dofs[r * ce->Nf + f]);
399       y[r] = PetscLog10Real(ce->errors[r * ce->Nf + f]);
400     }
401     PetscCall(PetscLinearRegression(Nr + 1, x, y, &slope, &intercept));
402     /* Since h^{-dim} = N, lg err = s lg N + b = -s dim lg h + b */
403     alpha[f] = -slope * dim;
404   }
405   PetscCall(PetscFree2(x, y));
406   PetscCall(PetscFree(dm));
407   /* Restore solver */
408   PetscCall(SNESReset(snes));
409   {
410     /* PCReset() does not wipe out the level structure */
411     KSP ksp;
412     PC  pc;
413 
414     PetscCall(SNESGetKSP(snes, &ksp));
415     PetscCall(KSPGetPC(ksp, &pc));
416     PetscCall(PCMGSetLevels(pc, oldnlev, NULL));
417     PetscCall(DMSetRefineLevel(ce->idm, oldlevel)); /* The damn DMCoarsen() calls in PCMG can reset this */
418   }
419   PetscCall(SNESSetDM(snes, ce->idm));
420   PetscCall(DMPlexSetSNESLocalFEM(ce->idm, PETSC_FALSE, ctx));
421   PetscCall(SNESSetFromOptions(snes));
422   PetscCall(PetscConvEstSetJacobianNullSpace_Private(ce, snes));
423   PetscFunctionReturn(PETSC_SUCCESS);
424 }
425 
426 /*@
427   PetscConvEstGetConvRate - Returns an estimate of the convergence rate for the discretization
428 
429   Not Collective
430 
431   Input Parameter:
432 . ce - The `PetscConvEst` object
433 
434   Output Parameter:
435 . alpha - The convergence rate for each field
436 
437   Options Database Keys:
438 + -snes_convergence_estimate - Execute convergence estimation inside `SNESSolve()` and print out the rate
439 - -ts_convergence_estimate   - Execute convergence estimation inside `TSSolve()` and print out the rate
440 
441   Level: intermediate
442 
443   Notes:
444   The convergence rate alpha is defined by
445 $ || u_\Delta - u_exact || < C \Delta^alpha
446   where u_\Delta is the discrete solution, and Delta is a measure of the discretization size. We usually use h for the
447   spatial resolution and \Delta t for the temporal resolution.
448 
449   We solve a series of problems using increasing resolution (refined meshes or decreased timesteps), calculate an error
450   based upon the exact solution in the `PetscDS`, and then fit the result to our model above using linear regression.
451 
452 .seealso: `PetscConvEstSetSolver()`, `PetscConvEstCreate()`, `SNESSolve()`, `TSSolve()`
453 @*/
454 PetscErrorCode PetscConvEstGetConvRate(PetscConvEst ce, PetscReal alpha[])
455 {
456   PetscInt f;
457 
458   PetscFunctionBegin;
459   if (ce->event < 0) PetscCall(PetscLogEventRegister("ConvEst Error", PETSC_OBJECT_CLASSID, &ce->event));
460   for (f = 0; f < ce->Nf; ++f) alpha[f] = 0.0;
461   PetscUseTypeMethod(ce, getconvrate, alpha);
462   PetscFunctionReturn(PETSC_SUCCESS);
463 }
464 
465 /*@
466   PetscConvEstRateView - Displays the convergence rate obtained from `PetscConvEstGetConvRate()` using a `PetscViewer`
467 
468   Collective
469 
470   Input Parameters:
471 + ce     - iterative context obtained from `SNESCreate()`
472 . alpha  - the convergence rate for each field
473 - viewer - the viewer to display the reason
474 
475   Options Database Key:
476 . -snes_convergence_estimate - print the convergence rate
477 
478   Level: developer
479 
480 .seealso: `PetscConvEst`, `PetscConvEstGetConvRate()`
481 @*/
482 PetscErrorCode PetscConvEstRateView(PetscConvEst ce, const PetscReal alpha[], PetscViewer viewer)
483 {
484   PetscBool isAscii;
485 
486   PetscFunctionBegin;
487   PetscCall(PetscObjectTypeCompare((PetscObject)viewer, PETSCVIEWERASCII, &isAscii));
488   if (isAscii) {
489     PetscInt Nf = ce->Nf, f;
490 
491     PetscCall(PetscViewerASCIIAddTab(viewer, ((PetscObject)ce)->tablevel));
492     PetscCall(PetscViewerASCIIPrintf(viewer, "L_2 convergence rate: "));
493     if (Nf > 1) PetscCall(PetscViewerASCIIPrintf(viewer, "["));
494     for (f = 0; f < Nf; ++f) {
495       if (f > 0) PetscCall(PetscViewerASCIIPrintf(viewer, ", "));
496       PetscCall(PetscViewerASCIIPrintf(viewer, "%#.2g", (double)alpha[f]));
497     }
498     if (Nf > 1) PetscCall(PetscViewerASCIIPrintf(viewer, "]"));
499     PetscCall(PetscViewerASCIIPrintf(viewer, "\n"));
500     PetscCall(PetscViewerASCIISubtractTab(viewer, ((PetscObject)ce)->tablevel));
501   }
502   PetscFunctionReturn(PETSC_SUCCESS);
503 }
504 
505 /*@
506   PetscConvEstCreate - Create a `PetscConvEst` object
507 
508   Collective
509 
510   Input Parameter:
511 . comm - The communicator for the `PetscConvEst` object
512 
513   Output Parameter:
514 . ce - The `PetscConvEst` object
515 
516   Level: beginner
517 
518 .seealso: `PetscConvEst`, `PetscConvEstDestroy()`, `PetscConvEstGetConvRate()`, `DMAdaptorCreate()`, `DMAdaptor`
519 @*/
520 PetscErrorCode PetscConvEstCreate(MPI_Comm comm, PetscConvEst *ce)
521 {
522   PetscFunctionBegin;
523   PetscAssertPointer(ce, 2);
524   PetscCall(PetscSysInitializePackage());
525   PetscCall(PetscHeaderCreate(*ce, PETSC_OBJECT_CLASSID, "PetscConvEst", "ConvergenceEstimator", "SNES", comm, PetscConvEstDestroy, PetscConvEstView));
526   (*ce)->monitor           = PETSC_FALSE;
527   (*ce)->r                 = 2.0;
528   (*ce)->Nr                = 4;
529   (*ce)->event             = -1;
530   (*ce)->ops->setsolver    = PetscConvEstSetSNES_Private;
531   (*ce)->ops->initguess    = PetscConvEstInitGuessSNES_Private;
532   (*ce)->ops->computeerror = PetscConvEstComputeErrorSNES_Private;
533   (*ce)->ops->getconvrate  = PetscConvEstGetConvRateSNES_Private;
534   PetscFunctionReturn(PETSC_SUCCESS);
535 }
536