xref: /petsc/src/dm/dt/interface/dt.c (revision d2522c19e8fa9bca20aaca277941d9a63e71db6a)
1 /* Discretization tools */
2 
3 #include <petscdt.h> /*I "petscdt.h" I*/
4 #include <petscblaslapack.h>
5 #include <petsc/private/petscimpl.h>
6 #include <petsc/private/dtimpl.h>
7 #include <petscviewer.h>
8 #include <petscdmplex.h>
9 #include <petscdmshell.h>
10 
11 #if defined(PETSC_HAVE_MPFR)
12 #include <mpfr.h>
13 #endif
14 
15 const char *const        PetscDTNodeTypes_shifted[] = {"default", "gaussjacobi", "equispaced", "tanhsinh", "PETSCDTNODES_", NULL};
16 const char *const *const PetscDTNodeTypes           = PetscDTNodeTypes_shifted + 1;
17 
18 const char *const        PetscDTSimplexQuadratureTypes_shifted[] = {"default", "conic", "minsym", "PETSCDTSIMPLEXQUAD_", NULL};
19 const char *const *const PetscDTSimplexQuadratureTypes           = PetscDTSimplexQuadratureTypes_shifted + 1;
20 
21 static PetscBool GolubWelschCite       = PETSC_FALSE;
22 const char       GolubWelschCitation[] = "@article{GolubWelsch1969,\n"
23                                          "  author  = {Golub and Welsch},\n"
24                                          "  title   = {Calculation of Quadrature Rules},\n"
25                                          "  journal = {Math. Comp.},\n"
26                                          "  volume  = {23},\n"
27                                          "  number  = {106},\n"
28                                          "  pages   = {221--230},\n"
29                                          "  year    = {1969}\n}\n";
30 
31 /* Numerical tests in src/dm/dt/tests/ex1.c show that when computing the nodes and weights of Gauss-Jacobi
32    quadrature rules:
33 
34    - in double precision, Newton's method and Golub & Welsch both work for moderate degrees (< 100),
35    - in single precision, Newton's method starts producing incorrect roots around n = 15, but
36      the weights from Golub & Welsch become a problem before then: they produces errors
37      in computing the Jacobi-polynomial Gram matrix around n = 6.
38 
39    So we default to Newton's method (required fewer dependencies) */
40 PetscBool PetscDTGaussQuadratureNewton_Internal = PETSC_TRUE;
41 
42 PetscClassId PETSCQUADRATURE_CLASSID = 0;
43 
44 /*@
45   PetscQuadratureCreate - Create a PetscQuadrature object
46 
47   Collective
48 
49   Input Parameter:
50 . comm - The communicator for the PetscQuadrature object
51 
52   Output Parameter:
53 . q  - The PetscQuadrature object
54 
55   Level: beginner
56 
57 .seealso: `PetscQuadratureDestroy()`, `PetscQuadratureGetData()`
58 @*/
59 PetscErrorCode PetscQuadratureCreate(MPI_Comm comm, PetscQuadrature *q) {
60   PetscFunctionBegin;
61   PetscValidPointer(q, 2);
62   PetscCall(DMInitializePackage());
63   PetscCall(PetscHeaderCreate(*q, PETSCQUADRATURE_CLASSID, "PetscQuadrature", "Quadrature", "DT", comm, PetscQuadratureDestroy, PetscQuadratureView));
64   (*q)->dim       = -1;
65   (*q)->Nc        = 1;
66   (*q)->order     = -1;
67   (*q)->numPoints = 0;
68   (*q)->points    = NULL;
69   (*q)->weights   = NULL;
70   PetscFunctionReturn(0);
71 }
72 
73 /*@
74   PetscQuadratureDuplicate - Create a deep copy of the PetscQuadrature object
75 
76   Collective on q
77 
78   Input Parameter:
79 . q  - The PetscQuadrature object
80 
81   Output Parameter:
82 . r  - The new PetscQuadrature object
83 
84   Level: beginner
85 
86 .seealso: `PetscQuadratureCreate()`, `PetscQuadratureDestroy()`, `PetscQuadratureGetData()`
87 @*/
88 PetscErrorCode PetscQuadratureDuplicate(PetscQuadrature q, PetscQuadrature *r) {
89   PetscInt         order, dim, Nc, Nq;
90   const PetscReal *points, *weights;
91   PetscReal       *p, *w;
92 
93   PetscFunctionBegin;
94   PetscValidPointer(q, 1);
95   PetscCall(PetscQuadratureCreate(PetscObjectComm((PetscObject)q), r));
96   PetscCall(PetscQuadratureGetOrder(q, &order));
97   PetscCall(PetscQuadratureSetOrder(*r, order));
98   PetscCall(PetscQuadratureGetData(q, &dim, &Nc, &Nq, &points, &weights));
99   PetscCall(PetscMalloc1(Nq * dim, &p));
100   PetscCall(PetscMalloc1(Nq * Nc, &w));
101   PetscCall(PetscArraycpy(p, points, Nq * dim));
102   PetscCall(PetscArraycpy(w, weights, Nc * Nq));
103   PetscCall(PetscQuadratureSetData(*r, dim, Nc, Nq, p, w));
104   PetscFunctionReturn(0);
105 }
106 
107 /*@
108   PetscQuadratureDestroy - Destroys a PetscQuadrature object
109 
110   Collective on q
111 
112   Input Parameter:
113 . q  - The PetscQuadrature object
114 
115   Level: beginner
116 
117 .seealso: `PetscQuadratureCreate()`, `PetscQuadratureGetData()`
118 @*/
119 PetscErrorCode PetscQuadratureDestroy(PetscQuadrature *q) {
120   PetscFunctionBegin;
121   if (!*q) PetscFunctionReturn(0);
122   PetscValidHeaderSpecific((*q), PETSCQUADRATURE_CLASSID, 1);
123   if (--((PetscObject)(*q))->refct > 0) {
124     *q = NULL;
125     PetscFunctionReturn(0);
126   }
127   PetscCall(PetscFree((*q)->points));
128   PetscCall(PetscFree((*q)->weights));
129   PetscCall(PetscHeaderDestroy(q));
130   PetscFunctionReturn(0);
131 }
132 
133 /*@
134   PetscQuadratureGetOrder - Return the order of the method
135 
136   Not collective
137 
138   Input Parameter:
139 . q - The PetscQuadrature object
140 
141   Output Parameter:
142 . order - The order of the quadrature, i.e. the highest degree polynomial that is exactly integrated
143 
144   Level: intermediate
145 
146 .seealso: `PetscQuadratureSetOrder()`, `PetscQuadratureGetData()`, `PetscQuadratureSetData()`
147 @*/
148 PetscErrorCode PetscQuadratureGetOrder(PetscQuadrature q, PetscInt *order) {
149   PetscFunctionBegin;
150   PetscValidHeaderSpecific(q, PETSCQUADRATURE_CLASSID, 1);
151   PetscValidIntPointer(order, 2);
152   *order = q->order;
153   PetscFunctionReturn(0);
154 }
155 
156 /*@
157   PetscQuadratureSetOrder - Return the order of the method
158 
159   Not collective
160 
161   Input Parameters:
162 + q - The PetscQuadrature object
163 - order - The order of the quadrature, i.e. the highest degree polynomial that is exactly integrated
164 
165   Level: intermediate
166 
167 .seealso: `PetscQuadratureGetOrder()`, `PetscQuadratureGetData()`, `PetscQuadratureSetData()`
168 @*/
169 PetscErrorCode PetscQuadratureSetOrder(PetscQuadrature q, PetscInt order) {
170   PetscFunctionBegin;
171   PetscValidHeaderSpecific(q, PETSCQUADRATURE_CLASSID, 1);
172   q->order = order;
173   PetscFunctionReturn(0);
174 }
175 
176 /*@
177   PetscQuadratureGetNumComponents - Return the number of components for functions to be integrated
178 
179   Not collective
180 
181   Input Parameter:
182 . q - The PetscQuadrature object
183 
184   Output Parameter:
185 . Nc - The number of components
186 
187   Note: We are performing an integral int f(x) . w(x) dx, where both f and w (the weight) have Nc components.
188 
189   Level: intermediate
190 
191 .seealso: `PetscQuadratureSetNumComponents()`, `PetscQuadratureGetData()`, `PetscQuadratureSetData()`
192 @*/
193 PetscErrorCode PetscQuadratureGetNumComponents(PetscQuadrature q, PetscInt *Nc) {
194   PetscFunctionBegin;
195   PetscValidHeaderSpecific(q, PETSCQUADRATURE_CLASSID, 1);
196   PetscValidIntPointer(Nc, 2);
197   *Nc = q->Nc;
198   PetscFunctionReturn(0);
199 }
200 
201 /*@
202   PetscQuadratureSetNumComponents - Return the number of components for functions to be integrated
203 
204   Not collective
205 
206   Input Parameters:
207 + q  - The PetscQuadrature object
208 - Nc - The number of components
209 
210   Note: We are performing an integral int f(x) . w(x) dx, where both f and w (the weight) have Nc components.
211 
212   Level: intermediate
213 
214 .seealso: `PetscQuadratureGetNumComponents()`, `PetscQuadratureGetData()`, `PetscQuadratureSetData()`
215 @*/
216 PetscErrorCode PetscQuadratureSetNumComponents(PetscQuadrature q, PetscInt Nc) {
217   PetscFunctionBegin;
218   PetscValidHeaderSpecific(q, PETSCQUADRATURE_CLASSID, 1);
219   q->Nc = Nc;
220   PetscFunctionReturn(0);
221 }
222 
223 /*@C
224   PetscQuadratureGetData - Returns the data defining the quadrature
225 
226   Not collective
227 
228   Input Parameter:
229 . q  - The PetscQuadrature object
230 
231   Output Parameters:
232 + dim - The spatial dimension
233 . Nc - The number of components
234 . npoints - The number of quadrature points
235 . points - The coordinates of each quadrature point
236 - weights - The weight of each quadrature point
237 
238   Level: intermediate
239 
240   Fortran Notes:
241     From Fortran you must call PetscQuadratureRestoreData() when you are done with the data
242 
243 .seealso: `PetscQuadratureCreate()`, `PetscQuadratureSetData()`
244 @*/
245 PetscErrorCode PetscQuadratureGetData(PetscQuadrature q, PetscInt *dim, PetscInt *Nc, PetscInt *npoints, const PetscReal *points[], const PetscReal *weights[]) {
246   PetscFunctionBegin;
247   PetscValidHeaderSpecific(q, PETSCQUADRATURE_CLASSID, 1);
248   if (dim) {
249     PetscValidIntPointer(dim, 2);
250     *dim = q->dim;
251   }
252   if (Nc) {
253     PetscValidIntPointer(Nc, 3);
254     *Nc = q->Nc;
255   }
256   if (npoints) {
257     PetscValidIntPointer(npoints, 4);
258     *npoints = q->numPoints;
259   }
260   if (points) {
261     PetscValidPointer(points, 5);
262     *points = q->points;
263   }
264   if (weights) {
265     PetscValidPointer(weights, 6);
266     *weights = q->weights;
267   }
268   PetscFunctionReturn(0);
269 }
270 
271 /*@
272   PetscQuadratureEqual - determine whether two quadratures are equivalent
273 
274   Input Parameters:
275 + A - A PetscQuadrature object
276 - B - Another PetscQuadrature object
277 
278   Output Parameters:
279 . equal - PETSC_TRUE if the quadratures are the same
280 
281   Level: intermediate
282 
283 .seealso: `PetscQuadratureCreate()`
284 @*/
285 PetscErrorCode PetscQuadratureEqual(PetscQuadrature A, PetscQuadrature B, PetscBool *equal) {
286   PetscFunctionBegin;
287   PetscValidHeaderSpecific(A, PETSCQUADRATURE_CLASSID, 1);
288   PetscValidHeaderSpecific(B, PETSCQUADRATURE_CLASSID, 2);
289   PetscValidBoolPointer(equal, 3);
290   *equal = PETSC_FALSE;
291   if (A->dim != B->dim || A->Nc != B->Nc || A->order != B->order || A->numPoints != B->numPoints) { PetscFunctionReturn(0); }
292   for (PetscInt i = 0; i < A->numPoints * A->dim; i++) {
293     if (PetscAbsReal(A->points[i] - B->points[i]) > PETSC_SMALL) { PetscFunctionReturn(0); }
294   }
295   if (!A->weights && !B->weights) {
296     *equal = PETSC_TRUE;
297     PetscFunctionReturn(0);
298   }
299   if (A->weights && B->weights) {
300     for (PetscInt i = 0; i < A->numPoints; i++) {
301       if (PetscAbsReal(A->weights[i] - B->weights[i]) > PETSC_SMALL) { PetscFunctionReturn(0); }
302     }
303     *equal = PETSC_TRUE;
304   }
305   PetscFunctionReturn(0);
306 }
307 
308 static PetscErrorCode PetscDTJacobianInverse_Internal(PetscInt m, PetscInt n, const PetscReal J[], PetscReal Jinv[]) {
309   PetscScalar *Js, *Jinvs;
310   PetscInt     i, j, k;
311   PetscBLASInt bm, bn, info;
312 
313   PetscFunctionBegin;
314   if (!m || !n) PetscFunctionReturn(0);
315   PetscCall(PetscBLASIntCast(m, &bm));
316   PetscCall(PetscBLASIntCast(n, &bn));
317 #if defined(PETSC_USE_COMPLEX)
318   PetscCall(PetscMalloc2(m * n, &Js, m * n, &Jinvs));
319   for (i = 0; i < m * n; i++) Js[i] = J[i];
320 #else
321   Js    = (PetscReal *)J;
322   Jinvs = Jinv;
323 #endif
324   if (m == n) {
325     PetscBLASInt *pivots;
326     PetscScalar  *W;
327 
328     PetscCall(PetscMalloc2(m, &pivots, m, &W));
329 
330     PetscCall(PetscArraycpy(Jinvs, Js, m * m));
331     PetscCallBLAS("LAPACKgetrf", LAPACKgetrf_(&bm, &bm, Jinvs, &bm, pivots, &info));
332     PetscCheck(!info, PETSC_COMM_SELF, PETSC_ERR_LIB, "Error returned from LAPACKgetrf %" PetscInt_FMT, (PetscInt)info);
333     PetscCallBLAS("LAPACKgetri", LAPACKgetri_(&bm, Jinvs, &bm, pivots, W, &bm, &info));
334     PetscCheck(!info, PETSC_COMM_SELF, PETSC_ERR_LIB, "Error returned from LAPACKgetri %" PetscInt_FMT, (PetscInt)info);
335     PetscCall(PetscFree2(pivots, W));
336   } else if (m < n) {
337     PetscScalar  *JJT;
338     PetscBLASInt *pivots;
339     PetscScalar  *W;
340 
341     PetscCall(PetscMalloc1(m * m, &JJT));
342     PetscCall(PetscMalloc2(m, &pivots, m, &W));
343     for (i = 0; i < m; i++) {
344       for (j = 0; j < m; j++) {
345         PetscScalar val = 0.;
346 
347         for (k = 0; k < n; k++) val += Js[i * n + k] * Js[j * n + k];
348         JJT[i * m + j] = val;
349       }
350     }
351 
352     PetscCallBLAS("LAPACKgetrf", LAPACKgetrf_(&bm, &bm, JJT, &bm, pivots, &info));
353     PetscCheck(!info, PETSC_COMM_SELF, PETSC_ERR_LIB, "Error returned from LAPACKgetrf %" PetscInt_FMT, (PetscInt)info);
354     PetscCallBLAS("LAPACKgetri", LAPACKgetri_(&bm, JJT, &bm, pivots, W, &bm, &info));
355     PetscCheck(!info, PETSC_COMM_SELF, PETSC_ERR_LIB, "Error returned from LAPACKgetri %" PetscInt_FMT, (PetscInt)info);
356     for (i = 0; i < n; i++) {
357       for (j = 0; j < m; j++) {
358         PetscScalar val = 0.;
359 
360         for (k = 0; k < m; k++) val += Js[k * n + i] * JJT[k * m + j];
361         Jinvs[i * m + j] = val;
362       }
363     }
364     PetscCall(PetscFree2(pivots, W));
365     PetscCall(PetscFree(JJT));
366   } else {
367     PetscScalar  *JTJ;
368     PetscBLASInt *pivots;
369     PetscScalar  *W;
370 
371     PetscCall(PetscMalloc1(n * n, &JTJ));
372     PetscCall(PetscMalloc2(n, &pivots, n, &W));
373     for (i = 0; i < n; i++) {
374       for (j = 0; j < n; j++) {
375         PetscScalar val = 0.;
376 
377         for (k = 0; k < m; k++) val += Js[k * n + i] * Js[k * n + j];
378         JTJ[i * n + j] = val;
379       }
380     }
381 
382     PetscCallBLAS("LAPACKgetrf", LAPACKgetrf_(&bn, &bn, JTJ, &bn, pivots, &info));
383     PetscCheck(!info, PETSC_COMM_SELF, PETSC_ERR_LIB, "Error returned from LAPACKgetrf %" PetscInt_FMT, (PetscInt)info);
384     PetscCallBLAS("LAPACKgetri", LAPACKgetri_(&bn, JTJ, &bn, pivots, W, &bn, &info));
385     PetscCheck(!info, PETSC_COMM_SELF, PETSC_ERR_LIB, "Error returned from LAPACKgetri %" PetscInt_FMT, (PetscInt)info);
386     for (i = 0; i < n; i++) {
387       for (j = 0; j < m; j++) {
388         PetscScalar val = 0.;
389 
390         for (k = 0; k < n; k++) val += JTJ[i * n + k] * Js[j * n + k];
391         Jinvs[i * m + j] = val;
392       }
393     }
394     PetscCall(PetscFree2(pivots, W));
395     PetscCall(PetscFree(JTJ));
396   }
397 #if defined(PETSC_USE_COMPLEX)
398   for (i = 0; i < m * n; i++) Jinv[i] = PetscRealPart(Jinvs[i]);
399   PetscCall(PetscFree2(Js, Jinvs));
400 #endif
401   PetscFunctionReturn(0);
402 }
403 
404 /*@
405    PetscQuadraturePushForward - Push forward a quadrature functional under an affine transformation.
406 
407    Collecive on PetscQuadrature
408 
409    Input Parameters:
410 +  q - the quadrature functional
411 .  imageDim - the dimension of the image of the transformation
412 .  origin - a point in the original space
413 .  originImage - the image of the origin under the transformation
414 .  J - the Jacobian of the image: an [imageDim x dim] matrix in row major order
415 -  formDegree - transform the quadrature weights as k-forms of this form degree (if the number of components is a multiple of (dim choose formDegree), it is assumed that they represent multiple k-forms) [see PetscDTAltVPullback() for interpretation of formDegree]
416 
417    Output Parameters:
418 .  Jinvstarq - a quadrature rule where each point is the image of a point in the original quadrature rule, and where the k-form weights have been pulled-back by the pseudoinverse of J to the k-form weights in the image space.
419 
420    Note: the new quadrature rule will have a different number of components if spaces have different dimensions.  For example, pushing a 2-form forward from a two dimensional space to a three dimensional space changes the number of components from 1 to 3.
421 
422    Level: intermediate
423 
424 .seealso: `PetscDTAltVPullback()`, `PetscDTAltVPullbackMatrix()`
425 @*/
426 PetscErrorCode PetscQuadraturePushForward(PetscQuadrature q, PetscInt imageDim, const PetscReal origin[], const PetscReal originImage[], const PetscReal J[], PetscInt formDegree, PetscQuadrature *Jinvstarq) {
427   PetscInt         dim, Nc, imageNc, formSize, Ncopies, imageFormSize, Npoints, pt, i, j, c;
428   const PetscReal *points;
429   const PetscReal *weights;
430   PetscReal       *imagePoints, *imageWeights;
431   PetscReal       *Jinv;
432   PetscReal       *Jinvstar;
433 
434   PetscFunctionBegin;
435   PetscValidHeaderSpecific(q, PETSCQUADRATURE_CLASSID, 1);
436   PetscCheck(imageDim >= PetscAbsInt(formDegree), PetscObjectComm((PetscObject)q), PETSC_ERR_ARG_INCOMP, "Cannot represent a %" PetscInt_FMT "-form in %" PetscInt_FMT " dimensions", PetscAbsInt(formDegree), imageDim);
437   PetscCall(PetscQuadratureGetData(q, &dim, &Nc, &Npoints, &points, &weights));
438   PetscCall(PetscDTBinomialInt(dim, PetscAbsInt(formDegree), &formSize));
439   PetscCheck(Nc % formSize == 0, PetscObjectComm((PetscObject)q), PETSC_ERR_ARG_INCOMP, "Number of components %" PetscInt_FMT " is not a multiple of formSize %" PetscInt_FMT, Nc, formSize);
440   Ncopies = Nc / formSize;
441   PetscCall(PetscDTBinomialInt(imageDim, PetscAbsInt(formDegree), &imageFormSize));
442   imageNc = Ncopies * imageFormSize;
443   PetscCall(PetscMalloc1(Npoints * imageDim, &imagePoints));
444   PetscCall(PetscMalloc1(Npoints * imageNc, &imageWeights));
445   PetscCall(PetscMalloc2(imageDim * dim, &Jinv, formSize * imageFormSize, &Jinvstar));
446   PetscCall(PetscDTJacobianInverse_Internal(imageDim, dim, J, Jinv));
447   PetscCall(PetscDTAltVPullbackMatrix(imageDim, dim, Jinv, formDegree, Jinvstar));
448   for (pt = 0; pt < Npoints; pt++) {
449     const PetscReal *point      = &points[pt * dim];
450     PetscReal       *imagePoint = &imagePoints[pt * imageDim];
451 
452     for (i = 0; i < imageDim; i++) {
453       PetscReal val = originImage[i];
454 
455       for (j = 0; j < dim; j++) val += J[i * dim + j] * (point[j] - origin[j]);
456       imagePoint[i] = val;
457     }
458     for (c = 0; c < Ncopies; c++) {
459       const PetscReal *form      = &weights[pt * Nc + c * formSize];
460       PetscReal       *imageForm = &imageWeights[pt * imageNc + c * imageFormSize];
461 
462       for (i = 0; i < imageFormSize; i++) {
463         PetscReal val = 0.;
464 
465         for (j = 0; j < formSize; j++) val += Jinvstar[i * formSize + j] * form[j];
466         imageForm[i] = val;
467       }
468     }
469   }
470   PetscCall(PetscQuadratureCreate(PetscObjectComm((PetscObject)q), Jinvstarq));
471   PetscCall(PetscQuadratureSetData(*Jinvstarq, imageDim, imageNc, Npoints, imagePoints, imageWeights));
472   PetscCall(PetscFree2(Jinv, Jinvstar));
473   PetscFunctionReturn(0);
474 }
475 
476 /*@C
477   PetscQuadratureSetData - Sets the data defining the quadrature
478 
479   Not collective
480 
481   Input Parameters:
482 + q  - The PetscQuadrature object
483 . dim - The spatial dimension
484 . Nc - The number of components
485 . npoints - The number of quadrature points
486 . points - The coordinates of each quadrature point
487 - weights - The weight of each quadrature point
488 
489   Note: This routine owns the references to points and weights, so they must be allocated using PetscMalloc() and the user should not free them.
490 
491   Level: intermediate
492 
493 .seealso: `PetscQuadratureCreate()`, `PetscQuadratureGetData()`
494 @*/
495 PetscErrorCode PetscQuadratureSetData(PetscQuadrature q, PetscInt dim, PetscInt Nc, PetscInt npoints, const PetscReal points[], const PetscReal weights[]) {
496   PetscFunctionBegin;
497   PetscValidHeaderSpecific(q, PETSCQUADRATURE_CLASSID, 1);
498   if (dim >= 0) q->dim = dim;
499   if (Nc >= 0) q->Nc = Nc;
500   if (npoints >= 0) q->numPoints = npoints;
501   if (points) {
502     PetscValidRealPointer(points, 5);
503     q->points = points;
504   }
505   if (weights) {
506     PetscValidRealPointer(weights, 6);
507     q->weights = weights;
508   }
509   PetscFunctionReturn(0);
510 }
511 
512 static PetscErrorCode PetscQuadratureView_Ascii(PetscQuadrature quad, PetscViewer v) {
513   PetscInt          q, d, c;
514   PetscViewerFormat format;
515 
516   PetscFunctionBegin;
517   if (quad->Nc > 1) PetscCall(PetscViewerASCIIPrintf(v, "Quadrature of order %" PetscInt_FMT " on %" PetscInt_FMT " points (dim %" PetscInt_FMT ") with %" PetscInt_FMT " components\n", quad->order, quad->numPoints, quad->dim, quad->Nc));
518   else PetscCall(PetscViewerASCIIPrintf(v, "Quadrature of order %" PetscInt_FMT " on %" PetscInt_FMT " points (dim %" PetscInt_FMT ")\n", quad->order, quad->numPoints, quad->dim));
519   PetscCall(PetscViewerGetFormat(v, &format));
520   if (format != PETSC_VIEWER_ASCII_INFO_DETAIL) PetscFunctionReturn(0);
521   for (q = 0; q < quad->numPoints; ++q) {
522     PetscCall(PetscViewerASCIIPrintf(v, "p%" PetscInt_FMT " (", q));
523     PetscCall(PetscViewerASCIIUseTabs(v, PETSC_FALSE));
524     for (d = 0; d < quad->dim; ++d) {
525       if (d) PetscCall(PetscViewerASCIIPrintf(v, ", "));
526       PetscCall(PetscViewerASCIIPrintf(v, "%+g", (double)quad->points[q * quad->dim + d]));
527     }
528     PetscCall(PetscViewerASCIIPrintf(v, ") "));
529     if (quad->Nc > 1) PetscCall(PetscViewerASCIIPrintf(v, "w%" PetscInt_FMT " (", q));
530     for (c = 0; c < quad->Nc; ++c) {
531       if (c) PetscCall(PetscViewerASCIIPrintf(v, ", "));
532       PetscCall(PetscViewerASCIIPrintf(v, "%+g", (double)quad->weights[q * quad->Nc + c]));
533     }
534     if (quad->Nc > 1) PetscCall(PetscViewerASCIIPrintf(v, ")"));
535     PetscCall(PetscViewerASCIIPrintf(v, "\n"));
536     PetscCall(PetscViewerASCIIUseTabs(v, PETSC_TRUE));
537   }
538   PetscFunctionReturn(0);
539 }
540 
541 /*@C
542   PetscQuadratureView - Views a PetscQuadrature object
543 
544   Collective on quad
545 
546   Input Parameters:
547 + quad  - The PetscQuadrature object
548 - viewer - The PetscViewer object
549 
550   Level: beginner
551 
552 .seealso: `PetscQuadratureCreate()`, `PetscQuadratureGetData()`
553 @*/
554 PetscErrorCode PetscQuadratureView(PetscQuadrature quad, PetscViewer viewer) {
555   PetscBool iascii;
556 
557   PetscFunctionBegin;
558   PetscValidHeader(quad, 1);
559   if (viewer) PetscValidHeaderSpecific(viewer, PETSC_VIEWER_CLASSID, 2);
560   if (!viewer) PetscCall(PetscViewerASCIIGetStdout(PetscObjectComm((PetscObject)quad), &viewer));
561   PetscCall(PetscObjectTypeCompare((PetscObject)viewer, PETSCVIEWERASCII, &iascii));
562   PetscCall(PetscViewerASCIIPushTab(viewer));
563   if (iascii) PetscCall(PetscQuadratureView_Ascii(quad, viewer));
564   PetscCall(PetscViewerASCIIPopTab(viewer));
565   PetscFunctionReturn(0);
566 }
567 
568 /*@C
569   PetscQuadratureExpandComposite - Return a quadrature over the composite element, which has the original quadrature in each subelement
570 
571   Not collective
572 
573   Input Parameters:
574 + q - The original PetscQuadrature
575 . numSubelements - The number of subelements the original element is divided into
576 . v0 - An array of the initial points for each subelement
577 - jac - An array of the Jacobian mappings from the reference to each subelement
578 
579   Output Parameters:
580 . dim - The dimension
581 
582   Note: Together v0 and jac define an affine mapping from the original reference element to each subelement
583 
584  Not available from Fortran
585 
586   Level: intermediate
587 
588 .seealso: `PetscFECreate()`, `PetscSpaceGetDimension()`, `PetscDualSpaceGetDimension()`
589 @*/
590 PetscErrorCode PetscQuadratureExpandComposite(PetscQuadrature q, PetscInt numSubelements, const PetscReal v0[], const PetscReal jac[], PetscQuadrature *qref) {
591   const PetscReal *points, *weights;
592   PetscReal       *pointsRef, *weightsRef;
593   PetscInt         dim, Nc, order, npoints, npointsRef, c, p, cp, d, e;
594 
595   PetscFunctionBegin;
596   PetscValidHeaderSpecific(q, PETSCQUADRATURE_CLASSID, 1);
597   PetscValidRealPointer(v0, 3);
598   PetscValidRealPointer(jac, 4);
599   PetscValidPointer(qref, 5);
600   PetscCall(PetscQuadratureCreate(PETSC_COMM_SELF, qref));
601   PetscCall(PetscQuadratureGetOrder(q, &order));
602   PetscCall(PetscQuadratureGetData(q, &dim, &Nc, &npoints, &points, &weights));
603   npointsRef = npoints * numSubelements;
604   PetscCall(PetscMalloc1(npointsRef * dim, &pointsRef));
605   PetscCall(PetscMalloc1(npointsRef * Nc, &weightsRef));
606   for (c = 0; c < numSubelements; ++c) {
607     for (p = 0; p < npoints; ++p) {
608       for (d = 0; d < dim; ++d) {
609         pointsRef[(c * npoints + p) * dim + d] = v0[c * dim + d];
610         for (e = 0; e < dim; ++e) { pointsRef[(c * npoints + p) * dim + d] += jac[(c * dim + d) * dim + e] * (points[p * dim + e] + 1.0); }
611       }
612       /* Could also use detJ here */
613       for (cp = 0; cp < Nc; ++cp) weightsRef[(c * npoints + p) * Nc + cp] = weights[p * Nc + cp] / numSubelements;
614     }
615   }
616   PetscCall(PetscQuadratureSetOrder(*qref, order));
617   PetscCall(PetscQuadratureSetData(*qref, dim, Nc, npointsRef, pointsRef, weightsRef));
618   PetscFunctionReturn(0);
619 }
620 
621 /* Compute the coefficients for the Jacobi polynomial recurrence,
622  *
623  * J^{a,b}_n(x) = (cnm1 + cnm1x * x) * J^{a,b}_{n-1}(x) - cnm2 * J^{a,b}_{n-2}(x).
624  */
625 #define PetscDTJacobiRecurrence_Internal(n, a, b, cnm1, cnm1x, cnm2) \
626   do { \
627     PetscReal _a = (a); \
628     PetscReal _b = (b); \
629     PetscReal _n = (n); \
630     if (n == 1) { \
631       (cnm1)  = (_a - _b) * 0.5; \
632       (cnm1x) = (_a + _b + 2.) * 0.5; \
633       (cnm2)  = 0.; \
634     } else { \
635       PetscReal _2n  = _n + _n; \
636       PetscReal _d   = (_2n * (_n + _a + _b) * (_2n + _a + _b - 2)); \
637       PetscReal _n1  = (_2n + _a + _b - 1.) * (_a * _a - _b * _b); \
638       PetscReal _n1x = (_2n + _a + _b - 1.) * (_2n + _a + _b) * (_2n + _a + _b - 2); \
639       PetscReal _n2  = 2. * ((_n + _a - 1.) * (_n + _b - 1.) * (_2n + _a + _b)); \
640       (cnm1)         = _n1 / _d; \
641       (cnm1x)        = _n1x / _d; \
642       (cnm2)         = _n2 / _d; \
643     } \
644   } while (0)
645 
646 /*@
647   PetscDTJacobiNorm - Compute the weighted L2 norm of a Jacobi polynomial.
648 
649   $\| P^{\alpha,\beta}_n \|_{\alpha,\beta}^2 = \int_{-1}^1 (1 + x)^{\alpha} (1 - x)^{\beta} P^{\alpha,\beta}_n (x)^2 dx.$
650 
651   Input Parameters:
652 - alpha - the left exponent > -1
653 . beta - the right exponent > -1
654 + n - the polynomial degree
655 
656   Output Parameter:
657 . norm - the weighted L2 norm
658 
659   Level: beginner
660 
661 .seealso: `PetscDTJacobiEval()`
662 @*/
663 PetscErrorCode PetscDTJacobiNorm(PetscReal alpha, PetscReal beta, PetscInt n, PetscReal *norm) {
664   PetscReal twoab1;
665   PetscReal gr;
666 
667   PetscFunctionBegin;
668   PetscCheck(alpha > -1., PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Exponent alpha %g <= -1. invalid", (double)alpha);
669   PetscCheck(beta > -1., PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Exponent beta %g <= -1. invalid", (double)beta);
670   PetscCheck(n >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "n %" PetscInt_FMT " < 0 invalid", n);
671   twoab1 = PetscPowReal(2., alpha + beta + 1.);
672 #if defined(PETSC_HAVE_LGAMMA)
673   if (!n) {
674     gr = PetscExpReal(PetscLGamma(alpha + 1.) + PetscLGamma(beta + 1.) - PetscLGamma(alpha + beta + 2.));
675   } else {
676     gr = PetscExpReal(PetscLGamma(n + alpha + 1.) + PetscLGamma(n + beta + 1.) - (PetscLGamma(n + 1.) + PetscLGamma(n + alpha + beta + 1.))) / (n + n + alpha + beta + 1.);
677   }
678 #else
679   {
680     PetscInt alphai = (PetscInt)alpha;
681     PetscInt betai  = (PetscInt)beta;
682     PetscInt i;
683 
684     gr = n ? (1. / (n + n + alpha + beta + 1.)) : 1.;
685     if ((PetscReal)alphai == alpha) {
686       if (!n) {
687         for (i = 0; i < alphai; i++) gr *= (i + 1.) / (beta + i + 1.);
688         gr /= (alpha + beta + 1.);
689       } else {
690         for (i = 0; i < alphai; i++) gr *= (n + i + 1.) / (n + beta + i + 1.);
691       }
692     } else if ((PetscReal)betai == beta) {
693       if (!n) {
694         for (i = 0; i < betai; i++) gr *= (i + 1.) / (alpha + i + 2.);
695         gr /= (alpha + beta + 1.);
696       } else {
697         for (i = 0; i < betai; i++) gr *= (n + i + 1.) / (n + alpha + i + 1.);
698       }
699     } else SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "lgamma() - math routine is unavailable.");
700   }
701 #endif
702   *norm = PetscSqrtReal(twoab1 * gr);
703   PetscFunctionReturn(0);
704 }
705 
706 static PetscErrorCode PetscDTJacobiEval_Internal(PetscInt npoints, PetscReal a, PetscReal b, PetscInt k, const PetscReal *points, PetscInt ndegree, const PetscInt *degrees, PetscReal *p) {
707   PetscReal ak, bk;
708   PetscReal abk1;
709   PetscInt  i, l, maxdegree;
710 
711   PetscFunctionBegin;
712   maxdegree = degrees[ndegree - 1] - k;
713   ak        = a + k;
714   bk        = b + k;
715   abk1      = a + b + k + 1.;
716   if (maxdegree < 0) {
717     for (i = 0; i < npoints; i++)
718       for (l = 0; l < ndegree; l++) p[i * ndegree + l] = 0.;
719     PetscFunctionReturn(0);
720   }
721   for (i = 0; i < npoints; i++) {
722     PetscReal pm1, pm2, x;
723     PetscReal cnm1, cnm1x, cnm2;
724     PetscInt  j, m;
725 
726     x   = points[i];
727     pm2 = 1.;
728     PetscDTJacobiRecurrence_Internal(1, ak, bk, cnm1, cnm1x, cnm2);
729     pm1 = (cnm1 + cnm1x * x);
730     l   = 0;
731     while (l < ndegree && degrees[l] - k < 0) { p[l++] = 0.; }
732     while (l < ndegree && degrees[l] - k == 0) {
733       p[l] = pm2;
734       for (m = 0; m < k; m++) p[l] *= (abk1 + m) * 0.5;
735       l++;
736     }
737     while (l < ndegree && degrees[l] - k == 1) {
738       p[l] = pm1;
739       for (m = 0; m < k; m++) p[l] *= (abk1 + 1 + m) * 0.5;
740       l++;
741     }
742     for (j = 2; j <= maxdegree; j++) {
743       PetscReal pp;
744 
745       PetscDTJacobiRecurrence_Internal(j, ak, bk, cnm1, cnm1x, cnm2);
746       pp  = (cnm1 + cnm1x * x) * pm1 - cnm2 * pm2;
747       pm2 = pm1;
748       pm1 = pp;
749       while (l < ndegree && degrees[l] - k == j) {
750         p[l] = pp;
751         for (m = 0; m < k; m++) p[l] *= (abk1 + j + m) * 0.5;
752         l++;
753       }
754     }
755     p += ndegree;
756   }
757   PetscFunctionReturn(0);
758 }
759 
760 /*@
761   PetscDTJacobiEvalJet - Evaluate the jet (function and derivatives) of the Jacobi polynomials basis up to a given degree.  The Jacobi polynomials with indices $\alpha$ and $\beta$ are orthogonal with respect to the weighted inner product $\langle f, g \rangle = \int_{-1}^1 (1+x)^{\alpha} (1-x)^{\beta) f(x) g(x) dx$.
762 
763   Input Parameters:
764 + alpha - the left exponent of the weight
765 . beta - the right exponetn of the weight
766 . npoints - the number of points to evaluate the polynomials at
767 . points - [npoints] array of point coordinates
768 . degree - the maximm degree polynomial space to evaluate, (degree + 1) will be evaluated total.
769 - k - the maximum derivative to evaluate in the jet, (k + 1) will be evaluated total.
770 
771   Output Parameters:
772 - p - an array containing the evaluations of the Jacobi polynomials's jets on the points.  the size is (degree + 1) x
773   (k + 1) x npoints, which also describes the order of the dimensions of this three-dimensional array: the first
774   (slowest varying) dimension is polynomial degree; the second dimension is derivative order; the third (fastest
775   varying) dimension is the index of the evaluation point.
776 
777   Level: advanced
778 
779 .seealso: `PetscDTJacobiEval()`, `PetscDTPKDEvalJet()`
780 @*/
781 PetscErrorCode PetscDTJacobiEvalJet(PetscReal alpha, PetscReal beta, PetscInt npoints, const PetscReal points[], PetscInt degree, PetscInt k, PetscReal p[]) {
782   PetscInt   i, j, l;
783   PetscInt  *degrees;
784   PetscReal *psingle;
785 
786   PetscFunctionBegin;
787   if (degree == 0) {
788     PetscInt zero = 0;
789 
790     for (i = 0; i <= k; i++) { PetscCall(PetscDTJacobiEval_Internal(npoints, alpha, beta, i, points, 1, &zero, &p[i * npoints])); }
791     PetscFunctionReturn(0);
792   }
793   PetscCall(PetscMalloc1(degree + 1, &degrees));
794   PetscCall(PetscMalloc1((degree + 1) * npoints, &psingle));
795   for (i = 0; i <= degree; i++) degrees[i] = i;
796   for (i = 0; i <= k; i++) {
797     PetscCall(PetscDTJacobiEval_Internal(npoints, alpha, beta, i, points, degree + 1, degrees, psingle));
798     for (j = 0; j <= degree; j++) {
799       for (l = 0; l < npoints; l++) { p[(j * (k + 1) + i) * npoints + l] = psingle[l * (degree + 1) + j]; }
800     }
801   }
802   PetscCall(PetscFree(psingle));
803   PetscCall(PetscFree(degrees));
804   PetscFunctionReturn(0);
805 }
806 
807 /*@
808    PetscDTJacobiEval - evaluate Jacobi polynomials for the weight function $(1.+x)^{\alpha} (1.-x)^{\beta}$
809                        at points
810 
811    Not Collective
812 
813    Input Parameters:
814 +  npoints - number of spatial points to evaluate at
815 .  alpha - the left exponent > -1
816 .  beta - the right exponent > -1
817 .  points - array of locations to evaluate at
818 .  ndegree - number of basis degrees to evaluate
819 -  degrees - sorted array of degrees to evaluate
820 
821    Output Parameters:
822 +  B - row-oriented basis evaluation matrix B[point*ndegree + degree] (dimension npoints*ndegrees, allocated by caller) (or NULL)
823 .  D - row-oriented derivative evaluation matrix (or NULL)
824 -  D2 - row-oriented second derivative evaluation matrix (or NULL)
825 
826    Level: intermediate
827 
828 .seealso: `PetscDTGaussQuadrature()`
829 @*/
830 PetscErrorCode PetscDTJacobiEval(PetscInt npoints, PetscReal alpha, PetscReal beta, const PetscReal *points, PetscInt ndegree, const PetscInt *degrees, PetscReal *B, PetscReal *D, PetscReal *D2) {
831   PetscFunctionBegin;
832   PetscCheck(alpha > -1., PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "alpha must be > -1.");
833   PetscCheck(beta > -1., PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "beta must be > -1.");
834   if (!npoints || !ndegree) PetscFunctionReturn(0);
835   if (B) PetscCall(PetscDTJacobiEval_Internal(npoints, alpha, beta, 0, points, ndegree, degrees, B));
836   if (D) PetscCall(PetscDTJacobiEval_Internal(npoints, alpha, beta, 1, points, ndegree, degrees, D));
837   if (D2) PetscCall(PetscDTJacobiEval_Internal(npoints, alpha, beta, 2, points, ndegree, degrees, D2));
838   PetscFunctionReturn(0);
839 }
840 
841 /*@
842    PetscDTLegendreEval - evaluate Legendre polynomials at points
843 
844    Not Collective
845 
846    Input Parameters:
847 +  npoints - number of spatial points to evaluate at
848 .  points - array of locations to evaluate at
849 .  ndegree - number of basis degrees to evaluate
850 -  degrees - sorted array of degrees to evaluate
851 
852    Output Parameters:
853 +  B - row-oriented basis evaluation matrix B[point*ndegree + degree] (dimension npoints*ndegrees, allocated by caller) (or NULL)
854 .  D - row-oriented derivative evaluation matrix (or NULL)
855 -  D2 - row-oriented second derivative evaluation matrix (or NULL)
856 
857    Level: intermediate
858 
859 .seealso: `PetscDTGaussQuadrature()`
860 @*/
861 PetscErrorCode PetscDTLegendreEval(PetscInt npoints, const PetscReal *points, PetscInt ndegree, const PetscInt *degrees, PetscReal *B, PetscReal *D, PetscReal *D2) {
862   PetscFunctionBegin;
863   PetscCall(PetscDTJacobiEval(npoints, 0., 0., points, ndegree, degrees, B, D, D2));
864   PetscFunctionReturn(0);
865 }
866 
867 /*@
868   PetscDTIndexToGradedOrder - convert an index into a tuple of monomial degrees in a graded order (that is, if the degree sum of tuple x is less than the degree sum of tuple y, then the index of x is smaller than the index of y)
869 
870   Input Parameters:
871 + len - the desired length of the degree tuple
872 - index - the index to convert: should be >= 0
873 
874   Output Parameter:
875 . degtup - will be filled with a tuple of degrees
876 
877   Level: beginner
878 
879   Note: for two tuples x and y with the same degree sum, partial degree sums over the final elements of the tuples
880   acts as a tiebreaker.  For example, (2, 1, 1) and (1, 2, 1) have the same degree sum, but the degree sum over the
881   last two elements is smaller for the former, so (2, 1, 1) < (1, 2, 1).
882 
883 .seealso: `PetscDTGradedOrderToIndex()`
884 @*/
885 PetscErrorCode PetscDTIndexToGradedOrder(PetscInt len, PetscInt index, PetscInt degtup[]) {
886   PetscInt i, total;
887   PetscInt sum;
888 
889   PetscFunctionBeginHot;
890   PetscCheck(len >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "length must be non-negative");
891   PetscCheck(index >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "index must be non-negative");
892   total = 1;
893   sum   = 0;
894   while (index >= total) {
895     index -= total;
896     total = (total * (len + sum)) / (sum + 1);
897     sum++;
898   }
899   for (i = 0; i < len; i++) {
900     PetscInt c;
901 
902     degtup[i] = sum;
903     for (c = 0, total = 1; c < sum; c++) {
904       /* going into the loop, total is the number of way to have a tuple of sum exactly c with length len - 1 - i */
905       if (index < total) break;
906       index -= total;
907       total = (total * (len - 1 - i + c)) / (c + 1);
908       degtup[i]--;
909     }
910     sum -= degtup[i];
911   }
912   PetscFunctionReturn(0);
913 }
914 
915 /*@
916   PetscDTGradedOrderToIndex - convert a tuple into an index in a graded order, the inverse of PetscDTIndexToGradedOrder().
917 
918   Input Parameters:
919 + len - the length of the degree tuple
920 - degtup - tuple with this length
921 
922   Output Parameter:
923 . index - index in graded order: >= 0
924 
925   Level: Beginner
926 
927   Note: for two tuples x and y with the same degree sum, partial degree sums over the final elements of the tuples
928   acts as a tiebreaker.  For example, (2, 1, 1) and (1, 2, 1) have the same degree sum, but the degree sum over the
929   last two elements is smaller for the former, so (2, 1, 1) < (1, 2, 1).
930 
931 .seealso: `PetscDTIndexToGradedOrder()`
932 @*/
933 PetscErrorCode PetscDTGradedOrderToIndex(PetscInt len, const PetscInt degtup[], PetscInt *index) {
934   PetscInt i, idx, sum, total;
935 
936   PetscFunctionBeginHot;
937   PetscCheck(len >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "length must be non-negative");
938   for (i = 0, sum = 0; i < len; i++) sum += degtup[i];
939   idx   = 0;
940   total = 1;
941   for (i = 0; i < sum; i++) {
942     idx += total;
943     total = (total * (len + i)) / (i + 1);
944   }
945   for (i = 0; i < len - 1; i++) {
946     PetscInt c;
947 
948     total = 1;
949     sum -= degtup[i];
950     for (c = 0; c < sum; c++) {
951       idx += total;
952       total = (total * (len - 1 - i + c)) / (c + 1);
953     }
954   }
955   *index = idx;
956   PetscFunctionReturn(0);
957 }
958 
959 static PetscBool PKDCite       = PETSC_FALSE;
960 const char       PKDCitation[] = "@article{Kirby2010,\n"
961                                  "  title={Singularity-free evaluation of collapsed-coordinate orthogonal polynomials},\n"
962                                  "  author={Kirby, Robert C},\n"
963                                  "  journal={ACM Transactions on Mathematical Software (TOMS)},\n"
964                                  "  volume={37},\n"
965                                  "  number={1},\n"
966                                  "  pages={1--16},\n"
967                                  "  year={2010},\n"
968                                  "  publisher={ACM New York, NY, USA}\n}\n";
969 
970 /*@
971   PetscDTPKDEvalJet - Evaluate the jet (function and derivatives) of the Proriol-Koornwinder-Dubiner (PKD) basis for
972   the space of polynomials up to a given degree.  The PKD basis is L2-orthonormal on the biunit simplex (which is used
973   as the reference element for finite elements in PETSc), which makes it a stable basis to use for evaluating
974   polynomials in that domain.
975 
976   Input Parameters:
977 + dim - the number of variables in the multivariate polynomials
978 . npoints - the number of points to evaluate the polynomials at
979 . points - [npoints x dim] array of point coordinates
980 . degree - the degree (sum of degrees on the variables in a monomial) of the polynomial space to evaluate.  There are ((dim + degree) choose dim) polynomials in this space.
981 - k - the maximum order partial derivative to evaluate in the jet.  There are (dim + k choose dim) partial derivatives
982   in the jet.  Choosing k = 0 means to evaluate just the function and no derivatives
983 
984   Output Parameters:
985 - p - an array containing the evaluations of the PKD polynomials' jets on the points.  The size is ((dim + degree)
986   choose dim) x ((dim + k) choose dim) x npoints, which also describes the order of the dimensions of this
987   three-dimensional array: the first (slowest varying) dimension is basis function index; the second dimension is jet
988   index; the third (fastest varying) dimension is the index of the evaluation point.
989 
990   Level: advanced
991 
992   Note: The ordering of the basis functions, and the ordering of the derivatives in the jet, both follow the graded
993   ordering of PetscDTIndexToGradedOrder() and PetscDTGradedOrderToIndex().  For example, in 3D, the polynomial with
994   leading monomial x^2,y^0,z^1, which has degree tuple (2,0,1), which by PetscDTGradedOrderToIndex() has index 12 (it is the 13th basis function in the space);
995   the partial derivative $\partial_x \partial_z$ has order tuple (1,0,1), appears at index 6 in the jet (it is the 7th partial derivative in the jet).
996 
997   The implementation uses Kirby's singularity-free evaluation algorithm, https://doi.org/10.1145/1644001.1644006.
998 
999 .seealso: `PetscDTGradedOrderToIndex()`, `PetscDTIndexToGradedOrder()`, `PetscDTJacobiEvalJet()`
1000 @*/
1001 PetscErrorCode PetscDTPKDEvalJet(PetscInt dim, PetscInt npoints, const PetscReal points[], PetscInt degree, PetscInt k, PetscReal p[]) {
1002   PetscInt   degidx, kidx, d, pt;
1003   PetscInt   Nk, Ndeg;
1004   PetscInt  *ktup, *degtup;
1005   PetscReal *scales, initscale, scaleexp;
1006 
1007   PetscFunctionBegin;
1008   PetscCall(PetscCitationsRegister(PKDCitation, &PKDCite));
1009   PetscCall(PetscDTBinomialInt(dim + k, k, &Nk));
1010   PetscCall(PetscDTBinomialInt(degree + dim, degree, &Ndeg));
1011   PetscCall(PetscMalloc2(dim, &degtup, dim, &ktup));
1012   PetscCall(PetscMalloc1(Ndeg, &scales));
1013   initscale = 1.;
1014   if (dim > 1) {
1015     PetscCall(PetscDTBinomial(dim, 2, &scaleexp));
1016     initscale = PetscPowReal(2., scaleexp * 0.5);
1017   }
1018   for (degidx = 0; degidx < Ndeg; degidx++) {
1019     PetscInt  e, i;
1020     PetscInt  m1idx = -1, m2idx = -1;
1021     PetscInt  n;
1022     PetscInt  degsum;
1023     PetscReal alpha;
1024     PetscReal cnm1, cnm1x, cnm2;
1025     PetscReal norm;
1026 
1027     PetscCall(PetscDTIndexToGradedOrder(dim, degidx, degtup));
1028     for (d = dim - 1; d >= 0; d--)
1029       if (degtup[d]) break;
1030     if (d < 0) { /* constant is 1 everywhere, all derivatives are zero */
1031       scales[degidx] = initscale;
1032       for (e = 0; e < dim; e++) {
1033         PetscCall(PetscDTJacobiNorm(e, 0., 0, &norm));
1034         scales[degidx] /= norm;
1035       }
1036       for (i = 0; i < npoints; i++) p[degidx * Nk * npoints + i] = 1.;
1037       for (i = 0; i < (Nk - 1) * npoints; i++) p[(degidx * Nk + 1) * npoints + i] = 0.;
1038       continue;
1039     }
1040     n = degtup[d];
1041     degtup[d]--;
1042     PetscCall(PetscDTGradedOrderToIndex(dim, degtup, &m1idx));
1043     if (degtup[d] > 0) {
1044       degtup[d]--;
1045       PetscCall(PetscDTGradedOrderToIndex(dim, degtup, &m2idx));
1046       degtup[d]++;
1047     }
1048     degtup[d]++;
1049     for (e = 0, degsum = 0; e < d; e++) degsum += degtup[e];
1050     alpha = 2 * degsum + d;
1051     PetscDTJacobiRecurrence_Internal(n, alpha, 0., cnm1, cnm1x, cnm2);
1052 
1053     scales[degidx] = initscale;
1054     for (e = 0, degsum = 0; e < dim; e++) {
1055       PetscInt  f;
1056       PetscReal ealpha;
1057       PetscReal enorm;
1058 
1059       ealpha = 2 * degsum + e;
1060       for (f = 0; f < degsum; f++) scales[degidx] *= 2.;
1061       PetscCall(PetscDTJacobiNorm(ealpha, 0., degtup[e], &enorm));
1062       scales[degidx] /= enorm;
1063       degsum += degtup[e];
1064     }
1065 
1066     for (pt = 0; pt < npoints; pt++) {
1067       /* compute the multipliers */
1068       PetscReal thetanm1, thetanm1x, thetanm2;
1069 
1070       thetanm1x = dim - (d + 1) + 2. * points[pt * dim + d];
1071       for (e = d + 1; e < dim; e++) thetanm1x += points[pt * dim + e];
1072       thetanm1x *= 0.5;
1073       thetanm1 = (2. - (dim - (d + 1)));
1074       for (e = d + 1; e < dim; e++) thetanm1 -= points[pt * dim + e];
1075       thetanm1 *= 0.5;
1076       thetanm2 = thetanm1 * thetanm1;
1077 
1078       for (kidx = 0; kidx < Nk; kidx++) {
1079         PetscInt f;
1080 
1081         PetscCall(PetscDTIndexToGradedOrder(dim, kidx, ktup));
1082         /* first sum in the same derivative terms */
1083         p[(degidx * Nk + kidx) * npoints + pt] = (cnm1 * thetanm1 + cnm1x * thetanm1x) * p[(m1idx * Nk + kidx) * npoints + pt];
1084         if (m2idx >= 0) { p[(degidx * Nk + kidx) * npoints + pt] -= cnm2 * thetanm2 * p[(m2idx * Nk + kidx) * npoints + pt]; }
1085 
1086         for (f = d; f < dim; f++) {
1087           PetscInt km1idx, mplty = ktup[f];
1088 
1089           if (!mplty) continue;
1090           ktup[f]--;
1091           PetscCall(PetscDTGradedOrderToIndex(dim, ktup, &km1idx));
1092 
1093           /* the derivative of  cnm1x * thetanm1x  wrt x variable f is 0.5 * cnm1x if f > d otherwise it is cnm1x */
1094           /* the derivative of  cnm1  * thetanm1   wrt x variable f is 0 if f == d, otherwise it is -0.5 * cnm1 */
1095           /* the derivative of -cnm2  * thetanm2   wrt x variable f is 0 if f == d, otherwise it is cnm2 * thetanm1 */
1096           if (f > d) {
1097             PetscInt f2;
1098 
1099             p[(degidx * Nk + kidx) * npoints + pt] += mplty * 0.5 * (cnm1x - cnm1) * p[(m1idx * Nk + km1idx) * npoints + pt];
1100             if (m2idx >= 0) {
1101               p[(degidx * Nk + kidx) * npoints + pt] += mplty * cnm2 * thetanm1 * p[(m2idx * Nk + km1idx) * npoints + pt];
1102               /* second derivatives of -cnm2  * thetanm2   wrt x variable f,f2 is like - 0.5 * cnm2 */
1103               for (f2 = f; f2 < dim; f2++) {
1104                 PetscInt km2idx, mplty2 = ktup[f2];
1105                 PetscInt factor;
1106 
1107                 if (!mplty2) continue;
1108                 ktup[f2]--;
1109                 PetscCall(PetscDTGradedOrderToIndex(dim, ktup, &km2idx));
1110 
1111                 factor = mplty * mplty2;
1112                 if (f == f2) factor /= 2;
1113                 p[(degidx * Nk + kidx) * npoints + pt] -= 0.5 * factor * cnm2 * p[(m2idx * Nk + km2idx) * npoints + pt];
1114                 ktup[f2]++;
1115               }
1116             }
1117           } else {
1118             p[(degidx * Nk + kidx) * npoints + pt] += mplty * cnm1x * p[(m1idx * Nk + km1idx) * npoints + pt];
1119           }
1120           ktup[f]++;
1121         }
1122       }
1123     }
1124   }
1125   for (degidx = 0; degidx < Ndeg; degidx++) {
1126     PetscReal scale = scales[degidx];
1127     PetscInt  i;
1128 
1129     for (i = 0; i < Nk * npoints; i++) p[degidx * Nk * npoints + i] *= scale;
1130   }
1131   PetscCall(PetscFree(scales));
1132   PetscCall(PetscFree2(degtup, ktup));
1133   PetscFunctionReturn(0);
1134 }
1135 
1136 /*@
1137   PetscDTPTrimmedSize - The size of the trimmed polynomial space of k-forms with a given degree and form degree,
1138   which can be evaluated in PetscDTPTrimmedEvalJet().
1139 
1140   Input Parameters:
1141 + dim - the number of variables in the multivariate polynomials
1142 . degree - the degree (sum of degrees on the variables in a monomial) of the trimmed polynomial space.
1143 - formDegree - the degree of the form
1144 
1145   Output Parameters:
1146 - size - The number ((dim + degree) choose (dim + formDegree)) x ((degree + formDegree - 1) choose (formDegree))
1147 
1148   Level: advanced
1149 
1150 .seealso: `PetscDTPTrimmedEvalJet()`
1151 @*/
1152 PetscErrorCode PetscDTPTrimmedSize(PetscInt dim, PetscInt degree, PetscInt formDegree, PetscInt *size) {
1153   PetscInt Nrk, Nbpt; // number of trimmed polynomials
1154 
1155   PetscFunctionBegin;
1156   formDegree = PetscAbsInt(formDegree);
1157   PetscCall(PetscDTBinomialInt(degree + dim, degree + formDegree, &Nbpt));
1158   PetscCall(PetscDTBinomialInt(degree + formDegree - 1, formDegree, &Nrk));
1159   Nbpt *= Nrk;
1160   *size = Nbpt;
1161   PetscFunctionReturn(0);
1162 }
1163 
1164 /* there was a reference implementation based on section 4.4 of Arnold, Falk & Winther (acta numerica, 2006), but it
1165  * was inferior to this implementation */
1166 static PetscErrorCode PetscDTPTrimmedEvalJet_Internal(PetscInt dim, PetscInt npoints, const PetscReal points[], PetscInt degree, PetscInt formDegree, PetscInt jetDegree, PetscReal p[]) {
1167   PetscInt  formDegreeOrig = formDegree;
1168   PetscBool formNegative   = (formDegreeOrig < 0) ? PETSC_TRUE : PETSC_FALSE;
1169 
1170   PetscFunctionBegin;
1171   formDegree = PetscAbsInt(formDegreeOrig);
1172   if (formDegree == 0) {
1173     PetscCall(PetscDTPKDEvalJet(dim, npoints, points, degree, jetDegree, p));
1174     PetscFunctionReturn(0);
1175   }
1176   if (formDegree == dim) {
1177     PetscCall(PetscDTPKDEvalJet(dim, npoints, points, degree - 1, jetDegree, p));
1178     PetscFunctionReturn(0);
1179   }
1180   PetscInt Nbpt;
1181   PetscCall(PetscDTPTrimmedSize(dim, degree, formDegree, &Nbpt));
1182   PetscInt Nf;
1183   PetscCall(PetscDTBinomialInt(dim, formDegree, &Nf));
1184   PetscInt Nk;
1185   PetscCall(PetscDTBinomialInt(dim + jetDegree, dim, &Nk));
1186   PetscCall(PetscArrayzero(p, Nbpt * Nf * Nk * npoints));
1187 
1188   PetscInt Nbpm1; // number of scalar polynomials up to degree - 1;
1189   PetscCall(PetscDTBinomialInt(dim + degree - 1, dim, &Nbpm1));
1190   PetscReal *p_scalar;
1191   PetscCall(PetscMalloc1(Nbpm1 * Nk * npoints, &p_scalar));
1192   PetscCall(PetscDTPKDEvalJet(dim, npoints, points, degree - 1, jetDegree, p_scalar));
1193   PetscInt total = 0;
1194   // First add the full polynomials up to degree - 1 into the basis: take the scalar
1195   // and copy one for each form component
1196   for (PetscInt i = 0; i < Nbpm1; i++) {
1197     const PetscReal *src = &p_scalar[i * Nk * npoints];
1198     for (PetscInt f = 0; f < Nf; f++) {
1199       PetscReal *dest = &p[(total++ * Nf + f) * Nk * npoints];
1200       PetscCall(PetscArraycpy(dest, src, Nk * npoints));
1201     }
1202   }
1203   PetscInt *form_atoms;
1204   PetscCall(PetscMalloc1(formDegree + 1, &form_atoms));
1205   // construct the interior product pattern
1206   PetscInt(*pattern)[3];
1207   PetscInt Nf1; // number of formDegree + 1 forms
1208   PetscCall(PetscDTBinomialInt(dim, formDegree + 1, &Nf1));
1209   PetscInt nnz = Nf1 * (formDegree + 1);
1210   PetscCall(PetscMalloc1(Nf1 * (formDegree + 1), &pattern));
1211   PetscCall(PetscDTAltVInteriorPattern(dim, formDegree + 1, pattern));
1212   PetscReal centroid = (1. - dim) / (dim + 1.);
1213   PetscInt *deriv;
1214   PetscCall(PetscMalloc1(dim, &deriv));
1215   for (PetscInt d = dim; d >= formDegree + 1; d--) {
1216     PetscInt Nfd1; // number of formDegree + 1 forms in dimension d that include dx_0
1217                    // (equal to the number of formDegree forms in dimension d-1)
1218     PetscCall(PetscDTBinomialInt(d - 1, formDegree, &Nfd1));
1219     // The number of homogeneous (degree-1) scalar polynomials in d variables
1220     PetscInt Nh;
1221     PetscCall(PetscDTBinomialInt(d - 1 + degree - 1, d - 1, &Nh));
1222     const PetscReal *h_scalar = &p_scalar[(Nbpm1 - Nh) * Nk * npoints];
1223     for (PetscInt b = 0; b < Nh; b++) {
1224       const PetscReal *h_s = &h_scalar[b * Nk * npoints];
1225       for (PetscInt f = 0; f < Nfd1; f++) {
1226         // construct all formDegree+1 forms that start with dx_(dim - d) /\ ...
1227         form_atoms[0] = dim - d;
1228         PetscCall(PetscDTEnumSubset(d - 1, formDegree, f, &form_atoms[1]));
1229         for (PetscInt i = 0; i < formDegree; i++) { form_atoms[1 + i] += form_atoms[0] + 1; }
1230         PetscInt f_ind; // index of the resulting form
1231         PetscCall(PetscDTSubsetIndex(dim, formDegree + 1, form_atoms, &f_ind));
1232         PetscReal *p_f = &p[total++ * Nf * Nk * npoints];
1233         for (PetscInt nz = 0; nz < nnz; nz++) {
1234           PetscInt  i     = pattern[nz][0]; // formDegree component
1235           PetscInt  j     = pattern[nz][1]; // (formDegree + 1) component
1236           PetscInt  v     = pattern[nz][2]; // coordinate component
1237           PetscReal scale = v < 0 ? -1. : 1.;
1238 
1239           i     = formNegative ? (Nf - 1 - i) : i;
1240           scale = (formNegative && (i & 1)) ? -scale : scale;
1241           v     = v < 0 ? -(v + 1) : v;
1242           if (j != f_ind) { continue; }
1243           PetscReal *p_i = &p_f[i * Nk * npoints];
1244           for (PetscInt jet = 0; jet < Nk; jet++) {
1245             const PetscReal *h_jet = &h_s[jet * npoints];
1246             PetscReal       *p_jet = &p_i[jet * npoints];
1247 
1248             for (PetscInt pt = 0; pt < npoints; pt++) { p_jet[pt] += scale * h_jet[pt] * (points[pt * dim + v] - centroid); }
1249             PetscCall(PetscDTIndexToGradedOrder(dim, jet, deriv));
1250             deriv[v]++;
1251             PetscReal mult = deriv[v];
1252             PetscInt  l;
1253             PetscCall(PetscDTGradedOrderToIndex(dim, deriv, &l));
1254             if (l >= Nk) { continue; }
1255             p_jet = &p_i[l * npoints];
1256             for (PetscInt pt = 0; pt < npoints; pt++) { p_jet[pt] += scale * mult * h_jet[pt]; }
1257             deriv[v]--;
1258           }
1259         }
1260       }
1261     }
1262   }
1263   PetscCheck(total == Nbpt, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Incorrectly counted P trimmed polynomials");
1264   PetscCall(PetscFree(deriv));
1265   PetscCall(PetscFree(pattern));
1266   PetscCall(PetscFree(form_atoms));
1267   PetscCall(PetscFree(p_scalar));
1268   PetscFunctionReturn(0);
1269 }
1270 
1271 /*@
1272   PetscDTPTrimmedEvalJet - Evaluate the jet (function and derivatives) of a basis of the trimmed polynomial k-forms up to
1273   a given degree.
1274 
1275   Input Parameters:
1276 + dim - the number of variables in the multivariate polynomials
1277 . npoints - the number of points to evaluate the polynomials at
1278 . points - [npoints x dim] array of point coordinates
1279 . degree - the degree (sum of degrees on the variables in a monomial) of the trimmed polynomial space to evaluate.
1280            There are ((dim + degree) choose (dim + formDegree)) x ((degree + formDegree - 1) choose (formDegree)) polynomials in this space.
1281            (You can use PetscDTPTrimmedSize() to compute this size.)
1282 . formDegree - the degree of the form
1283 - jetDegree - the maximum order partial derivative to evaluate in the jet.  There are ((dim + jetDegree) choose dim) partial derivatives
1284               in the jet.  Choosing jetDegree = 0 means to evaluate just the function and no derivatives
1285 
1286   Output Parameters:
1287 - p - an array containing the evaluations of the PKD polynomials' jets on the points.  The size is
1288       PetscDTPTrimmedSize() x ((dim + formDegree) choose dim) x ((dim + k) choose dim) x npoints,
1289       which also describes the order of the dimensions of this
1290       four-dimensional array:
1291         the first (slowest varying) dimension is basis function index;
1292         the second dimension is component of the form;
1293         the third dimension is jet index;
1294         the fourth (fastest varying) dimension is the index of the evaluation point.
1295 
1296   Level: advanced
1297 
1298   Note: The ordering of the basis functions is not graded, so the basis functions are not nested by degree like PetscDTPKDEvalJet().
1299         The basis functions are not an L2-orthonormal basis on any particular domain.
1300 
1301   The implementation is based on the description of the trimmed polynomials up to degree r as
1302   the direct sum of polynomials up to degree (r-1) and the Koszul differential applied to
1303   homogeneous polynomials of degree (r-1).
1304 
1305 .seealso: `PetscDTPKDEvalJet()`, `PetscDTPTrimmedSize()`
1306 @*/
1307 PetscErrorCode PetscDTPTrimmedEvalJet(PetscInt dim, PetscInt npoints, const PetscReal points[], PetscInt degree, PetscInt formDegree, PetscInt jetDegree, PetscReal p[]) {
1308   PetscFunctionBegin;
1309   PetscCall(PetscDTPTrimmedEvalJet_Internal(dim, npoints, points, degree, formDegree, jetDegree, p));
1310   PetscFunctionReturn(0);
1311 }
1312 
1313 /* solve the symmetric tridiagonal eigenvalue system, writing the eigenvalues into eigs and the eigenvectors into V
1314  * with lds n; diag and subdiag are overwritten */
1315 static PetscErrorCode PetscDTSymmetricTridiagonalEigensolve(PetscInt n, PetscReal diag[], PetscReal subdiag[], PetscReal eigs[], PetscScalar V[]) {
1316   char          jobz   = 'V'; /* eigenvalues and eigenvectors */
1317   char          range  = 'A'; /* all eigenvalues will be found */
1318   PetscReal     VL     = 0.;  /* ignored because range is 'A' */
1319   PetscReal     VU     = 0.;  /* ignored because range is 'A' */
1320   PetscBLASInt  IL     = 0;   /* ignored because range is 'A' */
1321   PetscBLASInt  IU     = 0;   /* ignored because range is 'A' */
1322   PetscReal     abstol = 0.;  /* unused */
1323   PetscBLASInt  bn, bm, ldz;  /* bm will equal bn on exit */
1324   PetscBLASInt *isuppz;
1325   PetscBLASInt  lwork, liwork;
1326   PetscReal     workquery;
1327   PetscBLASInt  iworkquery;
1328   PetscBLASInt *iwork;
1329   PetscBLASInt  info;
1330   PetscReal    *work = NULL;
1331 
1332   PetscFunctionBegin;
1333 #if !defined(PETSCDTGAUSSIANQUADRATURE_EIG)
1334   SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP_SYS, "A LAPACK symmetric tridiagonal eigensolver could not be found");
1335 #endif
1336   PetscCall(PetscBLASIntCast(n, &bn));
1337   PetscCall(PetscBLASIntCast(n, &ldz));
1338 #if !defined(PETSC_MISSING_LAPACK_STEGR)
1339   PetscCall(PetscMalloc1(2 * n, &isuppz));
1340   lwork  = -1;
1341   liwork = -1;
1342   PetscCallBLAS("LAPACKstegr", LAPACKstegr_(&jobz, &range, &bn, diag, subdiag, &VL, &VU, &IL, &IU, &abstol, &bm, eigs, V, &ldz, isuppz, &workquery, &lwork, &iworkquery, &liwork, &info));
1343   PetscCheck(!info, PETSC_COMM_SELF, PETSC_ERR_PLIB, "xSTEGR error");
1344   lwork  = (PetscBLASInt)workquery;
1345   liwork = (PetscBLASInt)iworkquery;
1346   PetscCall(PetscMalloc2(lwork, &work, liwork, &iwork));
1347   PetscCall(PetscFPTrapPush(PETSC_FP_TRAP_OFF));
1348   PetscCallBLAS("LAPACKstegr", LAPACKstegr_(&jobz, &range, &bn, diag, subdiag, &VL, &VU, &IL, &IU, &abstol, &bm, eigs, V, &ldz, isuppz, work, &lwork, iwork, &liwork, &info));
1349   PetscCall(PetscFPTrapPop());
1350   PetscCheck(!info, PETSC_COMM_SELF, PETSC_ERR_PLIB, "xSTEGR error");
1351   PetscCall(PetscFree2(work, iwork));
1352   PetscCall(PetscFree(isuppz));
1353 #elif !defined(PETSC_MISSING_LAPACK_STEQR)
1354   jobz = 'I'; /* Compute eigenvalues and eigenvectors of the
1355                  tridiagonal matrix.  Z is initialized to the identity
1356                  matrix. */
1357   PetscCall(PetscMalloc1(PetscMax(1, 2 * n - 2), &work));
1358   PetscCallBLAS("LAPACKsteqr", LAPACKsteqr_("I", &bn, diag, subdiag, V, &ldz, work, &info));
1359   PetscCall(PetscFPTrapPop());
1360   PetscCheck(!info, PETSC_COMM_SELF, PETSC_ERR_PLIB, "xSTEQR error");
1361   PetscCall(PetscFree(work));
1362   PetscCall(PetscArraycpy(eigs, diag, n));
1363 #endif
1364   PetscFunctionReturn(0);
1365 }
1366 
1367 /* Formula for the weights at the endpoints (-1 and 1) of Gauss-Lobatto-Jacobi
1368  * quadrature rules on the interval [-1, 1] */
1369 static PetscErrorCode PetscDTGaussLobattoJacobiEndweights_Internal(PetscInt n, PetscReal alpha, PetscReal beta, PetscReal *leftw, PetscReal *rightw) {
1370   PetscReal twoab1;
1371   PetscInt  m = n - 2;
1372   PetscReal a = alpha + 1.;
1373   PetscReal b = beta + 1.;
1374   PetscReal gra, grb;
1375 
1376   PetscFunctionBegin;
1377   twoab1 = PetscPowReal(2., a + b - 1.);
1378 #if defined(PETSC_HAVE_LGAMMA)
1379   grb = PetscExpReal(2. * PetscLGamma(b + 1.) + PetscLGamma(m + 1.) + PetscLGamma(m + a + 1.) - (PetscLGamma(m + b + 1) + PetscLGamma(m + a + b + 1.)));
1380   gra = PetscExpReal(2. * PetscLGamma(a + 1.) + PetscLGamma(m + 1.) + PetscLGamma(m + b + 1.) - (PetscLGamma(m + a + 1) + PetscLGamma(m + a + b + 1.)));
1381 #else
1382   {
1383     PetscInt alphai = (PetscInt)alpha;
1384     PetscInt betai  = (PetscInt)beta;
1385 
1386     if ((PetscReal)alphai == alpha && (PetscReal)betai == beta) {
1387       PetscReal binom1, binom2;
1388 
1389       PetscCall(PetscDTBinomial(m + b, b, &binom1));
1390       PetscCall(PetscDTBinomial(m + a + b, b, &binom2));
1391       grb = 1. / (binom1 * binom2);
1392       PetscCall(PetscDTBinomial(m + a, a, &binom1));
1393       PetscCall(PetscDTBinomial(m + a + b, a, &binom2));
1394       gra = 1. / (binom1 * binom2);
1395     } else SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "lgamma() - math routine is unavailable.");
1396   }
1397 #endif
1398   *leftw  = twoab1 * grb / b;
1399   *rightw = twoab1 * gra / a;
1400   PetscFunctionReturn(0);
1401 }
1402 
1403 /* Evaluates the nth jacobi polynomial with weight parameters a,b at a point x.
1404    Recurrence relations implemented from the pseudocode given in Karniadakis and Sherwin, Appendix B */
1405 static inline PetscErrorCode PetscDTComputeJacobi(PetscReal a, PetscReal b, PetscInt n, PetscReal x, PetscReal *P) {
1406   PetscReal pn1, pn2;
1407   PetscReal cnm1, cnm1x, cnm2;
1408   PetscInt  k;
1409 
1410   PetscFunctionBegin;
1411   if (!n) {
1412     *P = 1.0;
1413     PetscFunctionReturn(0);
1414   }
1415   PetscDTJacobiRecurrence_Internal(1, a, b, cnm1, cnm1x, cnm2);
1416   pn2 = 1.;
1417   pn1 = cnm1 + cnm1x * x;
1418   if (n == 1) {
1419     *P = pn1;
1420     PetscFunctionReturn(0);
1421   }
1422   *P = 0.0;
1423   for (k = 2; k < n + 1; ++k) {
1424     PetscDTJacobiRecurrence_Internal(k, a, b, cnm1, cnm1x, cnm2);
1425 
1426     *P  = (cnm1 + cnm1x * x) * pn1 - cnm2 * pn2;
1427     pn2 = pn1;
1428     pn1 = *P;
1429   }
1430   PetscFunctionReturn(0);
1431 }
1432 
1433 /* Evaluates the first derivative of P_{n}^{a,b} at a point x. */
1434 static inline PetscErrorCode PetscDTComputeJacobiDerivative(PetscReal a, PetscReal b, PetscInt n, PetscReal x, PetscInt k, PetscReal *P) {
1435   PetscReal nP;
1436   PetscInt  i;
1437 
1438   PetscFunctionBegin;
1439   *P = 0.0;
1440   if (k > n) PetscFunctionReturn(0);
1441   PetscCall(PetscDTComputeJacobi(a + k, b + k, n - k, x, &nP));
1442   for (i = 0; i < k; i++) nP *= (a + b + n + 1. + i) * 0.5;
1443   *P = nP;
1444   PetscFunctionReturn(0);
1445 }
1446 
1447 static PetscErrorCode PetscDTGaussJacobiQuadrature_Newton_Internal(PetscInt npoints, PetscReal a, PetscReal b, PetscReal x[], PetscReal w[]) {
1448   PetscInt  maxIter = 100;
1449   PetscReal eps     = PetscExpReal(0.75 * PetscLogReal(PETSC_MACHINE_EPSILON));
1450   PetscReal a1, a6, gf;
1451   PetscInt  k;
1452 
1453   PetscFunctionBegin;
1454 
1455   a1 = PetscPowReal(2.0, a + b + 1);
1456 #if defined(PETSC_HAVE_LGAMMA)
1457   {
1458     PetscReal a2, a3, a4, a5;
1459     a2 = PetscLGamma(a + npoints + 1);
1460     a3 = PetscLGamma(b + npoints + 1);
1461     a4 = PetscLGamma(a + b + npoints + 1);
1462     a5 = PetscLGamma(npoints + 1);
1463     gf = PetscExpReal(a2 + a3 - (a4 + a5));
1464   }
1465 #else
1466   {
1467     PetscInt ia, ib;
1468 
1469     ia = (PetscInt)a;
1470     ib = (PetscInt)b;
1471     gf = 1.;
1472     if (ia == a && ia >= 0) { /* compute ratio of rising factorals wrt a */
1473       for (k = 0; k < ia; k++) gf *= (npoints + 1. + k) / (npoints + b + 1. + k);
1474     } else if (b == b && ib >= 0) { /* compute ratio of rising factorials wrt b */
1475       for (k = 0; k < ib; k++) gf *= (npoints + 1. + k) / (npoints + a + 1. + k);
1476     } else SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "lgamma() - math routine is unavailable.");
1477   }
1478 #endif
1479 
1480   a6 = a1 * gf;
1481   /* Computes the m roots of P_{m}^{a,b} on [-1,1] by Newton's method with Chebyshev points as initial guesses.
1482    Algorithm implemented from the pseudocode given by Karniadakis and Sherwin and Python in FIAT */
1483   for (k = 0; k < npoints; ++k) {
1484     PetscReal r = PetscCosReal(PETSC_PI * (1. - (4. * k + 3. + 2. * b) / (4. * npoints + 2. * (a + b + 1.)))), dP;
1485     PetscInt  j;
1486 
1487     if (k > 0) r = 0.5 * (r + x[k - 1]);
1488     for (j = 0; j < maxIter; ++j) {
1489       PetscReal s = 0.0, delta, f, fp;
1490       PetscInt  i;
1491 
1492       for (i = 0; i < k; ++i) s = s + 1.0 / (r - x[i]);
1493       PetscCall(PetscDTComputeJacobi(a, b, npoints, r, &f));
1494       PetscCall(PetscDTComputeJacobiDerivative(a, b, npoints, r, 1, &fp));
1495       delta = f / (fp - f * s);
1496       r     = r - delta;
1497       if (PetscAbsReal(delta) < eps) break;
1498     }
1499     x[k] = r;
1500     PetscCall(PetscDTComputeJacobiDerivative(a, b, npoints, x[k], 1, &dP));
1501     w[k] = a6 / (1.0 - PetscSqr(x[k])) / PetscSqr(dP);
1502   }
1503   PetscFunctionReturn(0);
1504 }
1505 
1506 /* Compute the diagonals of the Jacobi matrix used in Golub & Welsch algorithms for Gauss-Jacobi
1507  * quadrature weight calculations on [-1,1] for exponents (1. + x)^a (1.-x)^b */
1508 static PetscErrorCode PetscDTJacobiMatrix_Internal(PetscInt nPoints, PetscReal a, PetscReal b, PetscReal *d, PetscReal *s) {
1509   PetscInt i;
1510 
1511   PetscFunctionBegin;
1512   for (i = 0; i < nPoints; i++) {
1513     PetscReal A, B, C;
1514 
1515     PetscDTJacobiRecurrence_Internal(i + 1, a, b, A, B, C);
1516     d[i] = -A / B;
1517     if (i) s[i - 1] *= C / B;
1518     if (i < nPoints - 1) s[i] = 1. / B;
1519   }
1520   PetscFunctionReturn(0);
1521 }
1522 
1523 static PetscErrorCode PetscDTGaussJacobiQuadrature_GolubWelsch_Internal(PetscInt npoints, PetscReal a, PetscReal b, PetscReal *x, PetscReal *w) {
1524   PetscReal mu0;
1525   PetscReal ga, gb, gab;
1526   PetscInt  i;
1527 
1528   PetscFunctionBegin;
1529   PetscCall(PetscCitationsRegister(GolubWelschCitation, &GolubWelschCite));
1530 
1531 #if defined(PETSC_HAVE_TGAMMA)
1532   ga  = PetscTGamma(a + 1);
1533   gb  = PetscTGamma(b + 1);
1534   gab = PetscTGamma(a + b + 2);
1535 #else
1536   {
1537     PetscInt ia, ib;
1538 
1539     ia = (PetscInt)a;
1540     ib = (PetscInt)b;
1541     if (ia == a && ib == b && ia + 1 > 0 && ib + 1 > 0 && ia + ib + 2 > 0) { /* All gamma(x) terms are (x-1)! terms */
1542       PetscCall(PetscDTFactorial(ia, &ga));
1543       PetscCall(PetscDTFactorial(ib, &gb));
1544       PetscCall(PetscDTFactorial(ia + ib + 1, &gb));
1545     } else SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "tgamma() - math routine is unavailable.");
1546   }
1547 #endif
1548   mu0 = PetscPowReal(2., a + b + 1.) * ga * gb / gab;
1549 
1550 #if defined(PETSCDTGAUSSIANQUADRATURE_EIG)
1551   {
1552     PetscReal   *diag, *subdiag;
1553     PetscScalar *V;
1554 
1555     PetscCall(PetscMalloc2(npoints, &diag, npoints, &subdiag));
1556     PetscCall(PetscMalloc1(npoints * npoints, &V));
1557     PetscCall(PetscDTJacobiMatrix_Internal(npoints, a, b, diag, subdiag));
1558     for (i = 0; i < npoints - 1; i++) subdiag[i] = PetscSqrtReal(subdiag[i]);
1559     PetscCall(PetscDTSymmetricTridiagonalEigensolve(npoints, diag, subdiag, x, V));
1560     for (i = 0; i < npoints; i++) w[i] = PetscSqr(PetscRealPart(V[i * npoints])) * mu0;
1561     PetscCall(PetscFree(V));
1562     PetscCall(PetscFree2(diag, subdiag));
1563   }
1564 #else
1565   SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP_SYS, "A LAPACK symmetric tridiagonal eigensolver could not be found");
1566 #endif
1567   { /* As of March 2, 2020, The Sun Performance Library breaks the LAPACK contract for xstegr and xsteqr: the
1568        eigenvalues are not guaranteed to be in ascending order.  So we heave a passive aggressive sigh and check that
1569        the eigenvalues are sorted */
1570     PetscBool sorted;
1571 
1572     PetscCall(PetscSortedReal(npoints, x, &sorted));
1573     if (!sorted) {
1574       PetscInt  *order, i;
1575       PetscReal *tmp;
1576 
1577       PetscCall(PetscMalloc2(npoints, &order, npoints, &tmp));
1578       for (i = 0; i < npoints; i++) order[i] = i;
1579       PetscCall(PetscSortRealWithPermutation(npoints, x, order));
1580       PetscCall(PetscArraycpy(tmp, x, npoints));
1581       for (i = 0; i < npoints; i++) x[i] = tmp[order[i]];
1582       PetscCall(PetscArraycpy(tmp, w, npoints));
1583       for (i = 0; i < npoints; i++) w[i] = tmp[order[i]];
1584       PetscCall(PetscFree2(order, tmp));
1585     }
1586   }
1587   PetscFunctionReturn(0);
1588 }
1589 
1590 static PetscErrorCode PetscDTGaussJacobiQuadrature_Internal(PetscInt npoints, PetscReal alpha, PetscReal beta, PetscReal x[], PetscReal w[], PetscBool newton) {
1591   PetscFunctionBegin;
1592   PetscCheck(npoints >= 1, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Number of points must be positive");
1593   /* If asking for a 1D Lobatto point, just return the non-Lobatto 1D point */
1594   PetscCheck(alpha > -1., PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "alpha must be > -1.");
1595   PetscCheck(beta > -1., PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "beta must be > -1.");
1596 
1597   if (newton) PetscCall(PetscDTGaussJacobiQuadrature_Newton_Internal(npoints, alpha, beta, x, w));
1598   else PetscCall(PetscDTGaussJacobiQuadrature_GolubWelsch_Internal(npoints, alpha, beta, x, w));
1599   if (alpha == beta) { /* symmetrize */
1600     PetscInt i;
1601     for (i = 0; i < (npoints + 1) / 2; i++) {
1602       PetscInt  j  = npoints - 1 - i;
1603       PetscReal xi = x[i];
1604       PetscReal xj = x[j];
1605       PetscReal wi = w[i];
1606       PetscReal wj = w[j];
1607 
1608       x[i] = (xi - xj) / 2.;
1609       x[j] = (xj - xi) / 2.;
1610       w[i] = w[j] = (wi + wj) / 2.;
1611     }
1612   }
1613   PetscFunctionReturn(0);
1614 }
1615 
1616 /*@
1617   PetscDTGaussJacobiQuadrature - quadrature for the interval [a, b] with the weight function
1618   $(x-a)^\alpha (x-b)^\beta$.
1619 
1620   Not collective
1621 
1622   Input Parameters:
1623 + npoints - the number of points in the quadrature rule
1624 . a - the left endpoint of the interval
1625 . b - the right endpoint of the interval
1626 . alpha - the left exponent
1627 - beta - the right exponent
1628 
1629   Output Parameters:
1630 + x - array of length npoints, the locations of the quadrature points
1631 - w - array of length npoints, the weights of the quadrature points
1632 
1633   Level: intermediate
1634 
1635   Note: this quadrature rule is exact for polynomials up to degree 2*npoints - 1.
1636 @*/
1637 PetscErrorCode PetscDTGaussJacobiQuadrature(PetscInt npoints, PetscReal a, PetscReal b, PetscReal alpha, PetscReal beta, PetscReal x[], PetscReal w[]) {
1638   PetscInt i;
1639 
1640   PetscFunctionBegin;
1641   PetscCall(PetscDTGaussJacobiQuadrature_Internal(npoints, alpha, beta, x, w, PetscDTGaussQuadratureNewton_Internal));
1642   if (a != -1. || b != 1.) { /* shift */
1643     for (i = 0; i < npoints; i++) {
1644       x[i] = (x[i] + 1.) * ((b - a) / 2.) + a;
1645       w[i] *= (b - a) / 2.;
1646     }
1647   }
1648   PetscFunctionReturn(0);
1649 }
1650 
1651 static PetscErrorCode PetscDTGaussLobattoJacobiQuadrature_Internal(PetscInt npoints, PetscReal alpha, PetscReal beta, PetscReal x[], PetscReal w[], PetscBool newton) {
1652   PetscInt i;
1653 
1654   PetscFunctionBegin;
1655   PetscCheck(npoints >= 2, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Number of points must be positive");
1656   /* If asking for a 1D Lobatto point, just return the non-Lobatto 1D point */
1657   PetscCheck(alpha > -1., PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "alpha must be > -1.");
1658   PetscCheck(beta > -1., PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "beta must be > -1.");
1659 
1660   x[0]           = -1.;
1661   x[npoints - 1] = 1.;
1662   if (npoints > 2) { PetscCall(PetscDTGaussJacobiQuadrature_Internal(npoints - 2, alpha + 1., beta + 1., &x[1], &w[1], newton)); }
1663   for (i = 1; i < npoints - 1; i++) { w[i] /= (1. - x[i] * x[i]); }
1664   PetscCall(PetscDTGaussLobattoJacobiEndweights_Internal(npoints, alpha, beta, &w[0], &w[npoints - 1]));
1665   PetscFunctionReturn(0);
1666 }
1667 
1668 /*@
1669   PetscDTGaussLobattoJacobiQuadrature - quadrature for the interval [a, b] with the weight function
1670   $(x-a)^\alpha (x-b)^\beta$, with endpoints a and b included as quadrature points.
1671 
1672   Not collective
1673 
1674   Input Parameters:
1675 + npoints - the number of points in the quadrature rule
1676 . a - the left endpoint of the interval
1677 . b - the right endpoint of the interval
1678 . alpha - the left exponent
1679 - beta - the right exponent
1680 
1681   Output Parameters:
1682 + x - array of length npoints, the locations of the quadrature points
1683 - w - array of length npoints, the weights of the quadrature points
1684 
1685   Level: intermediate
1686 
1687   Note: this quadrature rule is exact for polynomials up to degree 2*npoints - 3.
1688 @*/
1689 PetscErrorCode PetscDTGaussLobattoJacobiQuadrature(PetscInt npoints, PetscReal a, PetscReal b, PetscReal alpha, PetscReal beta, PetscReal x[], PetscReal w[]) {
1690   PetscInt i;
1691 
1692   PetscFunctionBegin;
1693   PetscCall(PetscDTGaussLobattoJacobiQuadrature_Internal(npoints, alpha, beta, x, w, PetscDTGaussQuadratureNewton_Internal));
1694   if (a != -1. || b != 1.) { /* shift */
1695     for (i = 0; i < npoints; i++) {
1696       x[i] = (x[i] + 1.) * ((b - a) / 2.) + a;
1697       w[i] *= (b - a) / 2.;
1698     }
1699   }
1700   PetscFunctionReturn(0);
1701 }
1702 
1703 /*@
1704    PetscDTGaussQuadrature - create Gauss-Legendre quadrature
1705 
1706    Not Collective
1707 
1708    Input Parameters:
1709 +  npoints - number of points
1710 .  a - left end of interval (often-1)
1711 -  b - right end of interval (often +1)
1712 
1713    Output Parameters:
1714 +  x - quadrature points
1715 -  w - quadrature weights
1716 
1717    Level: intermediate
1718 
1719    References:
1720 .  * - Golub and Welsch, Calculation of Quadrature Rules, Math. Comp. 23(106), 1969.
1721 
1722 .seealso: `PetscDTLegendreEval()`
1723 @*/
1724 PetscErrorCode PetscDTGaussQuadrature(PetscInt npoints, PetscReal a, PetscReal b, PetscReal *x, PetscReal *w) {
1725   PetscInt i;
1726 
1727   PetscFunctionBegin;
1728   PetscCall(PetscDTGaussJacobiQuadrature_Internal(npoints, 0., 0., x, w, PetscDTGaussQuadratureNewton_Internal));
1729   if (a != -1. || b != 1.) { /* shift */
1730     for (i = 0; i < npoints; i++) {
1731       x[i] = (x[i] + 1.) * ((b - a) / 2.) + a;
1732       w[i] *= (b - a) / 2.;
1733     }
1734   }
1735   PetscFunctionReturn(0);
1736 }
1737 
1738 /*@C
1739    PetscDTGaussLobattoLegendreQuadrature - creates a set of the locations and weights of the Gauss-Lobatto-Legendre
1740                       nodes of a given size on the domain [-1,1]
1741 
1742    Not Collective
1743 
1744    Input Parameters:
1745 +  n - number of grid nodes
1746 -  type - PETSCGAUSSLOBATTOLEGENDRE_VIA_LINEAR_ALGEBRA or PETSCGAUSSLOBATTOLEGENDRE_VIA_NEWTON
1747 
1748    Output Parameters:
1749 +  x - quadrature points
1750 -  w - quadrature weights
1751 
1752    Notes:
1753     For n > 30  the Newton approach computes duplicate (incorrect) values for some nodes because the initial guess is apparently not
1754           close enough to the desired solution
1755 
1756    These are useful for implementing spectral methods based on Gauss-Lobatto-Legendre (GLL) nodes
1757 
1758    See  https://epubs.siam.org/doi/abs/10.1137/110855442  https://epubs.siam.org/doi/abs/10.1137/120889873 for better ways to compute GLL nodes
1759 
1760    Level: intermediate
1761 
1762 .seealso: `PetscDTGaussQuadrature()`
1763 
1764 @*/
1765 PetscErrorCode PetscDTGaussLobattoLegendreQuadrature(PetscInt npoints, PetscGaussLobattoLegendreCreateType type, PetscReal *x, PetscReal *w) {
1766   PetscBool newton;
1767 
1768   PetscFunctionBegin;
1769   PetscCheck(npoints >= 2, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Must provide at least 2 grid points per element");
1770   newton = (PetscBool)(type == PETSCGAUSSLOBATTOLEGENDRE_VIA_NEWTON);
1771   PetscCall(PetscDTGaussLobattoJacobiQuadrature_Internal(npoints, 0., 0., x, w, newton));
1772   PetscFunctionReturn(0);
1773 }
1774 
1775 /*@
1776   PetscDTGaussTensorQuadrature - creates a tensor-product Gauss quadrature
1777 
1778   Not Collective
1779 
1780   Input Parameters:
1781 + dim     - The spatial dimension
1782 . Nc      - The number of components
1783 . npoints - number of points in one dimension
1784 . a       - left end of interval (often-1)
1785 - b       - right end of interval (often +1)
1786 
1787   Output Parameter:
1788 . q - A PetscQuadrature object
1789 
1790   Level: intermediate
1791 
1792 .seealso: `PetscDTGaussQuadrature()`, `PetscDTLegendreEval()`
1793 @*/
1794 PetscErrorCode PetscDTGaussTensorQuadrature(PetscInt dim, PetscInt Nc, PetscInt npoints, PetscReal a, PetscReal b, PetscQuadrature *q) {
1795   PetscInt   totpoints = dim > 1 ? dim > 2 ? npoints * PetscSqr(npoints) : PetscSqr(npoints) : npoints, i, j, k, c;
1796   PetscReal *x, *w, *xw, *ww;
1797 
1798   PetscFunctionBegin;
1799   PetscCall(PetscMalloc1(totpoints * dim, &x));
1800   PetscCall(PetscMalloc1(totpoints * Nc, &w));
1801   /* Set up the Golub-Welsch system */
1802   switch (dim) {
1803   case 0:
1804     PetscCall(PetscFree(x));
1805     PetscCall(PetscFree(w));
1806     PetscCall(PetscMalloc1(1, &x));
1807     PetscCall(PetscMalloc1(Nc, &w));
1808     x[0] = 0.0;
1809     for (c = 0; c < Nc; ++c) w[c] = 1.0;
1810     break;
1811   case 1:
1812     PetscCall(PetscMalloc1(npoints, &ww));
1813     PetscCall(PetscDTGaussQuadrature(npoints, a, b, x, ww));
1814     for (i = 0; i < npoints; ++i)
1815       for (c = 0; c < Nc; ++c) w[i * Nc + c] = ww[i];
1816     PetscCall(PetscFree(ww));
1817     break;
1818   case 2:
1819     PetscCall(PetscMalloc2(npoints, &xw, npoints, &ww));
1820     PetscCall(PetscDTGaussQuadrature(npoints, a, b, xw, ww));
1821     for (i = 0; i < npoints; ++i) {
1822       for (j = 0; j < npoints; ++j) {
1823         x[(i * npoints + j) * dim + 0] = xw[i];
1824         x[(i * npoints + j) * dim + 1] = xw[j];
1825         for (c = 0; c < Nc; ++c) w[(i * npoints + j) * Nc + c] = ww[i] * ww[j];
1826       }
1827     }
1828     PetscCall(PetscFree2(xw, ww));
1829     break;
1830   case 3:
1831     PetscCall(PetscMalloc2(npoints, &xw, npoints, &ww));
1832     PetscCall(PetscDTGaussQuadrature(npoints, a, b, xw, ww));
1833     for (i = 0; i < npoints; ++i) {
1834       for (j = 0; j < npoints; ++j) {
1835         for (k = 0; k < npoints; ++k) {
1836           x[((i * npoints + j) * npoints + k) * dim + 0] = xw[i];
1837           x[((i * npoints + j) * npoints + k) * dim + 1] = xw[j];
1838           x[((i * npoints + j) * npoints + k) * dim + 2] = xw[k];
1839           for (c = 0; c < Nc; ++c) w[((i * npoints + j) * npoints + k) * Nc + c] = ww[i] * ww[j] * ww[k];
1840         }
1841       }
1842     }
1843     PetscCall(PetscFree2(xw, ww));
1844     break;
1845   default: SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Cannot construct quadrature rule for dimension %" PetscInt_FMT, dim);
1846   }
1847   PetscCall(PetscQuadratureCreate(PETSC_COMM_SELF, q));
1848   PetscCall(PetscQuadratureSetOrder(*q, 2 * npoints - 1));
1849   PetscCall(PetscQuadratureSetData(*q, dim, Nc, totpoints, x, w));
1850   PetscCall(PetscObjectChangeTypeName((PetscObject)*q, "GaussTensor"));
1851   PetscFunctionReturn(0);
1852 }
1853 
1854 /*@
1855   PetscDTStroudConicalQuadrature - create Stroud conical quadrature for a simplex
1856 
1857   Not Collective
1858 
1859   Input Parameters:
1860 + dim     - The simplex dimension
1861 . Nc      - The number of components
1862 . npoints - The number of points in one dimension
1863 . a       - left end of interval (often-1)
1864 - b       - right end of interval (often +1)
1865 
1866   Output Parameter:
1867 . q - A PetscQuadrature object
1868 
1869   Level: intermediate
1870 
1871   References:
1872 . * - Karniadakis and Sherwin.  FIAT
1873 
1874   Note: For dim == 1, this is Gauss-Legendre quadrature
1875 
1876 .seealso: `PetscDTGaussTensorQuadrature()`, `PetscDTGaussQuadrature()`
1877 @*/
1878 PetscErrorCode PetscDTStroudConicalQuadrature(PetscInt dim, PetscInt Nc, PetscInt npoints, PetscReal a, PetscReal b, PetscQuadrature *q) {
1879   PetscInt   totprev, totrem;
1880   PetscInt   totpoints;
1881   PetscReal *p1, *w1;
1882   PetscReal *x, *w;
1883   PetscInt   i, j, k, l, m, pt, c;
1884 
1885   PetscFunctionBegin;
1886   PetscCheck(!(a != -1.0) && !(b != 1.0), PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Must use default internal right now");
1887   totpoints = 1;
1888   for (i = 0, totpoints = 1; i < dim; i++) totpoints *= npoints;
1889   PetscCall(PetscMalloc1(totpoints * dim, &x));
1890   PetscCall(PetscMalloc1(totpoints * Nc, &w));
1891   PetscCall(PetscMalloc2(npoints, &p1, npoints, &w1));
1892   for (i = 0; i < totpoints * Nc; i++) w[i] = 1.;
1893   for (i = 0, totprev = 1, totrem = totpoints / npoints; i < dim; i++) {
1894     PetscReal mul;
1895 
1896     mul = PetscPowReal(2., -i);
1897     PetscCall(PetscDTGaussJacobiQuadrature(npoints, -1., 1., i, 0.0, p1, w1));
1898     for (pt = 0, l = 0; l < totprev; l++) {
1899       for (j = 0; j < npoints; j++) {
1900         for (m = 0; m < totrem; m++, pt++) {
1901           for (k = 0; k < i; k++) x[pt * dim + k] = (x[pt * dim + k] + 1.) * (1. - p1[j]) * 0.5 - 1.;
1902           x[pt * dim + i] = p1[j];
1903           for (c = 0; c < Nc; c++) w[pt * Nc + c] *= mul * w1[j];
1904         }
1905       }
1906     }
1907     totprev *= npoints;
1908     totrem /= npoints;
1909   }
1910   PetscCall(PetscFree2(p1, w1));
1911   PetscCall(PetscQuadratureCreate(PETSC_COMM_SELF, q));
1912   PetscCall(PetscQuadratureSetOrder(*q, 2 * npoints - 1));
1913   PetscCall(PetscQuadratureSetData(*q, dim, Nc, totpoints, x, w));
1914   PetscCall(PetscObjectChangeTypeName((PetscObject)*q, "StroudConical"));
1915   PetscFunctionReturn(0);
1916 }
1917 
1918 static PetscBool MinSymTriQuadCite       = PETSC_FALSE;
1919 const char       MinSymTriQuadCitation[] = "@article{WitherdenVincent2015,\n"
1920                                            "  title = {On the identification of symmetric quadrature rules for finite element methods},\n"
1921                                            "  journal = {Computers & Mathematics with Applications},\n"
1922                                            "  volume = {69},\n"
1923                                            "  number = {10},\n"
1924                                            "  pages = {1232-1241},\n"
1925                                            "  year = {2015},\n"
1926                                            "  issn = {0898-1221},\n"
1927                                            "  doi = {10.1016/j.camwa.2015.03.017},\n"
1928                                            "  url = {https://www.sciencedirect.com/science/article/pii/S0898122115001224},\n"
1929                                            "  author = {F.D. Witherden and P.E. Vincent},\n"
1930                                            "}\n";
1931 
1932 #include "petscdttriquadrules.h"
1933 
1934 static PetscBool MinSymTetQuadCite       = PETSC_FALSE;
1935 const char       MinSymTetQuadCitation[] = "@article{JaskowiecSukumar2021\n"
1936                                            "  author = {Jaskowiec, Jan and Sukumar, N.},\n"
1937                                            "  title = {High-order symmetric cubature rules for tetrahedra and pyramids},\n"
1938                                            "  journal = {International Journal for Numerical Methods in Engineering},\n"
1939                                            "  volume = {122},\n"
1940                                            "  number = {1},\n"
1941                                            "  pages = {148-171},\n"
1942                                            "  doi = {10.1002/nme.6528},\n"
1943                                            "  url = {https://onlinelibrary.wiley.com/doi/abs/10.1002/nme.6528},\n"
1944                                            "  eprint = {https://onlinelibrary.wiley.com/doi/pdf/10.1002/nme.6528},\n"
1945                                            "  year = {2021}\n"
1946                                            "}\n";
1947 
1948 #include "petscdttetquadrules.h"
1949 
1950 // https://en.wikipedia.org/wiki/Partition_(number_theory)
1951 static PetscErrorCode PetscDTPartitionNumber(PetscInt n, PetscInt *p) {
1952   // sequence A000041 in the OEIS
1953   const PetscInt partition[]   = {1, 1, 2, 3, 5, 7, 11, 15, 22, 30, 42, 56, 77, 101, 135, 176, 231, 297, 385, 490, 627, 792, 1002, 1255, 1575, 1958, 2436, 3010, 3718, 4565, 5604};
1954   PetscInt       tabulated_max = PETSC_STATIC_ARRAY_LENGTH(partition) - 1;
1955 
1956   PetscFunctionBegin;
1957   PetscCheck(n >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Partition number not defined for negative number %" PetscInt_FMT, n);
1958   // not implementing the pentagonal number recurrence, we don't need partition numbers for n that high
1959   PetscCheck(n <= tabulated_max, PETSC_COMM_SELF, PETSC_ERR_SUP, "Partition numbers only tabulated up to %" PetscInt_FMT ", not computed for %" PetscInt_FMT, tabulated_max, n);
1960   *p = partition[n];
1961   PetscFunctionReturn(0);
1962 }
1963 
1964 /*@
1965   PetscDTSimplexQuadrature - Create a quadrature rule for a simplex that exactly integrates polynomials up to a given degree.
1966 
1967   Not Collective
1968 
1969   Input Parameters:
1970 + dim     - The spatial dimension of the simplex (1 = segment, 2 = triangle, 3 = tetrahedron)
1971 . degree  - The largest polynomial degree that is required to be integrated exactly
1972 - type    - left end of interval (often-1)
1973 
1974   Output Parameter:
1975 . quad    - A PetscQuadrature object for integration over the biunit simplex
1976             (defined by the bounds $x_i >= -1$ and $\sum_i x_i <= 2 - d$) that is exact for
1977             polynomials up to the given degree
1978 
1979   Level: intermediate
1980 
1981 .seealso: `PetscDTSimplexQuadratureType`, `PetscDTGaussQuadrature()`, `PetscDTStroudCononicalQuadrature()`
1982 @*/
1983 PetscErrorCode PetscDTSimplexQuadrature(PetscInt dim, PetscInt degree, PetscDTSimplexQuadratureType type, PetscQuadrature *quad) {
1984   PetscDTSimplexQuadratureType orig_type = type;
1985 
1986   PetscFunctionBegin;
1987   PetscCheck(dim >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Negative dimension %" PetscInt_FMT, dim);
1988   PetscCheck(degree >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Negative degree %" PetscInt_FMT, degree);
1989   if (type == PETSCDTSIMPLEXQUAD_DEFAULT) { type = PETSCDTSIMPLEXQUAD_MINSYM; }
1990   if (type == PETSCDTSIMPLEXQUAD_CONIC || dim < 2) {
1991     PetscInt points_per_dim = (degree + 2) / 2; // ceil((degree + 1) / 2);
1992     PetscCall(PetscDTStroudConicalQuadrature(dim, 1, points_per_dim, -1, 1, quad));
1993   } else {
1994     PetscInt          n    = dim + 1;
1995     PetscInt          fact = 1;
1996     PetscInt         *part, *perm;
1997     PetscInt          p = 0;
1998     PetscInt          max_degree;
1999     const PetscInt   *nodes_per_type     = NULL;
2000     const PetscInt   *all_num_full_nodes = NULL;
2001     const PetscReal **weights_list       = NULL;
2002     const PetscReal **compact_nodes_list = NULL;
2003     const char       *citation           = NULL;
2004     PetscBool        *cited              = NULL;
2005 
2006     switch (dim) {
2007     case 2:
2008       cited              = &MinSymTriQuadCite;
2009       citation           = MinSymTriQuadCitation;
2010       max_degree         = PetscDTWVTriQuad_max_degree;
2011       nodes_per_type     = PetscDTWVTriQuad_num_orbits;
2012       all_num_full_nodes = PetscDTWVTriQuad_num_nodes;
2013       weights_list       = PetscDTWVTriQuad_weights;
2014       compact_nodes_list = PetscDTWVTriQuad_orbits;
2015       break;
2016     case 3:
2017       cited              = &MinSymTetQuadCite;
2018       citation           = MinSymTetQuadCitation;
2019       max_degree         = PetscDTJSTetQuad_max_degree;
2020       nodes_per_type     = PetscDTJSTetQuad_num_orbits;
2021       all_num_full_nodes = PetscDTJSTetQuad_num_nodes;
2022       weights_list       = PetscDTJSTetQuad_weights;
2023       compact_nodes_list = PetscDTJSTetQuad_orbits;
2024       break;
2025     default: max_degree = -1; break;
2026     }
2027 
2028     if (degree > max_degree) {
2029       if (orig_type == PETSCDTSIMPLEXQUAD_DEFAULT) {
2030         // fall back to conic
2031         PetscCall(PetscDTSimplexQuadrature(dim, degree, PETSCDTSIMPLEXQUAD_CONIC, quad));
2032         PetscFunctionReturn(0);
2033       } else SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Minimal symmetric quadrature for dim %" PetscInt_FMT ", degree %" PetscInt_FMT " unsupported", dim, degree);
2034     }
2035 
2036     PetscCall(PetscCitationsRegister(citation, cited));
2037 
2038     PetscCall(PetscDTPartitionNumber(n, &p));
2039     for (PetscInt d = 2; d <= n; d++) fact *= d;
2040 
2041     PetscInt         num_full_nodes      = all_num_full_nodes[degree];
2042     const PetscReal *all_compact_nodes   = compact_nodes_list[degree];
2043     const PetscReal *all_compact_weights = weights_list[degree];
2044     nodes_per_type                       = &nodes_per_type[p * degree];
2045 
2046     PetscReal      *points;
2047     PetscReal      *counts;
2048     PetscReal      *weights;
2049     PetscReal      *bary_to_biunit; // row-major transformation of barycentric coordinate to biunit
2050     PetscQuadrature q;
2051 
2052     // compute the transformation
2053     PetscCall(PetscMalloc1(n * dim, &bary_to_biunit));
2054     for (PetscInt d = 0; d < dim; d++) {
2055       for (PetscInt b = 0; b < n; b++) { bary_to_biunit[d * n + b] = (d == b) ? 1.0 : -1.0; }
2056     }
2057 
2058     PetscCall(PetscMalloc3(n, &part, n, &perm, n, &counts));
2059     PetscCall(PetscCalloc1(num_full_nodes * dim, &points));
2060     PetscCall(PetscMalloc1(num_full_nodes, &weights));
2061 
2062     // (0, 0, ...) is the first partition lexicographically
2063     PetscCall(PetscArrayzero(part, n));
2064     PetscCall(PetscArrayzero(counts, n));
2065     counts[0] = n;
2066 
2067     // for each partition
2068     for (PetscInt s = 0, node_offset = 0; s < p; s++) {
2069       PetscInt num_compact_coords = part[n - 1] + 1;
2070 
2071       const PetscReal *compact_nodes   = all_compact_nodes;
2072       const PetscReal *compact_weights = all_compact_weights;
2073       all_compact_nodes += num_compact_coords * nodes_per_type[s];
2074       all_compact_weights += nodes_per_type[s];
2075 
2076       // for every permutation of the vertices
2077       for (PetscInt f = 0; f < fact; f++) {
2078         PetscCall(PetscDTEnumPerm(n, f, perm, NULL));
2079 
2080         // check if it is a valid permutation
2081         PetscInt digit;
2082         for (digit = 1; digit < n; digit++) {
2083           // skip permutations that would duplicate a node because it has a smaller symmetry group
2084           if (part[digit - 1] == part[digit] && perm[digit - 1] > perm[digit]) break;
2085         }
2086         if (digit < n) continue;
2087 
2088         // create full nodes from this permutation of the compact nodes
2089         PetscReal *full_nodes   = &points[node_offset * dim];
2090         PetscReal *full_weights = &weights[node_offset];
2091 
2092         PetscCall(PetscArraycpy(full_weights, compact_weights, nodes_per_type[s]));
2093         for (PetscInt b = 0; b < n; b++) {
2094           for (PetscInt d = 0; d < dim; d++) {
2095             for (PetscInt node = 0; node < nodes_per_type[s]; node++) { full_nodes[node * dim + d] += bary_to_biunit[d * n + perm[b]] * compact_nodes[node * num_compact_coords + part[b]]; }
2096           }
2097         }
2098         node_offset += nodes_per_type[s];
2099       }
2100 
2101       if (s < p - 1) { // Generate the next partition
2102         /* A partition is described by the number of coordinates that are in
2103          * each set of duplicates (counts) and redundantly by mapping each
2104          * index to its set of duplicates (part)
2105          *
2106          * Counts should always be in nonincreasing order
2107          *
2108          * We want to generate the partitions lexically by part, which means
2109          * finding the last index where count > 1 and reducing by 1.
2110          *
2111          * For the new counts beyond that index, we eagerly assign the remaining
2112          * capacity of the partition to smaller indices (ensures lexical ordering),
2113          * while respecting the nonincreasing invariant of the counts
2114          */
2115         PetscInt last_digit            = part[n - 1];
2116         PetscInt last_digit_with_extra = last_digit;
2117         while (counts[last_digit_with_extra] == 1) last_digit_with_extra--;
2118         PetscInt limit               = --counts[last_digit_with_extra];
2119         PetscInt total_to_distribute = last_digit - last_digit_with_extra + 1;
2120         for (PetscInt digit = last_digit_with_extra + 1; digit < n; digit++) {
2121           counts[digit] = PetscMin(limit, total_to_distribute);
2122           total_to_distribute -= PetscMin(limit, total_to_distribute);
2123         }
2124         for (PetscInt digit = 0, offset = 0; digit < n; digit++) {
2125           PetscInt count = counts[digit];
2126           for (PetscInt c = 0; c < count; c++) { part[offset++] = digit; }
2127         }
2128       }
2129     }
2130     PetscCall(PetscFree3(part, perm, counts));
2131     PetscCall(PetscFree(bary_to_biunit));
2132     PetscCall(PetscQuadratureCreate(PETSC_COMM_SELF, &q));
2133     PetscCall(PetscQuadratureSetData(q, dim, 1, num_full_nodes, points, weights));
2134     *quad = q;
2135   }
2136   PetscFunctionReturn(0);
2137 }
2138 
2139 /*@
2140   PetscDTTanhSinhTensorQuadrature - create tanh-sinh quadrature for a tensor product cell
2141 
2142   Not Collective
2143 
2144   Input Parameters:
2145 + dim   - The cell dimension
2146 . level - The number of points in one dimension, 2^l
2147 . a     - left end of interval (often-1)
2148 - b     - right end of interval (often +1)
2149 
2150   Output Parameter:
2151 . q - A PetscQuadrature object
2152 
2153   Level: intermediate
2154 
2155 .seealso: `PetscDTGaussTensorQuadrature()`
2156 @*/
2157 PetscErrorCode PetscDTTanhSinhTensorQuadrature(PetscInt dim, PetscInt level, PetscReal a, PetscReal b, PetscQuadrature *q) {
2158   const PetscInt  p     = 16;                        /* Digits of precision in the evaluation */
2159   const PetscReal alpha = (b - a) / 2.;              /* Half-width of the integration interval */
2160   const PetscReal beta  = (b + a) / 2.;              /* Center of the integration interval */
2161   const PetscReal h     = PetscPowReal(2.0, -level); /* Step size, length between x_k */
2162   PetscReal       xk;                                /* Quadrature point x_k on reference domain [-1, 1] */
2163   PetscReal       wk = 0.5 * PETSC_PI;               /* Quadrature weight at x_k */
2164   PetscReal      *x, *w;
2165   PetscInt        K, k, npoints;
2166 
2167   PetscFunctionBegin;
2168   PetscCheck(dim <= 1, PETSC_COMM_SELF, PETSC_ERR_SUP, "Dimension %" PetscInt_FMT " not yet implemented", dim);
2169   PetscCheck(level, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Must give a number of significant digits");
2170   /* Find K such that the weights are < 32 digits of precision */
2171   for (K = 1; PetscAbsReal(PetscLog10Real(wk)) < 2 * p; ++K) { wk = 0.5 * h * PETSC_PI * PetscCoshReal(K * h) / PetscSqr(PetscCoshReal(0.5 * PETSC_PI * PetscSinhReal(K * h))); }
2172   PetscCall(PetscQuadratureCreate(PETSC_COMM_SELF, q));
2173   PetscCall(PetscQuadratureSetOrder(*q, 2 * K + 1));
2174   npoints = 2 * K - 1;
2175   PetscCall(PetscMalloc1(npoints * dim, &x));
2176   PetscCall(PetscMalloc1(npoints, &w));
2177   /* Center term */
2178   x[0] = beta;
2179   w[0] = 0.5 * alpha * PETSC_PI;
2180   for (k = 1; k < K; ++k) {
2181     wk           = 0.5 * alpha * h * PETSC_PI * PetscCoshReal(k * h) / PetscSqr(PetscCoshReal(0.5 * PETSC_PI * PetscSinhReal(k * h)));
2182     xk           = PetscTanhReal(0.5 * PETSC_PI * PetscSinhReal(k * h));
2183     x[2 * k - 1] = -alpha * xk + beta;
2184     w[2 * k - 1] = wk;
2185     x[2 * k + 0] = alpha * xk + beta;
2186     w[2 * k + 0] = wk;
2187   }
2188   PetscCall(PetscQuadratureSetData(*q, dim, 1, npoints, x, w));
2189   PetscFunctionReturn(0);
2190 }
2191 
2192 PetscErrorCode PetscDTTanhSinhIntegrate(void (*func)(const PetscReal[], void *, PetscReal *), PetscReal a, PetscReal b, PetscInt digits, void *ctx, PetscReal *sol) {
2193   const PetscInt  p     = 16;           /* Digits of precision in the evaluation */
2194   const PetscReal alpha = (b - a) / 2.; /* Half-width of the integration interval */
2195   const PetscReal beta  = (b + a) / 2.; /* Center of the integration interval */
2196   PetscReal       h     = 1.0;          /* Step size, length between x_k */
2197   PetscInt        l     = 0;            /* Level of refinement, h = 2^{-l} */
2198   PetscReal       osum  = 0.0;          /* Integral on last level */
2199   PetscReal       psum  = 0.0;          /* Integral on the level before the last level */
2200   PetscReal       sum;                  /* Integral on current level */
2201   PetscReal       yk;                   /* Quadrature point 1 - x_k on reference domain [-1, 1] */
2202   PetscReal       lx, rx;               /* Quadrature points to the left and right of 0 on the real domain [a, b] */
2203   PetscReal       wk;                   /* Quadrature weight at x_k */
2204   PetscReal       lval, rval;           /* Terms in the quadature sum to the left and right of 0 */
2205   PetscInt        d;                    /* Digits of precision in the integral */
2206 
2207   PetscFunctionBegin;
2208   PetscCheck(digits > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Must give a positive number of significant digits");
2209   /* Center term */
2210   func(&beta, ctx, &lval);
2211   sum = 0.5 * alpha * PETSC_PI * lval;
2212   /* */
2213   do {
2214     PetscReal lterm, rterm, maxTerm = 0.0, d1, d2, d3, d4;
2215     PetscInt  k = 1;
2216 
2217     ++l;
2218     /* PetscPrintf(PETSC_COMM_SELF, "LEVEL %" PetscInt_FMT " sum: %15.15f\n", l, sum); */
2219     /* At each level of refinement, h --> h/2 and sum --> sum/2 */
2220     psum = osum;
2221     osum = sum;
2222     h *= 0.5;
2223     sum *= 0.5;
2224     do {
2225       wk = 0.5 * h * PETSC_PI * PetscCoshReal(k * h) / PetscSqr(PetscCoshReal(0.5 * PETSC_PI * PetscSinhReal(k * h)));
2226       yk = 1.0 / (PetscExpReal(0.5 * PETSC_PI * PetscSinhReal(k * h)) * PetscCoshReal(0.5 * PETSC_PI * PetscSinhReal(k * h)));
2227       lx = -alpha * (1.0 - yk) + beta;
2228       rx = alpha * (1.0 - yk) + beta;
2229       func(&lx, ctx, &lval);
2230       func(&rx, ctx, &rval);
2231       lterm   = alpha * wk * lval;
2232       maxTerm = PetscMax(PetscAbsReal(lterm), maxTerm);
2233       sum += lterm;
2234       rterm   = alpha * wk * rval;
2235       maxTerm = PetscMax(PetscAbsReal(rterm), maxTerm);
2236       sum += rterm;
2237       ++k;
2238       /* Only need to evaluate every other point on refined levels */
2239       if (l != 1) ++k;
2240     } while (PetscAbsReal(PetscLog10Real(wk)) < p); /* Only need to evaluate sum until weights are < 32 digits of precision */
2241 
2242     d1 = PetscLog10Real(PetscAbsReal(sum - osum));
2243     d2 = PetscLog10Real(PetscAbsReal(sum - psum));
2244     d3 = PetscLog10Real(maxTerm) - p;
2245     if (PetscMax(PetscAbsReal(lterm), PetscAbsReal(rterm)) == 0.0) d4 = 0.0;
2246     else d4 = PetscLog10Real(PetscMax(PetscAbsReal(lterm), PetscAbsReal(rterm)));
2247     d = PetscAbsInt(PetscMin(0, PetscMax(PetscMax(PetscMax(PetscSqr(d1) / d2, 2 * d1), d3), d4)));
2248   } while (d < digits && l < 12);
2249   *sol = sum;
2250 
2251   PetscFunctionReturn(0);
2252 }
2253 
2254 #if defined(PETSC_HAVE_MPFR)
2255 PetscErrorCode PetscDTTanhSinhIntegrateMPFR(void (*func)(const PetscReal[], void *, PetscReal *), PetscReal a, PetscReal b, PetscInt digits, void *ctx, PetscReal *sol) {
2256   const PetscInt safetyFactor = 2; /* Calculate abcissa until 2*p digits */
2257   PetscInt       l            = 0; /* Level of refinement, h = 2^{-l} */
2258   mpfr_t         alpha;            /* Half-width of the integration interval */
2259   mpfr_t         beta;             /* Center of the integration interval */
2260   mpfr_t         h;                /* Step size, length between x_k */
2261   mpfr_t         osum;             /* Integral on last level */
2262   mpfr_t         psum;             /* Integral on the level before the last level */
2263   mpfr_t         sum;              /* Integral on current level */
2264   mpfr_t         yk;               /* Quadrature point 1 - x_k on reference domain [-1, 1] */
2265   mpfr_t         lx, rx;           /* Quadrature points to the left and right of 0 on the real domain [a, b] */
2266   mpfr_t         wk;               /* Quadrature weight at x_k */
2267   PetscReal      lval, rval, rtmp; /* Terms in the quadature sum to the left and right of 0 */
2268   PetscInt       d;                /* Digits of precision in the integral */
2269   mpfr_t         pi2, kh, msinh, mcosh, maxTerm, curTerm, tmp;
2270 
2271   PetscFunctionBegin;
2272   PetscCheck(digits > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Must give a positive number of significant digits");
2273   /* Create high precision storage */
2274   mpfr_inits2(PetscCeilReal(safetyFactor * digits * PetscLogReal(10.) / PetscLogReal(2.)), alpha, beta, h, sum, osum, psum, yk, wk, lx, rx, tmp, maxTerm, curTerm, pi2, kh, msinh, mcosh, NULL);
2275   /* Initialization */
2276   mpfr_set_d(alpha, 0.5 * (b - a), MPFR_RNDN);
2277   mpfr_set_d(beta, 0.5 * (b + a), MPFR_RNDN);
2278   mpfr_set_d(osum, 0.0, MPFR_RNDN);
2279   mpfr_set_d(psum, 0.0, MPFR_RNDN);
2280   mpfr_set_d(h, 1.0, MPFR_RNDN);
2281   mpfr_const_pi(pi2, MPFR_RNDN);
2282   mpfr_mul_d(pi2, pi2, 0.5, MPFR_RNDN);
2283   /* Center term */
2284   rtmp = 0.5 * (b + a);
2285   func(&rtmp, ctx, &lval);
2286   mpfr_set(sum, pi2, MPFR_RNDN);
2287   mpfr_mul(sum, sum, alpha, MPFR_RNDN);
2288   mpfr_mul_d(sum, sum, lval, MPFR_RNDN);
2289   /* */
2290   do {
2291     PetscReal d1, d2, d3, d4;
2292     PetscInt  k = 1;
2293 
2294     ++l;
2295     mpfr_set_d(maxTerm, 0.0, MPFR_RNDN);
2296     /* PetscPrintf(PETSC_COMM_SELF, "LEVEL %" PetscInt_FMT " sum: %15.15f\n", l, sum); */
2297     /* At each level of refinement, h --> h/2 and sum --> sum/2 */
2298     mpfr_set(psum, osum, MPFR_RNDN);
2299     mpfr_set(osum, sum, MPFR_RNDN);
2300     mpfr_mul_d(h, h, 0.5, MPFR_RNDN);
2301     mpfr_mul_d(sum, sum, 0.5, MPFR_RNDN);
2302     do {
2303       mpfr_set_si(kh, k, MPFR_RNDN);
2304       mpfr_mul(kh, kh, h, MPFR_RNDN);
2305       /* Weight */
2306       mpfr_set(wk, h, MPFR_RNDN);
2307       mpfr_sinh_cosh(msinh, mcosh, kh, MPFR_RNDN);
2308       mpfr_mul(msinh, msinh, pi2, MPFR_RNDN);
2309       mpfr_mul(mcosh, mcosh, pi2, MPFR_RNDN);
2310       mpfr_cosh(tmp, msinh, MPFR_RNDN);
2311       mpfr_sqr(tmp, tmp, MPFR_RNDN);
2312       mpfr_mul(wk, wk, mcosh, MPFR_RNDN);
2313       mpfr_div(wk, wk, tmp, MPFR_RNDN);
2314       /* Abscissa */
2315       mpfr_set_d(yk, 1.0, MPFR_RNDZ);
2316       mpfr_cosh(tmp, msinh, MPFR_RNDN);
2317       mpfr_div(yk, yk, tmp, MPFR_RNDZ);
2318       mpfr_exp(tmp, msinh, MPFR_RNDN);
2319       mpfr_div(yk, yk, tmp, MPFR_RNDZ);
2320       /* Quadrature points */
2321       mpfr_sub_d(lx, yk, 1.0, MPFR_RNDZ);
2322       mpfr_mul(lx, lx, alpha, MPFR_RNDU);
2323       mpfr_add(lx, lx, beta, MPFR_RNDU);
2324       mpfr_d_sub(rx, 1.0, yk, MPFR_RNDZ);
2325       mpfr_mul(rx, rx, alpha, MPFR_RNDD);
2326       mpfr_add(rx, rx, beta, MPFR_RNDD);
2327       /* Evaluation */
2328       rtmp = mpfr_get_d(lx, MPFR_RNDU);
2329       func(&rtmp, ctx, &lval);
2330       rtmp = mpfr_get_d(rx, MPFR_RNDD);
2331       func(&rtmp, ctx, &rval);
2332       /* Update */
2333       mpfr_mul(tmp, wk, alpha, MPFR_RNDN);
2334       mpfr_mul_d(tmp, tmp, lval, MPFR_RNDN);
2335       mpfr_add(sum, sum, tmp, MPFR_RNDN);
2336       mpfr_abs(tmp, tmp, MPFR_RNDN);
2337       mpfr_max(maxTerm, maxTerm, tmp, MPFR_RNDN);
2338       mpfr_set(curTerm, tmp, MPFR_RNDN);
2339       mpfr_mul(tmp, wk, alpha, MPFR_RNDN);
2340       mpfr_mul_d(tmp, tmp, rval, MPFR_RNDN);
2341       mpfr_add(sum, sum, tmp, MPFR_RNDN);
2342       mpfr_abs(tmp, tmp, MPFR_RNDN);
2343       mpfr_max(maxTerm, maxTerm, tmp, MPFR_RNDN);
2344       mpfr_max(curTerm, curTerm, tmp, MPFR_RNDN);
2345       ++k;
2346       /* Only need to evaluate every other point on refined levels */
2347       if (l != 1) ++k;
2348       mpfr_log10(tmp, wk, MPFR_RNDN);
2349       mpfr_abs(tmp, tmp, MPFR_RNDN);
2350     } while (mpfr_get_d(tmp, MPFR_RNDN) < safetyFactor * digits); /* Only need to evaluate sum until weights are < 32 digits of precision */
2351     mpfr_sub(tmp, sum, osum, MPFR_RNDN);
2352     mpfr_abs(tmp, tmp, MPFR_RNDN);
2353     mpfr_log10(tmp, tmp, MPFR_RNDN);
2354     d1 = mpfr_get_d(tmp, MPFR_RNDN);
2355     mpfr_sub(tmp, sum, psum, MPFR_RNDN);
2356     mpfr_abs(tmp, tmp, MPFR_RNDN);
2357     mpfr_log10(tmp, tmp, MPFR_RNDN);
2358     d2 = mpfr_get_d(tmp, MPFR_RNDN);
2359     mpfr_log10(tmp, maxTerm, MPFR_RNDN);
2360     d3 = mpfr_get_d(tmp, MPFR_RNDN) - digits;
2361     mpfr_log10(tmp, curTerm, MPFR_RNDN);
2362     d4 = mpfr_get_d(tmp, MPFR_RNDN);
2363     d  = PetscAbsInt(PetscMin(0, PetscMax(PetscMax(PetscMax(PetscSqr(d1) / d2, 2 * d1), d3), d4)));
2364   } while (d < digits && l < 8);
2365   *sol = mpfr_get_d(sum, MPFR_RNDN);
2366   /* Cleanup */
2367   mpfr_clears(alpha, beta, h, sum, osum, psum, yk, wk, lx, rx, tmp, maxTerm, curTerm, pi2, kh, msinh, mcosh, NULL);
2368   PetscFunctionReturn(0);
2369 }
2370 #else
2371 
2372 PetscErrorCode PetscDTTanhSinhIntegrateMPFR(void (*func)(const PetscReal[], void *, PetscReal *), PetscReal a, PetscReal b, PetscInt digits, void *ctx, PetscReal *sol) {
2373   SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "This method will not work without MPFR. Reconfigure using --download-mpfr --download-gmp");
2374 }
2375 #endif
2376 
2377 /*@
2378   PetscDTTensorQuadratureCreate - create the tensor product quadrature from two lower-dimensional quadratures
2379 
2380   Not Collective
2381 
2382   Input Parameters:
2383 + q1 - The first quadrature
2384 - q2 - The second quadrature
2385 
2386   Output Parameter:
2387 . q - A PetscQuadrature object
2388 
2389   Level: intermediate
2390 
2391 .seealso: `PetscDTGaussTensorQuadrature()`
2392 @*/
2393 PetscErrorCode PetscDTTensorQuadratureCreate(PetscQuadrature q1, PetscQuadrature q2, PetscQuadrature *q) {
2394   const PetscReal *x1, *w1, *x2, *w2;
2395   PetscReal       *x, *w;
2396   PetscInt         dim1, Nc1, Np1, order1, qa, d1;
2397   PetscInt         dim2, Nc2, Np2, order2, qb, d2;
2398   PetscInt         dim, Nc, Np, order, qc, d;
2399 
2400   PetscFunctionBegin;
2401   PetscValidHeaderSpecific(q1, PETSCQUADRATURE_CLASSID, 1);
2402   PetscValidHeaderSpecific(q2, PETSCQUADRATURE_CLASSID, 2);
2403   PetscValidPointer(q, 3);
2404   PetscCall(PetscQuadratureGetOrder(q1, &order1));
2405   PetscCall(PetscQuadratureGetOrder(q2, &order2));
2406   PetscCheck(order1 == order2, PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "Order1 %" PetscInt_FMT " != %" PetscInt_FMT " Order2", order1, order2);
2407   PetscCall(PetscQuadratureGetData(q1, &dim1, &Nc1, &Np1, &x1, &w1));
2408   PetscCall(PetscQuadratureGetData(q2, &dim2, &Nc2, &Np2, &x2, &w2));
2409   PetscCheck(Nc1 == Nc2, PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "NumComp1 %" PetscInt_FMT " != %" PetscInt_FMT " NumComp2", Nc1, Nc2);
2410 
2411   dim   = dim1 + dim2;
2412   Nc    = Nc1;
2413   Np    = Np1 * Np2;
2414   order = order1;
2415   PetscCall(PetscQuadratureCreate(PETSC_COMM_SELF, q));
2416   PetscCall(PetscQuadratureSetOrder(*q, order));
2417   PetscCall(PetscMalloc1(Np * dim, &x));
2418   PetscCall(PetscMalloc1(Np, &w));
2419   for (qa = 0, qc = 0; qa < Np1; ++qa) {
2420     for (qb = 0; qb < Np2; ++qb, ++qc) {
2421       for (d1 = 0, d = 0; d1 < dim1; ++d1, ++d) { x[qc * dim + d] = x1[qa * dim1 + d1]; }
2422       for (d2 = 0; d2 < dim2; ++d2, ++d) { x[qc * dim + d] = x2[qb * dim2 + d2]; }
2423       w[qc] = w1[qa] * w2[qb];
2424     }
2425   }
2426   PetscCall(PetscQuadratureSetData(*q, dim, Nc, Np, x, w));
2427   PetscFunctionReturn(0);
2428 }
2429 
2430 /* Overwrites A. Can only handle full-rank problems with m>=n
2431  * A in column-major format
2432  * Ainv in row-major format
2433  * tau has length m
2434  * worksize must be >= max(1,n)
2435  */
2436 static PetscErrorCode PetscDTPseudoInverseQR(PetscInt m, PetscInt mstride, PetscInt n, PetscReal *A_in, PetscReal *Ainv_out, PetscScalar *tau, PetscInt worksize, PetscScalar *work) {
2437   PetscBLASInt M, N, K, lda, ldb, ldwork, info;
2438   PetscScalar *A, *Ainv, *R, *Q, Alpha;
2439 
2440   PetscFunctionBegin;
2441 #if defined(PETSC_USE_COMPLEX)
2442   {
2443     PetscInt i, j;
2444     PetscCall(PetscMalloc2(m * n, &A, m * n, &Ainv));
2445     for (j = 0; j < n; j++) {
2446       for (i = 0; i < m; i++) A[i + m * j] = A_in[i + mstride * j];
2447     }
2448     mstride = m;
2449   }
2450 #else
2451   A = A_in;
2452   Ainv = Ainv_out;
2453 #endif
2454 
2455   PetscCall(PetscBLASIntCast(m, &M));
2456   PetscCall(PetscBLASIntCast(n, &N));
2457   PetscCall(PetscBLASIntCast(mstride, &lda));
2458   PetscCall(PetscBLASIntCast(worksize, &ldwork));
2459   PetscCall(PetscFPTrapPush(PETSC_FP_TRAP_OFF));
2460   PetscCallBLAS("LAPACKgeqrf", LAPACKgeqrf_(&M, &N, A, &lda, tau, work, &ldwork, &info));
2461   PetscCall(PetscFPTrapPop());
2462   PetscCheck(!info, PETSC_COMM_SELF, PETSC_ERR_LIB, "xGEQRF error");
2463   R = A; /* Upper triangular part of A now contains R, the rest contains the elementary reflectors */
2464 
2465   /* Extract an explicit representation of Q */
2466   Q = Ainv;
2467   PetscCall(PetscArraycpy(Q, A, mstride * n));
2468   K = N; /* full rank */
2469   PetscCallBLAS("LAPACKorgqr", LAPACKorgqr_(&M, &N, &K, Q, &lda, tau, work, &ldwork, &info));
2470   PetscCheck(!info, PETSC_COMM_SELF, PETSC_ERR_LIB, "xORGQR/xUNGQR error");
2471 
2472   /* Compute A^{-T} = (R^{-1} Q^T)^T = Q R^{-T} */
2473   Alpha = 1.0;
2474   ldb   = lda;
2475   PetscCallBLAS("BLAStrsm", BLAStrsm_("Right", "Upper", "ConjugateTranspose", "NotUnitTriangular", &M, &N, &Alpha, R, &lda, Q, &ldb));
2476   /* Ainv is Q, overwritten with inverse */
2477 
2478 #if defined(PETSC_USE_COMPLEX)
2479   {
2480     PetscInt i;
2481     for (i = 0; i < m * n; i++) Ainv_out[i] = PetscRealPart(Ainv[i]);
2482     PetscCall(PetscFree2(A, Ainv));
2483   }
2484 #endif
2485   PetscFunctionReturn(0);
2486 }
2487 
2488 /* Computes integral of L_p' over intervals {(x0,x1),(x1,x2),...} */
2489 static PetscErrorCode PetscDTLegendreIntegrate(PetscInt ninterval, const PetscReal *x, PetscInt ndegree, const PetscInt *degrees, PetscBool Transpose, PetscReal *B) {
2490   PetscReal *Bv;
2491   PetscInt   i, j;
2492 
2493   PetscFunctionBegin;
2494   PetscCall(PetscMalloc1((ninterval + 1) * ndegree, &Bv));
2495   /* Point evaluation of L_p on all the source vertices */
2496   PetscCall(PetscDTLegendreEval(ninterval + 1, x, ndegree, degrees, Bv, NULL, NULL));
2497   /* Integral over each interval: \int_a^b L_p' = L_p(b)-L_p(a) */
2498   for (i = 0; i < ninterval; i++) {
2499     for (j = 0; j < ndegree; j++) {
2500       if (Transpose) B[i + ninterval * j] = Bv[(i + 1) * ndegree + j] - Bv[i * ndegree + j];
2501       else B[i * ndegree + j] = Bv[(i + 1) * ndegree + j] - Bv[i * ndegree + j];
2502     }
2503   }
2504   PetscCall(PetscFree(Bv));
2505   PetscFunctionReturn(0);
2506 }
2507 
2508 /*@
2509    PetscDTReconstructPoly - create matrix representing polynomial reconstruction using cell intervals and evaluation at target intervals
2510 
2511    Not Collective
2512 
2513    Input Parameters:
2514 +  degree - degree of reconstruction polynomial
2515 .  nsource - number of source intervals
2516 .  sourcex - sorted coordinates of source cell boundaries (length nsource+1)
2517 .  ntarget - number of target intervals
2518 -  targetx - sorted coordinates of target cell boundaries (length ntarget+1)
2519 
2520    Output Parameter:
2521 .  R - reconstruction matrix, utarget = sum_s R[t*nsource+s] * usource[s]
2522 
2523    Level: advanced
2524 
2525 .seealso: `PetscDTLegendreEval()`
2526 @*/
2527 PetscErrorCode PetscDTReconstructPoly(PetscInt degree, PetscInt nsource, const PetscReal *sourcex, PetscInt ntarget, const PetscReal *targetx, PetscReal *R) {
2528   PetscInt     i, j, k, *bdegrees, worksize;
2529   PetscReal    xmin, xmax, center, hscale, *sourcey, *targety, *Bsource, *Bsinv, *Btarget;
2530   PetscScalar *tau, *work;
2531 
2532   PetscFunctionBegin;
2533   PetscValidRealPointer(sourcex, 3);
2534   PetscValidRealPointer(targetx, 5);
2535   PetscValidRealPointer(R, 6);
2536   PetscCheck(degree < nsource, PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "Reconstruction degree %" PetscInt_FMT " must be less than number of source intervals %" PetscInt_FMT, degree, nsource);
2537   if (PetscDefined(USE_DEBUG)) {
2538     for (i = 0; i < nsource; i++) { PetscCheck(sourcex[i] < sourcex[i + 1], PETSC_COMM_SELF, PETSC_ERR_ARG_CORRUPT, "Source interval %" PetscInt_FMT " has negative orientation (%g,%g)", i, (double)sourcex[i], (double)sourcex[i + 1]); }
2539     for (i = 0; i < ntarget; i++) { PetscCheck(targetx[i] < targetx[i + 1], PETSC_COMM_SELF, PETSC_ERR_ARG_CORRUPT, "Target interval %" PetscInt_FMT " has negative orientation (%g,%g)", i, (double)targetx[i], (double)targetx[i + 1]); }
2540   }
2541   xmin     = PetscMin(sourcex[0], targetx[0]);
2542   xmax     = PetscMax(sourcex[nsource], targetx[ntarget]);
2543   center   = (xmin + xmax) / 2;
2544   hscale   = (xmax - xmin) / 2;
2545   worksize = nsource;
2546   PetscCall(PetscMalloc4(degree + 1, &bdegrees, nsource + 1, &sourcey, nsource * (degree + 1), &Bsource, worksize, &work));
2547   PetscCall(PetscMalloc4(nsource, &tau, nsource * (degree + 1), &Bsinv, ntarget + 1, &targety, ntarget * (degree + 1), &Btarget));
2548   for (i = 0; i <= nsource; i++) sourcey[i] = (sourcex[i] - center) / hscale;
2549   for (i = 0; i <= degree; i++) bdegrees[i] = i + 1;
2550   PetscCall(PetscDTLegendreIntegrate(nsource, sourcey, degree + 1, bdegrees, PETSC_TRUE, Bsource));
2551   PetscCall(PetscDTPseudoInverseQR(nsource, nsource, degree + 1, Bsource, Bsinv, tau, nsource, work));
2552   for (i = 0; i <= ntarget; i++) targety[i] = (targetx[i] - center) / hscale;
2553   PetscCall(PetscDTLegendreIntegrate(ntarget, targety, degree + 1, bdegrees, PETSC_FALSE, Btarget));
2554   for (i = 0; i < ntarget; i++) {
2555     PetscReal rowsum = 0;
2556     for (j = 0; j < nsource; j++) {
2557       PetscReal sum = 0;
2558       for (k = 0; k < degree + 1; k++) { sum += Btarget[i * (degree + 1) + k] * Bsinv[k * nsource + j]; }
2559       R[i * nsource + j] = sum;
2560       rowsum += sum;
2561     }
2562     for (j = 0; j < nsource; j++) R[i * nsource + j] /= rowsum; /* normalize each row */
2563   }
2564   PetscCall(PetscFree4(bdegrees, sourcey, Bsource, work));
2565   PetscCall(PetscFree4(tau, Bsinv, targety, Btarget));
2566   PetscFunctionReturn(0);
2567 }
2568 
2569 /*@C
2570    PetscGaussLobattoLegendreIntegrate - Compute the L2 integral of a function on the GLL points
2571 
2572    Not Collective
2573 
2574    Input Parameters:
2575 +  n - the number of GLL nodes
2576 .  nodes - the GLL nodes
2577 .  weights - the GLL weights
2578 -  f - the function values at the nodes
2579 
2580    Output Parameter:
2581 .  in - the value of the integral
2582 
2583    Level: beginner
2584 
2585 .seealso: `PetscDTGaussLobattoLegendreQuadrature()`
2586 
2587 @*/
2588 PetscErrorCode PetscGaussLobattoLegendreIntegrate(PetscInt n, PetscReal *nodes, PetscReal *weights, const PetscReal *f, PetscReal *in) {
2589   PetscInt i;
2590 
2591   PetscFunctionBegin;
2592   *in = 0.;
2593   for (i = 0; i < n; i++) { *in += f[i] * f[i] * weights[i]; }
2594   PetscFunctionReturn(0);
2595 }
2596 
2597 /*@C
2598    PetscGaussLobattoLegendreElementLaplacianCreate - computes the Laplacian for a single 1d GLL element
2599 
2600    Not Collective
2601 
2602    Input Parameters:
2603 +  n - the number of GLL nodes
2604 .  nodes - the GLL nodes
2605 -  weights - the GLL weights
2606 
2607    Output Parameter:
2608 .  A - the stiffness element
2609 
2610    Level: beginner
2611 
2612    Notes:
2613     Destroy this with PetscGaussLobattoLegendreElementLaplacianDestroy()
2614 
2615    You can access entries in this array with AA[i][j] but in memory it is stored in contiguous memory, row oriented (the array is symmetric)
2616 
2617 .seealso: `PetscDTGaussLobattoLegendreQuadrature()`, `PetscGaussLobattoLegendreElementLaplacianDestroy()`
2618 
2619 @*/
2620 PetscErrorCode PetscGaussLobattoLegendreElementLaplacianCreate(PetscInt n, PetscReal *nodes, PetscReal *weights, PetscReal ***AA) {
2621   PetscReal      **A;
2622   const PetscReal *gllnodes = nodes;
2623   const PetscInt   p        = n - 1;
2624   PetscReal        z0, z1, z2 = -1, x, Lpj, Lpr;
2625   PetscInt         i, j, nn, r;
2626 
2627   PetscFunctionBegin;
2628   PetscCall(PetscMalloc1(n, &A));
2629   PetscCall(PetscMalloc1(n * n, &A[0]));
2630   for (i = 1; i < n; i++) A[i] = A[i - 1] + n;
2631 
2632   for (j = 1; j < p; j++) {
2633     x  = gllnodes[j];
2634     z0 = 1.;
2635     z1 = x;
2636     for (nn = 1; nn < p; nn++) {
2637       z2 = x * z1 * (2. * ((PetscReal)nn) + 1.) / (((PetscReal)nn) + 1.) - z0 * (((PetscReal)nn) / (((PetscReal)nn) + 1.));
2638       z0 = z1;
2639       z1 = z2;
2640     }
2641     Lpj = z2;
2642     for (r = 1; r < p; r++) {
2643       if (r == j) {
2644         A[j][j] = 2. / (3. * (1. - gllnodes[j] * gllnodes[j]) * Lpj * Lpj);
2645       } else {
2646         x  = gllnodes[r];
2647         z0 = 1.;
2648         z1 = x;
2649         for (nn = 1; nn < p; nn++) {
2650           z2 = x * z1 * (2. * ((PetscReal)nn) + 1.) / (((PetscReal)nn) + 1.) - z0 * (((PetscReal)nn) / (((PetscReal)nn) + 1.));
2651           z0 = z1;
2652           z1 = z2;
2653         }
2654         Lpr     = z2;
2655         A[r][j] = 4. / (((PetscReal)p) * (((PetscReal)p) + 1.) * Lpj * Lpr * (gllnodes[j] - gllnodes[r]) * (gllnodes[j] - gllnodes[r]));
2656       }
2657     }
2658   }
2659   for (j = 1; j < p + 1; j++) {
2660     x  = gllnodes[j];
2661     z0 = 1.;
2662     z1 = x;
2663     for (nn = 1; nn < p; nn++) {
2664       z2 = x * z1 * (2. * ((PetscReal)nn) + 1.) / (((PetscReal)nn) + 1.) - z0 * (((PetscReal)nn) / (((PetscReal)nn) + 1.));
2665       z0 = z1;
2666       z1 = z2;
2667     }
2668     Lpj     = z2;
2669     A[j][0] = 4. * PetscPowRealInt(-1., p) / (((PetscReal)p) * (((PetscReal)p) + 1.) * Lpj * (1. + gllnodes[j]) * (1. + gllnodes[j]));
2670     A[0][j] = A[j][0];
2671   }
2672   for (j = 0; j < p; j++) {
2673     x  = gllnodes[j];
2674     z0 = 1.;
2675     z1 = x;
2676     for (nn = 1; nn < p; nn++) {
2677       z2 = x * z1 * (2. * ((PetscReal)nn) + 1.) / (((PetscReal)nn) + 1.) - z0 * (((PetscReal)nn) / (((PetscReal)nn) + 1.));
2678       z0 = z1;
2679       z1 = z2;
2680     }
2681     Lpj = z2;
2682 
2683     A[p][j] = 4. / (((PetscReal)p) * (((PetscReal)p) + 1.) * Lpj * (1. - gllnodes[j]) * (1. - gllnodes[j]));
2684     A[j][p] = A[p][j];
2685   }
2686   A[0][0] = 0.5 + (((PetscReal)p) * (((PetscReal)p) + 1.) - 2.) / 6.;
2687   A[p][p] = A[0][0];
2688   *AA     = A;
2689   PetscFunctionReturn(0);
2690 }
2691 
2692 /*@C
2693    PetscGaussLobattoLegendreElementLaplacianDestroy - frees the Laplacian for a single 1d GLL element
2694 
2695    Not Collective
2696 
2697    Input Parameters:
2698 +  n - the number of GLL nodes
2699 .  nodes - the GLL nodes
2700 .  weights - the GLL weightss
2701 -  A - the stiffness element
2702 
2703    Level: beginner
2704 
2705 .seealso: `PetscDTGaussLobattoLegendreQuadrature()`, `PetscGaussLobattoLegendreElementLaplacianCreate()`
2706 
2707 @*/
2708 PetscErrorCode PetscGaussLobattoLegendreElementLaplacianDestroy(PetscInt n, PetscReal *nodes, PetscReal *weights, PetscReal ***AA) {
2709   PetscFunctionBegin;
2710   PetscCall(PetscFree((*AA)[0]));
2711   PetscCall(PetscFree(*AA));
2712   *AA = NULL;
2713   PetscFunctionReturn(0);
2714 }
2715 
2716 /*@C
2717    PetscGaussLobattoLegendreElementGradientCreate - computes the gradient for a single 1d GLL element
2718 
2719    Not Collective
2720 
2721    Input Parameter:
2722 +  n - the number of GLL nodes
2723 .  nodes - the GLL nodes
2724 .  weights - the GLL weights
2725 
2726    Output Parameters:
2727 .  AA - the stiffness element
2728 -  AAT - the transpose of AA (pass in NULL if you do not need this array)
2729 
2730    Level: beginner
2731 
2732    Notes:
2733     Destroy this with PetscGaussLobattoLegendreElementGradientDestroy()
2734 
2735    You can access entries in these arrays with AA[i][j] but in memory it is stored in contiguous memory, row oriented
2736 
2737 .seealso: `PetscDTGaussLobattoLegendreQuadrature()`, `PetscGaussLobattoLegendreElementLaplacianDestroy()`
2738 
2739 @*/
2740 PetscErrorCode PetscGaussLobattoLegendreElementGradientCreate(PetscInt n, PetscReal *nodes, PetscReal *weights, PetscReal ***AA, PetscReal ***AAT) {
2741   PetscReal      **A, **AT = NULL;
2742   const PetscReal *gllnodes = nodes;
2743   const PetscInt   p        = n - 1;
2744   PetscReal        Li, Lj, d0;
2745   PetscInt         i, j;
2746 
2747   PetscFunctionBegin;
2748   PetscCall(PetscMalloc1(n, &A));
2749   PetscCall(PetscMalloc1(n * n, &A[0]));
2750   for (i = 1; i < n; i++) A[i] = A[i - 1] + n;
2751 
2752   if (AAT) {
2753     PetscCall(PetscMalloc1(n, &AT));
2754     PetscCall(PetscMalloc1(n * n, &AT[0]));
2755     for (i = 1; i < n; i++) AT[i] = AT[i - 1] + n;
2756   }
2757 
2758   if (n == 1) { A[0][0] = 0.; }
2759   d0 = (PetscReal)p * ((PetscReal)p + 1.) / 4.;
2760   for (i = 0; i < n; i++) {
2761     for (j = 0; j < n; j++) {
2762       A[i][j] = 0.;
2763       PetscCall(PetscDTComputeJacobi(0., 0., p, gllnodes[i], &Li));
2764       PetscCall(PetscDTComputeJacobi(0., 0., p, gllnodes[j], &Lj));
2765       if (i != j) A[i][j] = Li / (Lj * (gllnodes[i] - gllnodes[j]));
2766       if ((j == i) && (i == 0)) A[i][j] = -d0;
2767       if (j == i && i == p) A[i][j] = d0;
2768       if (AT) AT[j][i] = A[i][j];
2769     }
2770   }
2771   if (AAT) *AAT = AT;
2772   *AA = A;
2773   PetscFunctionReturn(0);
2774 }
2775 
2776 /*@C
2777    PetscGaussLobattoLegendreElementGradientDestroy - frees the gradient for a single 1d GLL element obtained with PetscGaussLobattoLegendreElementGradientCreate()
2778 
2779    Not Collective
2780 
2781    Input Parameters:
2782 +  n - the number of GLL nodes
2783 .  nodes - the GLL nodes
2784 .  weights - the GLL weights
2785 .  AA - the stiffness element
2786 -  AAT - the transpose of the element
2787 
2788    Level: beginner
2789 
2790 .seealso: `PetscDTGaussLobattoLegendreQuadrature()`, `PetscGaussLobattoLegendreElementLaplacianCreate()`, `PetscGaussLobattoLegendreElementAdvectionCreate()`
2791 
2792 @*/
2793 PetscErrorCode PetscGaussLobattoLegendreElementGradientDestroy(PetscInt n, PetscReal *nodes, PetscReal *weights, PetscReal ***AA, PetscReal ***AAT) {
2794   PetscFunctionBegin;
2795   PetscCall(PetscFree((*AA)[0]));
2796   PetscCall(PetscFree(*AA));
2797   *AA = NULL;
2798   if (*AAT) {
2799     PetscCall(PetscFree((*AAT)[0]));
2800     PetscCall(PetscFree(*AAT));
2801     *AAT = NULL;
2802   }
2803   PetscFunctionReturn(0);
2804 }
2805 
2806 /*@C
2807    PetscGaussLobattoLegendreElementAdvectionCreate - computes the advection operator for a single 1d GLL element
2808 
2809    Not Collective
2810 
2811    Input Parameters:
2812 +  n - the number of GLL nodes
2813 .  nodes - the GLL nodes
2814 -  weights - the GLL weightss
2815 
2816    Output Parameter:
2817 .  AA - the stiffness element
2818 
2819    Level: beginner
2820 
2821    Notes:
2822     Destroy this with PetscGaussLobattoLegendreElementAdvectionDestroy()
2823 
2824    This is the same as the Gradient operator multiplied by the diagonal mass matrix
2825 
2826    You can access entries in this array with AA[i][j] but in memory it is stored in contiguous memory, row oriented
2827 
2828 .seealso: `PetscDTGaussLobattoLegendreQuadrature()`, `PetscGaussLobattoLegendreElementLaplacianCreate()`, `PetscGaussLobattoLegendreElementAdvectionDestroy()`
2829 
2830 @*/
2831 PetscErrorCode PetscGaussLobattoLegendreElementAdvectionCreate(PetscInt n, PetscReal *nodes, PetscReal *weights, PetscReal ***AA) {
2832   PetscReal      **D;
2833   const PetscReal *gllweights = weights;
2834   const PetscInt   glln       = n;
2835   PetscInt         i, j;
2836 
2837   PetscFunctionBegin;
2838   PetscCall(PetscGaussLobattoLegendreElementGradientCreate(n, nodes, weights, &D, NULL));
2839   for (i = 0; i < glln; i++) {
2840     for (j = 0; j < glln; j++) { D[i][j] = gllweights[i] * D[i][j]; }
2841   }
2842   *AA = D;
2843   PetscFunctionReturn(0);
2844 }
2845 
2846 /*@C
2847    PetscGaussLobattoLegendreElementAdvectionDestroy - frees the advection stiffness for a single 1d GLL element
2848 
2849    Not Collective
2850 
2851    Input Parameters:
2852 +  n - the number of GLL nodes
2853 .  nodes - the GLL nodes
2854 .  weights - the GLL weights
2855 -  A - advection
2856 
2857    Level: beginner
2858 
2859 .seealso: `PetscDTGaussLobattoLegendreQuadrature()`, `PetscGaussLobattoLegendreElementAdvectionCreate()`
2860 
2861 @*/
2862 PetscErrorCode PetscGaussLobattoLegendreElementAdvectionDestroy(PetscInt n, PetscReal *nodes, PetscReal *weights, PetscReal ***AA) {
2863   PetscFunctionBegin;
2864   PetscCall(PetscFree((*AA)[0]));
2865   PetscCall(PetscFree(*AA));
2866   *AA = NULL;
2867   PetscFunctionReturn(0);
2868 }
2869 
2870 PetscErrorCode PetscGaussLobattoLegendreElementMassCreate(PetscInt n, PetscReal *nodes, PetscReal *weights, PetscReal ***AA) {
2871   PetscReal      **A;
2872   const PetscReal *gllweights = weights;
2873   const PetscInt   glln       = n;
2874   PetscInt         i, j;
2875 
2876   PetscFunctionBegin;
2877   PetscCall(PetscMalloc1(glln, &A));
2878   PetscCall(PetscMalloc1(glln * glln, &A[0]));
2879   for (i = 1; i < glln; i++) A[i] = A[i - 1] + glln;
2880   if (glln == 1) { A[0][0] = 0.; }
2881   for (i = 0; i < glln; i++) {
2882     for (j = 0; j < glln; j++) {
2883       A[i][j] = 0.;
2884       if (j == i) A[i][j] = gllweights[i];
2885     }
2886   }
2887   *AA = A;
2888   PetscFunctionReturn(0);
2889 }
2890 
2891 PetscErrorCode PetscGaussLobattoLegendreElementMassDestroy(PetscInt n, PetscReal *nodes, PetscReal *weights, PetscReal ***AA) {
2892   PetscFunctionBegin;
2893   PetscCall(PetscFree((*AA)[0]));
2894   PetscCall(PetscFree(*AA));
2895   *AA = NULL;
2896   PetscFunctionReturn(0);
2897 }
2898 
2899 /*@
2900   PetscDTIndexToBary - convert an index into a barycentric coordinate.
2901 
2902   Input Parameters:
2903 + len - the desired length of the barycentric tuple (usually 1 more than the dimension it represents, so a barycentric coordinate in a triangle has length 3)
2904 . sum - the value that the sum of the barycentric coordinates (which will be non-negative integers) should sum to
2905 - index - the index to convert: should be >= 0 and < Binomial(len - 1 + sum, sum)
2906 
2907   Output Parameter:
2908 . coord - will be filled with the barycentric coordinate
2909 
2910   Level: beginner
2911 
2912   Note: the indices map to barycentric coordinates in lexicographic order, where the first index is the
2913   least significant and the last index is the most significant.
2914 
2915 .seealso: `PetscDTBaryToIndex()`
2916 @*/
2917 PetscErrorCode PetscDTIndexToBary(PetscInt len, PetscInt sum, PetscInt index, PetscInt coord[]) {
2918   PetscInt c, d, s, total, subtotal, nexttotal;
2919 
2920   PetscFunctionBeginHot;
2921   PetscCheck(len >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "length must be non-negative");
2922   PetscCheck(index >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "index must be non-negative");
2923   if (!len) {
2924     if (!sum && !index) PetscFunctionReturn(0);
2925     SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Invalid index or sum for length 0 barycentric coordinate");
2926   }
2927   for (c = 1, total = 1; c <= len; c++) {
2928     /* total is the number of ways to have a tuple of length c with sum */
2929     if (index < total) break;
2930     total = (total * (sum + c)) / c;
2931   }
2932   PetscCheck(c <= len, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "index out of range");
2933   for (d = c; d < len; d++) coord[d] = 0;
2934   for (s = 0, subtotal = 1, nexttotal = 1; c > 0;) {
2935     /* subtotal is the number of ways to have a tuple of length c with sum s */
2936     /* nexttotal is the number of ways to have a tuple of length c-1 with sum s */
2937     if ((index + subtotal) >= total) {
2938       coord[--c] = sum - s;
2939       index -= (total - subtotal);
2940       sum       = s;
2941       total     = nexttotal;
2942       subtotal  = 1;
2943       nexttotal = 1;
2944       s         = 0;
2945     } else {
2946       subtotal  = (subtotal * (c + s)) / (s + 1);
2947       nexttotal = (nexttotal * (c - 1 + s)) / (s + 1);
2948       s++;
2949     }
2950   }
2951   PetscFunctionReturn(0);
2952 }
2953 
2954 /*@
2955   PetscDTBaryToIndex - convert a barycentric coordinate to an index
2956 
2957   Input Parameters:
2958 + len - the desired length of the barycentric tuple (usually 1 more than the dimension it represents, so a barycentric coordinate in a triangle has length 3)
2959 . sum - the value that the sum of the barycentric coordinates (which will be non-negative integers) should sum to
2960 - coord - a barycentric coordinate with the given length and sum
2961 
2962   Output Parameter:
2963 . index - the unique index for the coordinate, >= 0 and < Binomial(len - 1 + sum, sum)
2964 
2965   Level: beginner
2966 
2967   Note: the indices map to barycentric coordinates in lexicographic order, where the first index is the
2968   least significant and the last index is the most significant.
2969 
2970 .seealso: `PetscDTIndexToBary`
2971 @*/
2972 PetscErrorCode PetscDTBaryToIndex(PetscInt len, PetscInt sum, const PetscInt coord[], PetscInt *index) {
2973   PetscInt c;
2974   PetscInt i;
2975   PetscInt total;
2976 
2977   PetscFunctionBeginHot;
2978   PetscCheck(len >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "length must be non-negative");
2979   if (!len) {
2980     if (!sum) {
2981       *index = 0;
2982       PetscFunctionReturn(0);
2983     }
2984     SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Invalid index or sum for length 0 barycentric coordinate");
2985   }
2986   for (c = 1, total = 1; c < len; c++) total = (total * (sum + c)) / c;
2987   i = total - 1;
2988   c = len - 1;
2989   sum -= coord[c];
2990   while (sum > 0) {
2991     PetscInt subtotal;
2992     PetscInt s;
2993 
2994     for (s = 1, subtotal = 1; s < sum; s++) subtotal = (subtotal * (c + s)) / s;
2995     i -= subtotal;
2996     sum -= coord[--c];
2997   }
2998   *index = i;
2999   PetscFunctionReturn(0);
3000 }
3001