static const char help[] =
"Build and view a tiny three-triangle mesh using DMPlex, and integrate a\n"
"scalar function over it.  Option prefixes tny_ and plex_view_.\n\n";

/* either build DMPlex via call to DMPlexCreateFromCellList()
    ./tiny
or by directly setting cones "by hand":
    ./tiny -tny_by_hand

compute an integral by symmetric Gauss quadrature:
    ./tiny -tny_integrate_f
    ./tiny -tny_integrate_f -tny_quaddegree 1|[2]|3

compare these views:
    ./tiny -dm_view
    ./tiny -section_view
    ./tiny -v_vec_view

and -plex_view_xxx options (see plexview.h)

parallel refinement works:
    mpiexec -n 2 ./tiny -dm_refine 1 -plex_view_ranges -plex_view_coords

FIXME interpolation of f(x,y) by P2

FIXME interpolate f(x,y) by P2 and integrate it

FIXME add option -tny_element P1|P2|P3
*/

#include <petsc.h>
#include "../../quadrature.h"
#include "plexview.h"

// Describe the mesh "triangle style" with separate numbering for cells and vertices.
static const int    dim = 2,
                    ncell = 3,
                    nvert = 5,
                    cells[9] = {0, 3, 2,  // 9 = ncell * (dim+1)
                                0, 2, 1,
                                2, 3, 4};
static const double coordverts[10] = {0.0, 0.0,  // 10 = nvert * dim
                                      0.0, 1.0,
                                      0.5, 1.0,
                                      1.0, 0.0,
                                      1.0, 1.0};

// Describe same mesh, but directly as DMPlex, i.e. by giving cell and
// edge cones in DAG.  These values are generated by DMPlexCreateFromCellList()
// internally, so they are redundant if we use that create method.
static const int npoint = 15,
                 ccone[3][3] = {{8,9,10},
                                {10,11,12},
                                {9,13,14}},
                 econe[7][2] = {{3,6},
                                {5,6},
                                {3,5},
                                {4,5},
                                {3,4},
                                {6,7},
                                {5,7}};

static double f(double x, double y) {
    return exp(- x - 2 * y);
}

extern PetscErrorCode CreateMeshByHand(DM*);
extern PetscErrorCode CreateCoordinateSectionByHand(DM*);
extern PetscErrorCode CreateSectionP2(DM,PetscSection*);
extern PetscErrorCode IntegrateF(DM,double(double,double),int,double*);
extern PetscErrorCode EvaluateFP2(DM,PetscSection,double(double,double),Vec*);

int main(int argc,char **argv) {
    PetscErrorCode ierr;
    DM            dmplex;
    PetscSection  p2section;
    Vec           v;
    PetscBool     by_hand = PETSC_FALSE,
                  integrate_f = PETSC_FALSE;
    int           quaddegree = 2;

    PetscInitialize(&argc,&argv,NULL,help);
    ierr = PetscOptionsBegin(PETSC_COMM_WORLD, "tny_", "options for tiny", "");CHKERRQ(ierr);
    ierr = PetscOptionsBool("-by_hand",
        "use by-hand construction",
        "tiny.c", by_hand, &by_hand, NULL);CHKERRQ(ierr);
    ierr = PetscOptionsBool("-integrate_f",
        "integrate f(x,y) by summing over cells and using quadrature",
        "tiny.c", integrate_f, &integrate_f, NULL);CHKERRQ(ierr);
    ierr = PetscOptionsInt("-quaddegree",
        "use this quadrature degree for numerical integrations",
        "tiny.c", quaddegree, &quaddegree, NULL);CHKERRQ(ierr);
    ierr = PetscOptionsEnd();

    // create the DMPlex mesh
    if (by_hand) {
        ierr = CreateMeshByHand(&dmplex); CHKERRQ(ierr);
        ierr = CreateCoordinateSectionByHand(&dmplex); CHKERRQ(ierr);
    } else {
        PetscMPIInt rank;
        ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
        if (rank == 0) { // create mesh on rank 0
            ierr = DMPlexCreateFromCellList(PETSC_COMM_WORLD,
                dim,ncell,nvert,dim+1,
                PETSC_TRUE,  // "interpolate" flag; TRUE means "topologically-interpolate"
                             // i.e. create edges (1D) from vertices (0D) and cells (2D)
                cells,dim,coordverts,
                &dmplex); CHKERRQ(ierr);
        } else { // empty mesh on rank > 0
            ierr = DMPlexCreateFromCellList(PETSC_COMM_WORLD,
                dim,0,0,dim+1,PETSC_TRUE,NULL,dim,NULL,&dmplex); CHKERRQ(ierr);
        }
    }

    // distribute mesh over processes using default partitioner
    {
        DM  distributedMesh = NULL;
        // overlap of 0 is appropriate to P2 etc. FEM:
        ierr = DMPlexDistribute(dmplex, 0, NULL, &distributedMesh);CHKERRQ(ierr);
        if (distributedMesh) {
          ierr = DMDestroy(&dmplex);CHKERRQ(ierr);
          dmplex  = distributedMesh;
        }
    }

    // reset names before viewing
    ierr = PetscObjectSetName((PetscObject)dmplex, "tiny mesh"); CHKERRQ(ierr);
    {
        DM           cdm;
        PetscSection csection;
        ierr = DMGetCoordinateDM(dmplex, &cdm); CHKERRQ(ierr);
        ierr = DMGetDefaultSection(cdm, &csection); CHKERRQ(ierr);
        ierr = PetscObjectSetName((PetscObject)csection, "vertex coordinate section"); CHKERRQ(ierr);
    }
    ierr = DMSetFromOptions(dmplex); CHKERRQ(ierr);
    ierr = DMViewFromOptions(dmplex, NULL, "-dm_view"); CHKERRQ(ierr);
    ierr = PlexViewFromOptions(dmplex); CHKERRQ(ierr);

    // integrate f(x,y) by summing over cells and using quadrature
    if (integrate_f) {
        double       integral;
        const double intfexact = (1.0 - exp(-1.0)) * 0.5 * (1.0 - exp (-2.0));
        ierr = IntegrateF(dmplex,f,quaddegree,&integral); CHKERRQ(ierr);
        ierr = PetscPrintf(PETSC_COMM_WORLD,
            "integral of f(x,y) is %.12f with error %.2e\n",
            integral,fabs(integral - intfexact)); CHKERRQ(ierr);
    }

    // create nodes (degrees of freedom) for P2 elements using PetscSection
    ierr = CreateSectionP2(dmplex,&p2section); CHKERRQ(ierr);
    ierr = DMSetDefaultSection(dmplex, p2section); CHKERRQ(ierr);
    ierr = PetscObjectViewFromOptions((PetscObject)p2section,NULL,"-tny_view_section"); CHKERRQ(ierr);

    // put function f(x,y) into v by local calculations using coordinates and p2section
    ierr = DMCreateGlobalVector(dmplex, &v); CHKERRQ(ierr);
    ierr = PetscObjectSetName((PetscObject)v, "v"); CHKERRQ(ierr);
    ierr = EvaluateFP2(dmplex,p2section,f,&v); CHKERRQ(ierr);
    ierr = PetscObjectViewFromOptions((PetscObject)v,NULL,"-v_vec_view"); CHKERRQ(ierr);

    VecDestroy(&v);  PetscSectionDestroy(&p2section);  DMDestroy(&dmplex);
    return PetscFinalize();
}

/* This function is essentially equivalent to using DMPlexCreateFromCellList().
Note that rank 0 gets the actual mesh and other ranks get an empty mesh.
See the implementations of
    DMPlexBuildFromCellList_Private()
    DMPlexCreateFromCellListParallel()
    DMPlexInterpolate()
    DMPlexBuildCoordinates_Private()      */
PetscErrorCode CreateMeshByHand(DM *dmplex) {
    PetscErrorCode ierr;
    int           j;
    PetscMPIInt   rank;
    ierr = MPI_Comm_rank(PETSC_COMM_WORLD,&rank); CHKERRQ(ierr);
    ierr = DMPlexCreate(PETSC_COMM_WORLD,dmplex); CHKERRQ(ierr);
    ierr = DMSetDimension(*dmplex,dim); CHKERRQ(ierr);
    if (rank == 0) {
        // set the total number of points (npoint = ncell + nvert + nedges)
        ierr = DMPlexSetChart(*dmplex, 0, npoint); CHKERRQ(ierr);
        // the points are cells, vertices, edges in that order
        // we only set cones for cells and edges
        for (j = 0; j < ncell; j++) {
            ierr = DMPlexSetConeSize(*dmplex, j, dim+1); CHKERRQ(ierr);
        }
        for (j = ncell + nvert; j < npoint; j++) {
            ierr = DMPlexSetConeSize(*dmplex, j, dim); CHKERRQ(ierr);
        }
        ierr = DMSetUp(*dmplex);
        for (j = 0; j < ncell; j++) {
            ierr = DMPlexSetCone(*dmplex, j, ccone[j]); CHKERRQ(ierr);
        }
        for (j = ncell + nvert; j < npoint; j++) {
            ierr = DMPlexSetCone(*dmplex, j, econe[j-ncell-nvert]); CHKERRQ(ierr);
        }
    } else {
        ierr = DMPlexSetChart(*dmplex, 0, 0); CHKERRQ(ierr);
    }
    // with cones we have only upward directions and no labels for the strata
    // (note: both Symmetrize & Stratify are required, and they must be in this order
    ierr = DMPlexSymmetrize(*dmplex); CHKERRQ(ierr);
    ierr = DMPlexStratify(*dmplex); CHKERRQ(ierr);
    return 0;
}

// Set up a PetscSection which holds vertex coordinates.
PetscErrorCode CreateCoordinateSectionByHand(DM *dmplex) {
    PetscErrorCode ierr;
    PetscSection  coordSection;
    DM            cdm;
    Vec           coordinates;
    double        *acoord;
    int           j, d, dim, vertexstart, vertexend;
    // you have to setup the PetscSection returned by DMGetCoordinateSection() first,
    // or else the Vec returned by DMCreateLocalVector() has zero size
    // (and thus seg faults)
    ierr = DMGetDimension(*dmplex, &dim); CHKERRQ(ierr);
    ierr = DMGetCoordinateSection(*dmplex, &coordSection); CHKERRQ(ierr);
    ierr = DMPlexGetDepthStratum(*dmplex, 0, &vertexstart, &vertexend); CHKERRQ(ierr);
    ierr = PetscSectionSetNumFields(coordSection, 1); CHKERRQ(ierr);
    ierr = PetscSectionSetFieldComponents(coordSection, 0, dim); CHKERRQ(ierr);
    ierr = PetscSectionSetChart(coordSection, vertexstart, vertexend); CHKERRQ(ierr);
    for (j = vertexstart; j < vertexend; j++) {
        ierr = PetscSectionSetDof(coordSection, j, dim); CHKERRQ(ierr);
        ierr = PetscSectionSetFieldDof(coordSection, j, 0, dim); CHKERRQ(ierr);
    }
    ierr = PetscSectionSetUp(coordSection); CHKERRQ(ierr);
    // now we can actually set up the coordinate Vec
    ierr = DMGetCoordinateDM(*dmplex, &cdm); CHKERRQ(ierr);
    ierr = DMCreateLocalVector(cdm, &coordinates); CHKERRQ(ierr);
    ierr = VecSetBlockSize(coordinates,dim); CHKERRQ(ierr);
    ierr = PetscObjectSetName((PetscObject) coordinates, "coordinates"); CHKERRQ(ierr);
    ierr = VecGetArray(coordinates, &acoord); CHKERRQ(ierr);
    for (j = 0; j < vertexend-vertexstart; j++) {
        for (d = 0; d < dim; ++d) {
            acoord[j*dim+d] = coordverts[j*dim+d];
        }
    }
    ierr = VecRestoreArray(coordinates, &acoord); CHKERRQ(ierr);
    // finally we tell the DM that it has coordinates
    ierr = DMSetCoordinatesLocal(*dmplex, coordinates); CHKERRQ(ierr);
    VecDestroy(&coordinates);
    return 0;
}

// use quadrature to integrate a function by summing over cells
PetscErrorCode IntegrateF(DM dmplex, double f(double x,double y),
                          int quaddegree, double *fint) {
    PetscErrorCode ierr;
    DM              cdm;
    Vec             coords;
    const Quad2DTri q = symmgauss[quaddegree-1];
    const double    *acoords;
    double          x[3], y[3], xr, yr, dx1, dx2, dy1, dy2, absdetJ,
                    csum, fintloc;
    int             numpts, *pts = NULL, voff, j, p, l, r,
                    vertstart, vertend, cellstart, cellend;
    MPI_Comm        comm;

    ierr = DMGetCoordinateDM(dmplex, &cdm); CHKERRQ(ierr);
    ierr = DMGetCoordinatesLocal(dmplex, &coords); CHKERRQ(ierr);
    ierr = DMPlexGetHeightStratum(dmplex, 0, &cellstart, &cellend); CHKERRQ(ierr);
    ierr = DMPlexGetDepthStratum(dmplex, 0, &vertstart, &vertend); CHKERRQ(ierr);
    ierr = VecGetArrayRead(coords, &acoords); CHKERRQ(ierr);
    // integral by sum over cells
    fintloc = 0.0;
    for (j = cellstart; j < cellend; j++) {
        ierr = DMPlexGetTransitiveClosure(dmplex, j, PETSC_TRUE, &numpts, &pts);
        if (numpts != 7) {
            SETERRQ(PETSC_COMM_WORLD,1,"wrong: assume closure of triangle has 7 points\n");
        }
        // record vertex coordinates
        for (l = 0; l < 3; l++) { // loop through vertex points
            p = 8 + 2 * l; // p=0,1 are cell info; p=2,...,7 are edge info; omit orientations
            if ((pts[p] < vertstart) || (pts[p] >= vertend))  {
                SETERRQ(PETSC_COMM_WORLD,2,"wrong: pts[p] should be a vertex\n");
            }
            voff = pts[p] - vertstart;
            x[l] = acoords[2*voff+0];
            y[l] = acoords[2*voff+1];
        }
        // geometry of element (cell)
        dx1 = x[1] - x[0];
        dx2 = x[2] - x[0];
        dy1 = y[1] - y[0];
        dy2 = y[2] - y[0];
        absdetJ = fabs(dx1 * dy2 - dx2 * dy1);
        // sum over quadrature points on cell
        csum = 0.0;
        for (r = 0; r < q.n; r++) {
            xr = x[0] + dx1 * q.xi[r] + dx2 * q.eta[r];
            yr = y[0] + dy1 * q.xi[r] + dy2 * q.eta[r];
            csum += q.w[r] * f(xr,yr);
        }
        // add cell contribution
        fintloc += absdetJ * csum;
        ierr = DMPlexRestoreTransitiveClosure(dmplex, j, PETSC_TRUE, &numpts, &pts); CHKERRQ(ierr);
    }
    ierr = VecRestoreArrayRead(coords, &acoords); CHKERRQ(ierr);

    ierr = PetscObjectGetComm((PetscObject)dmplex,&comm); CHKERRQ(ierr);
    ierr = MPI_Allreduce(&fintloc,fint,1,MPI_DOUBLE,MPI_SUM,comm); CHKERRQ(ierr);
    return 0;
}

PetscErrorCode CreateSectionP2(DM dmplex, PetscSection *section) {
    PetscErrorCode ierr;
    int  j, pstart, pend, vertexstart, edgeend;
    ierr = PetscSectionCreate(PETSC_COMM_WORLD,section); CHKERRQ(ierr);
    ierr = PetscObjectSetName((PetscObject)*section, "P2 scalar section"); CHKERRQ(ierr);
    ierr = PetscSectionSetNumFields(*section, 1); CHKERRQ(ierr);
    ierr = DMPlexGetChart(dmplex, &pstart, &pend); CHKERRQ(ierr);
    ierr = PetscSectionSetChart(*section, pstart, pend); CHKERRQ(ierr);
    ierr = DMPlexGetDepthStratum(dmplex, 0, &vertexstart, NULL); CHKERRQ(ierr);
    ierr = DMPlexGetDepthStratum(dmplex, 1, NULL, &edgeend); CHKERRQ(ierr);
    for (j = pstart; j < pend; ++j) {
        if (j < vertexstart) {
            ierr = PetscSectionSetDof(*section, j, 0); CHKERRQ(ierr);
            ierr = PetscSectionSetFieldDof(*section, j, 0, 0); CHKERRQ(ierr);
        } else {
            ierr = PetscSectionSetDof(*section, j, 1); CHKERRQ(ierr);
            ierr = PetscSectionSetFieldDof(*section, j, 0, 1); CHKERRQ(ierr);
        }
    }
    ierr = PetscSectionSetUp(*section); CHKERRQ(ierr);
    return 0;
}

PetscErrorCode EvaluateFP2(DM dmplex, PetscSection section,
                                double f(double x,double y), Vec *v) {
    PetscErrorCode ierr;
    DM           cdm;
    Vec          vloc, coords;
    double       *avloc, x, y;
    const double *acoords;
    int          numpts, *pts = NULL, dof, off, j, p,
                 vertstart, vertend, edgestart, edgeend, cellstart, cellend;

    // we put values in a local vector, thus redundantly on overlap
    ierr = DMGetLocalVector(dmplex, &vloc); CHKERRQ(ierr);
    ierr = VecGetArray(vloc, &avloc); CHKERRQ(ierr);
    // need coordinates of P2 nodes (i.e. both vertices and edges)
    ierr = DMGetCoordinateDM(dmplex, &cdm); CHKERRQ(ierr);
    ierr = DMGetCoordinatesLocal(dmplex,&coords); CHKERRQ(ierr);
    ierr = DMPlexGetHeightStratum(dmplex, 0, &cellstart, &cellend); CHKERRQ(ierr);
    ierr = DMPlexGetDepthStratum(dmplex, 0, &vertstart, &vertend); CHKERRQ(ierr);
    ierr = DMPlexGetDepthStratum(dmplex, 1, &edgestart, &edgeend); CHKERRQ(ierr);
    ierr = VecGetArrayRead(coords, &acoords); CHKERRQ(ierr);
    for (j = cellstart; j < cellend; j++) {
        ierr = DMPlexGetTransitiveClosure(dmplex, j, PETSC_TRUE, &numpts, &pts);
        for (p = 0; p < numpts*2; p += 2) {   // omit orientations
            PetscSectionGetDof(section, pts[p], &dof);
            if (dof > 0) {
                // compute (x,y) for vertex or edge center from coords
                if (pts[p] < vertstart) {
                    SETERRQ(PETSC_COMM_WORLD,1,"cell center computation not implemented\n");
                } else if (pts[p] < edgestart) {
                    int voff;
                    voff = pts[p] - vertstart;
                    x = acoords[2*voff+0];
                    y = acoords[2*voff+1];
                } else { // pts[p] is an edge ...
                    const int *vpts;
                    int       voff[2];
                    ierr = DMPlexGetCone(dmplex, pts[p], &vpts); CHKERRQ(ierr);
                    voff[0] = vpts[0] - vertstart;
                    voff[1] = vpts[1] - vertstart;
                    x = 0.5 * (acoords[2*voff[0]+0] + acoords[2*voff[1]+0]);
                    y = 0.5 * (acoords[2*voff[0]+1] + acoords[2*voff[1]+1]);
                }
                // get index "off" into Vec vloc based on P2 scalar section
                PetscSectionGetOffset(section, pts[p], &off);
                avloc[off] = f(x,y);
            }
        }
        ierr = DMPlexRestoreTransitiveClosure(dmplex, j, PETSC_TRUE, &numpts, &pts); CHKERRQ(ierr);
    }
    ierr = VecRestoreArray(vloc, &avloc); CHKERRQ(ierr);
    ierr = VecRestoreArrayRead(coords, &acoords); CHKERRQ(ierr);

    // now we want v global, i.e. only values not duplicate ghosts
    ierr = DMLocalToGlobalBegin(dmplex,vloc,INSERT_VALUES,*v); CHKERRQ(ierr);
    ierr = DMLocalToGlobalEnd(dmplex,vloc,INSERT_VALUES,*v); CHKERRQ(ierr);
    ierr = DMRestoreLocalVector(dmplex, &vloc); CHKERRQ(ierr);
    return 0;
}

// FIXME InterpolateFP2()


#include <petsc.h>
#include <petsc/private/petscimpl.h>

#if PETSC_VERSION_LT(3,17,0)
#undef  SETERRQ
#define SETERRQ(comm,ierr,...) return PetscError(comm,__LINE__,PETSC_FUNCTION_NAME,__FILE__,ierr,PETSC_ERROR_INITIAL,__VA_ARGS__)
#endif

PETSC_EXTERN PetscErrorCode IGA_Partition(PetscInt,PetscInt,PetscInt,const PetscInt[],PetscInt[],PetscInt[]);
PETSC_EXTERN PetscErrorCode IGA_Distribute(PetscInt,const PetscInt[],const PetscInt[],const PetscInt[],PetscInt[],PetscInt[]);

static inline
PetscInt IGA_CUT2D(PetscInt M,PetscInt N,
                   PetscInt m,PetscInt n)
{return M*(n-1) + N*(m-1);}

static inline
PetscInt IGA_CUT3D(PetscInt M,PetscInt N,PetscInt P,
                   PetscInt m,PetscInt n,PetscInt p)
{return (N*P*(m-1) + M*P*(n-1) + M*N*(p-1));}

static inline
PetscInt IGA_PART2D_INNER(PetscInt size,
                          PetscInt   M,PetscInt   N,
                          PetscInt *_m,PetscInt *_n)
{
  PetscInt m,n;
  m = (PetscInt)(0.5 + sqrt(((double)M)/((double)N)*((double)size)));
  if (m == 0) {m = 1;} while (m > 0 && size % m) m--;
  n = size / m;
  *_m = m; *_n = n;
  return IGA_CUT2D(M,N,m,n);
}

static inline
void IGA_PART2D(PetscInt size,
                PetscInt   M,PetscInt   N,
                PetscInt *_m,PetscInt *_n)
{
  PetscInt m,n;
  PetscInt m1,n1,a;
  PetscInt m2,n2,b;
  a = IGA_PART2D_INNER(size,M,N,&m1,&n1);
  b = IGA_PART2D_INNER(size,N,M,&n2,&m2);
  if (a<b) {m = m1; n = n1;}
  else     {m = m2; n = n2;}
  if (M == N && n < m) {PetscInt t = m; m = n; n = t;}
  *_m = m; *_n = n;
}

static inline
PetscInt IGA_PART3D_INNER(PetscInt size,
                          PetscInt   M,PetscInt   N,PetscInt   P,
                          PetscInt *_m,PetscInt *_n,PetscInt *_p)
{
  PetscInt m,n,p,C;
  PetscInt mm,nn,pp,CC;
  /**/
  m = (PetscInt)(0.5 + pow(((double)M*(double)M)/((double)N*(double)P)*(double)size,1./3.));
  if (m == 0) {m = 1;} while (m > 0 && size % m) m--;
  /**/
  IGA_PART2D(size/m,N,P,&n,&p);
  C = IGA_CUT3D(M,N,P,m,n,p);
  /**/
  for (mm=m; mm>=1; mm--) {
    if (size % mm) continue;
    IGA_PART2D(size/mm,N,P,&nn,&pp);
    CC = IGA_CUT3D(M,N,P,mm,nn,pp);
    if (CC < C) {m = mm; n = nn; p = pp; C = CC;}
  }
  /**/
  for (nn=n; nn>=1; nn--) {
    if (size % nn) continue;
    IGA_PART2D(size/nn,M,P,&mm,&pp);
    CC = IGA_CUT3D(M,N,P,mm,nn,pp);
    if (CC < C) {m = mm; n = nn; p = pp; C = CC;}
  }
  /**/
  for (pp=p; pp>=1; pp--) {
    if (size % pp) continue;
    IGA_PART2D(size/pp,M,N,&mm,&nn);
    CC = IGA_CUT3D(M,N,P,mm,nn,pp);
    if (CC < C) {m = mm; n = nn; p = pp; C = CC;}
  }
  /**/
  *_m = m; *_n = n; *_p = p;
  return IGA_CUT3D(M,N,P,m,n,p);
}

static inline
void IGA_PART3D(PetscInt size,
                PetscInt   M,PetscInt   N,PetscInt   P,
                PetscInt *_m,PetscInt *_n,PetscInt *_p)
{
  PetscInt m[3],n[3],p[3],C[3],k,i=0,Cmin=PETSC_MAX_INT,t;
  C[0] = IGA_PART3D_INNER(size,M,N,P,&m[0],&n[0],&p[0]);
  C[1] = IGA_PART3D_INNER(size,N,M,P,&n[1],&m[1],&p[1]);
  C[2] = IGA_PART3D_INNER(size,P,M,N,&p[2],&m[2],&n[2]);
  for (k=0; k<3; k++) if (C[k]<Cmin) {Cmin=C[k]; i=k;}
  if (M == N && n[i] < m[i]) {t = m[i]; m[i] = n[i]; n[i] = t;}
  if (M == P && p[i] < m[i]) {t = m[i]; m[i] = p[i]; p[i] = t;}
  if (N == P && p[i] < n[i]) {t = n[i]; n[i] = p[i]; p[i] = t;}
  *_m = m[i]; *_n = n[i]; *_p = p[i];
}

static inline
void IGA_Part2D(PetscInt size,
                PetscInt   M,PetscInt   N,
                PetscInt *_m,PetscInt *_n)
{
  PetscInt m,n;
  m = *_m; n = *_n;
  if (m < 1 && n < 1) IGA_PART2D(size,M,N,&m,&n);
  else if (m < 1)     m = size / n;
  else if (n < 1)     n = size / m;
  *_m = m; *_n = n;
}

static inline
void IGA_Part3D(PetscInt size,
                PetscInt   M,PetscInt   N,PetscInt   P,
                PetscInt *_m,PetscInt *_n,PetscInt *_p)
{
  PetscInt m,n,p;
  m = *_m; n = *_n; p = *_p;
  if (m < 1 && n < 1 && p < 1) IGA_PART3D(size,M,N,P,&m,&n,&p);
  else if (m < 1 && n < 1)     IGA_PART2D(size/p,M,N,&m,&n);
  else if (m < 1 && p < 1)     IGA_PART2D(size/n,M,P,&m,&p);
  else if (n < 1 && p < 1)     IGA_PART2D(size/m,N,P,&n,&p);
  else if (m < 1)              m = size / (n*p);
  else if (n < 1)              n = size / (m*p);
  else if (p < 1)              p = size / (m*n);
  *_m = m; *_n = n; *_p = p;
}

PetscErrorCode IGA_Partition(PetscInt size,PetscInt rank,
                             PetscInt dim,const PetscInt N[],
                             PetscInt n[],PetscInt i[])
{
  PetscInt k,p=1,m[3]={1,1,1};
  PetscFunctionBegin;
  PetscValidIntPointer(N,4);
  PetscValidIntPointer(n,5);
  if (i) PetscValidIntPointer(i,6);
  if (size < 1)
    SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_OUTOFRANGE,
             "Number of partitions %D must be positive",size);
  if (i && (rank < 0 || rank >= size))
    SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_OUTOFRANGE,"Partition index %D must be in range [0,%D]",rank,size-1);

  switch (dim) {
  case 3:  IGA_Part3D(size,N[0],N[1],N[2],&n[0],&n[1],&n[2]); break;
  case 2:  IGA_Part2D(size,N[0],N[1],&n[0],&n[1]); break;
  case 1:  if (n[0] < 1) n[0] = size; break;
  default: SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_OUTOFRANGE,"Number of dimensions %D must be in range [1,3]",dim);
  }
  for (k=0; k<dim; k++) p *= (m[k] = n[k]);
  if (p != size) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_OUTOFRANGE,"Bad partition, prod(%D,%D,%D) != %D",m[0],m[1],m[2],size);
  for (k=0; k<dim; k++)
    if (N[k] < n[k]) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_OUTOFRANGE,"Partition %D is too fine, %D elements in %D parts",k,N[k],n[k]);
  if (i)
    for (k=0; k<dim; k++) {
      i[k] = rank % n[k];
      rank -= i[k];
      rank /= n[k];
    }
  PetscFunctionReturn(0);
}

static inline
void IGA_Dist1D(PetscInt size,PetscInt rank,
                PetscInt N,PetscInt *n,PetscInt *s)
{
  *n = N/size + ((N % size) > rank);
  *s = rank * (N/size) + (((N % size) > rank) ? rank : (N % size));
}

PetscErrorCode IGA_Distribute(PetscInt dim,
                              const PetscInt size[],const PetscInt rank[],
                              const PetscInt N[],PetscInt n[],PetscInt s[])
{
  PetscInt k;
  PetscFunctionBegin;
  PetscValidIntPointer(size,2);
  PetscValidIntPointer(rank,3);
  PetscValidIntPointer(N,4);
  PetscValidIntPointer(n,5);
  PetscValidIntPointer(s,6);
  if (dim < 1 || dim > 3)
    SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_OUTOFRANGE,"Number of dimensions %D must be in range [1,3]",dim);
  for (k=0; k<dim; k++) {
    if (size[k] < 1)
      SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_OUTOFRANGE,"Number of partitions %D must be positive",size[k]);
    if (rank[k] < 0 || rank[k] >= size[k])
      SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_OUTOFRANGE,"Partition index %D must be in range [0,%D]",rank[k],size[k]-1);
    if (N[k] < 0)
      SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_OUTOFRANGE,"Number of elements %D must be non-negative",N[k]);
  }
  for (k=0; k<dim; k++)
    IGA_Dist1D(size[k],rank[k],N[k],&n[k],&s[k]);
  PetscFunctionReturn(0);
}


#pragma once

#pragma once

#include "core/math/vec3.h"
#include "core/math/vec4.h"
#include "core/container/vector.h"
#include "core/memory/linear_allocator.h"
#include <lapacke.h>

/*
void compute_spline_coeff(uint n, float* coeff, float* y, uint stride, float* h, float constant_h) {
	LinearRegion region(get_temporary_allocator());

	float* DL = TEMPORARY_ARRAY(float, n);
	float* D  = TEMPORARY_ARRAY(float, n+1);
	float* DU = TEMPORARY_ARRAY(float, n);
	float* B = TEMPORARY_ARRAY(float, n + 1);

	for (uint i = 1; i < n; i++) {
		float h0 = h ? h[i - 1] : constant_h;
		float h1 = h ? h[i] : constant_h;

		float a0 = y[(i - 1) * stride];
		float a1 = y[i * stride];
		float a2 = y[(i + 1) * stride];

		DL[i-1] = h0;
		D[i] = 2*(h0 + h1);
		DU[i] = h1;
		B[i] = 3/h1 * (a2 - a1) + 3/h0 * (a1 - a0);
	}

	B[0] = 0;
	B[n] = 0;

	D[0] = 1;
	D[n] = 1;

	DU[0] = 0;
	DL[n - 1] = 1;

	int info = LAPACKE_sgtsv(LAPACK_ROW_MAJOR, n+1, 1, DL, D, DU, B, 1);
	assert(info == 0);

	for (uint i = 0; i < n; i++) {
		float h0 = h ? h[i] : constant_h;

		float a0 = y[i * stride];
		float a1 = y[(i + 1) * stride];

		float c0 = B[i];
		float c1 = B[i + 1];

		coeff[i * 4 * stride + 0] = a0;
		coeff[i * 4 * stride + 1] = 1 / h0 * (a1 - a0) - h0 / 3 * (2 * c0 + c1);
		coeff[i * 4 + stride + 2] = c0;
		coeff[i * 4 + stride + 3] = (c1 - c0) / (3*h0);
	}
}
*/

//Cubic hermitian spline
class Spline {
	struct Cubic {
		vec3 a, b, c, d;
	};

	vector<Cubic> coeff;
	vector<float> lengths;

public:
    float total_length;

	Spline(slice<vec3> points) {
		build(points);
	}

    uint points() {
        return lengths.length;
    }

	void build(slice<vec3> points) {
		uint n = points.length - 1;

		coeff.resize(n+1);
		lengths.resize(n);

		LinearRegion region(get_temporary_allocator());

		vec3* a = TEMPORARY_ARRAY(vec3, n + 1);
		for (int i = 1; i <= n - 1; i++)
			a[i] = 3 * ((points[i + 1] - 2 * points[i] + points[i - 1]));

		float* l = TEMPORARY_ARRAY(float, n+1);
		float* mu = TEMPORARY_ARRAY(float, n + 1);
		vec3* z = TEMPORARY_ARRAY(vec3, n + 1);

		l[0] = l[n] = 1;
		mu[0] = 0;
		z[0] = z[n] = vec3();
		coeff[n].c = vec3();

		for (int i = 1; i <= n - 1; i++)
		{
			l[i] = 4 - mu[i - 1];
			mu[i] = 1 / l[i];
			z[i] = (a[i] - z[i - 1]) / l[i];
		}

		for (int i = 0; i < n+1; i++)
			coeff[i].a = points[i];

		for (int j = n - 1; j >= 0; j--)
		{
			coeff[j].c = z[j] - mu[j] * coeff[j + 1].c;
			coeff[j].d = (1.0f / 3.0f) * (coeff[j + 1].c - coeff[j].c);
			coeff[j].b = points[j + 1] - points[j] - coeff[j].c - coeff[j].d;
		}

		/*
		float* DL = TEMPORARY_ARRAY(float, n);
		float* D = TEMPORARY_ARRAY(float, n + 1);
		float* DU = TEMPORARY_ARRAY(float, n);
		float* B = TEMPORARY_ARRAY(float, n + 1);

		for (uint k = 0; k < 3; k++) {
			for (uint i = 1; i < n; i++) {
				float a0 = points[i - 1][k];
				float a1 = points[i][k];
				float a2 = points[i + 1][k];

				DL[i - 1] = 1.0;
				D[i] = 4.0;
				DU[i] = 1.0;
				B[i] = 3.0*(a2 - a1) + 3.0*(a1 - a0);
			}

			B[0] = 0; B[n] = 0;
			D[0] = 1; D[n] = 1;

			DL[n - 1] = 0;
			DU[0] = 0;

            if (true) {
                char spaces[256];
                for (uint i = 0; i < 256; i++) spaces[i] = ' ';

                for (uint i = 0; i < n+1; i++) {
                    if (i == 0) printf("[ %.2f, %.2f,%.*s][%i] = [%f]\n", D[i], DU[0], (n-1)*6, spaces, i, B[i]);
                    else if (i == n) printf("[%.*s %.2f, %.2f ][%i] = [%f]\n", (i-1)*6, spaces, DL[i-1], D[i], i, B[i]);
                    else printf("[%.*s %.2f, %.2f, %.2f,%.*s][%i] = [%f]\n", (i-1)*6, spaces, DL[i-1], D[i], DU[i], (n-1-i)*6, spaces, i, B[i]);
                }
            }

			int info = LAPACKE_sgtsv(LAPACK_ROW_MAJOR, n + 1, 1, DL, D, DU, B, 1);
			assert(info == 0);

			for (uint i = 0; i < n; i++) {
				float a0 = points[i][k];
				float a1 = points[i + 1][k];

				float c0 = B[i];
				float c1 = B[i + 1];

				coeff[i].a[k] = a0;
				coeff[i].b[k] = (a1 - a0) - 1.0 / 3 * (2 * c0 + c1);
				coeff[i].c[k] = c0;
				coeff[i].d[k] = (c1 - c0) / 3.0;
			}
		}
		*/

        total_length = 0;
		for (uint i = 0; i < n; i++) {
			lengths[i] = integrate(i,1.0);
            total_length += lengths[i];
		}
	}

	vec3 at_time(float t) {
		uint i = min(t, coeff.length-1);
		float x = t - i;
		float xx = x * x;
		float xxx = xx * x;

		vec3 result = coeff[i].a + coeff[i].b * x + coeff[i].c * xx + coeff[i].d * xxx;
		return result;
	}

    vec3 tangent(float t) {
        uint i = min(t, coeff.length - 1);
        float x = t - i;
        float xx = x * x;

        vec3 result = coeff[i].b + 2.0f * coeff[i].c * x + 3.0f * coeff[i].d * xx;
        return normalize(result);
    }

	float arc_length_integrand(uint spline, float t) {
		float tt = t * t;

		vec3 result = coeff[spline].b + 2.0f * coeff[spline].c * t + 3.0f * coeff[spline].d * tt;
		return length(result);
	}

	float integrate(uint spline, float t) {
		uint n = 16;
		float h = t / n;

		float XI0 = arc_length_integrand(spline, t);
		float XI1 = 0;
		float XI2 = 0;

		for (uint i = 0; i < n; i++) {
			float X = i * h;
			if (i % 2 == 0) XI2 += arc_length_integrand(spline, X);
			else XI1 += arc_length_integrand(spline, X);
		}

		float XI = h * (XI0 + 2 * XI2 + 4 * XI1) / 3.0f;
		return XI;
	}

    float const_speed_reparam(float t, float speed) {
        float desired_distance = speed * t;

        float distance = 0.0f;

        uint spline;
        for (spline = 0; spline < lengths.length; spline++) {
            float new_distance = distance + lengths[spline];
            if (new_distance >= desired_distance) break;
			distance = new_distance;
        }

        assert(spline < lengths.length);
        float d = desired_distance - distance;

        float s = 0.5f;
        for (uint i = 0; i < 10; i++) {
            float arc_length = integrate(spline, s);
            float arc_speed = arc_length_integrand(spline, s);
            //printf("S %f, arc length %f, arc speed %f\n", s, arc_length, arc_speed);
            s = s - (arc_length - d) / arc_speed;
        }

        return spline + s;
    }

	vec3 const_speed_at_time(float t, float speed) {
		return at_time(const_speed_reparam(t, speed));
	}
};


#include <math.h>
#include <stdio.h>
#include <mpfr.h>
#include <gmp.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <gsl/gsl_sf_legendre.h>
#include <petsc.h>

#undef __FUNCT__
#define __FUNCT__ "DEwk"
PetscErrorCode DEwk(PetscInt k, mpfr_t h, mpfr_t *wk)
{
  PetscErrorCode ierr;
  mpfr_t kh, msinh, mcosh, piOver2, tmp;
  PetscFunctionBegin;

  mpfr_inits(kh, msinh, mcosh, piOver2, tmp, NULL);

  //init piOver2
  mpfr_const_pi(piOver2, MPFR_RNDN);
  mpfr_mul_d(piOver2, piOver2, 0.5, MPFR_RNDN);

  mpfr_set_d(kh, (double) k, MPFR_RNDN);
  mpfr_mul(kh, kh, h, MPFR_RNDN);
  mpfr_set(*wk, h, MPFR_RNDN);
  mpfr_sinh_cosh(msinh, mcosh, kh, MPFR_RNDN);
  mpfr_mul(msinh, msinh, piOver2, MPFR_RNDN);
  mpfr_mul(mcosh, mcosh, piOver2, MPFR_RNDN);
  mpfr_cosh(tmp, msinh, MPFR_RNDN);
  mpfr_sqr(tmp, tmp, MPFR_RNDN);
  mpfr_mul(*wk, *wk, mcosh, MPFR_RNDN);
  mpfr_div(*wk, *wk, tmp, MPFR_RNDN);

  PetscFunctionReturn(0);
}

/* have to make single exponential */
#undef __FUNCT__
#define __FUNCT__ "SEwk"
PetscErrorCode SEwk(PetscInt k, mpfr_t h, mpfr_t *wk)
{
  PetscErrorCode ierr;
  mpfr_t kh, msinh, mcosh, piOver2, tmp;
  PetscFunctionBegin;

  mpfr_inits(kh, msinh, mcosh, piOver2, tmp, NULL);

  //init piOver2
  mpfr_const_pi(piOver2, MPFR_RNDN);
  mpfr_mul_d(piOver2, piOver2, 0.5, MPFR_RNDN);

  mpfr_set_d(kh, (double) k, MPFR_RNDN);
  mpfr_mul(kh, kh, h, MPFR_RNDN);
  //printf("wow kh: %15.15f\n", mpfr_get_d(kh, MPFR_RNDN));
  mpfr_tanh(*wk, kh, MPFR_RNDN);
  //printf("wow wk: %15.15f\n", mpfr_get_d(*wk, MPFR_RNDN));
  mpfr_mul(*wk, *wk, *wk, MPFR_RNDN);
  mpfr_d_sub(*wk, 1.0, *wk, MPFR_RNDN);
  mpfr_mul(*wk, *wk, h, MPFR_RNDN);
  //mpfr_set(*wk, h, MPFR_RNDN);
  //mpfr_sinh_cosh(msinh, mcosh, kh, MPFR_RNDN);
  //mpfr_mul(msinh, msinh, piOver2, MPFR_RNDN);
  //mpfr_mul(mcosh, mcosh, piOver2, MPFR_RNDN);
  //mpfr_cosh(tmp, msinh, MPFR_RNDN);
  //mpfr_sqr(tmp, tmp, MPFR_RNDN);
  //mpfr_mul(*wk, *wk, mcosh, MPFR_RNDN);
  //mpfr_div(*wk, *wk, tmp, MPFR_RNDN);

  PetscFunctionReturn(0);
}

/* have to make single exponential */
#undef __FUNCT__
#define __FUNCT__ "ERFwk"
PetscErrorCode ERFwk(PetscInt k, mpfr_t h, mpfr_t *wk)
{
  PetscErrorCode ierr;
  mpfr_t kh, msinh, mcosh, piOver2, tmp;
  PetscFunctionBegin;

  mpfr_inits(kh, msinh, mcosh, piOver2, tmp, NULL);

  // init constant
  mpfr_const_pi(piOver2, MPFR_RNDN);
  mpfr_sqrt(piOver2, piOver2, MPFR_RNDN);
  mpfr_d_div(piOver2, 2.0, piOver2, MPFR_RNDN);

  mpfr_set_d(kh, (double) k, MPFR_RNDN);
  mpfr_mul(kh, kh, h, MPFR_RNDN);
  mpfr_mul(tmp, kh, kh, MPFR_RNDN);
  mpfr_mul_d(tmp, tmp, -1.0, MPFR_RNDN);
  mpfr_exp(*wk, tmp, MPFR_RNDN);
  mpfr_mul(*wk, *wk, h, MPFR_RNDN);
  mpfr_mul(*wk, *wk, piOver2, MPFR_RNDN);

  PetscFunctionReturn(0);
}

/* generates n weights spaced h apart in the positive direction */
/* includes "center" n=0 weight as first entry */
#undef __FUNCT__
#define __FUNCT__ "DExkwk"
PetscErrorCode DExkwk(PetscInt n, mpfr_t h, mpfr_t **xk, mpfr_t **wk)
{
  PetscErrorCode ierr;
  PetscInt k;
  mpfr_t kh, msinh, mcosh, piOver2, tmp;
  PetscReal alp, bet;
  PetscInt flopCount;
  PetscFunctionBegin;
  flopCount = 0;

  mpfr_inits(kh, msinh, mcosh, piOver2, tmp, NULL);

  // init piOver2
  mpfr_const_pi(piOver2, MPFR_RNDN);
  mpfr_mul_d(piOver2, piOver2, 0.5, MPFR_RNDN); flopCount ++;

  for(k=0; k<n; ++k) {
    /* calculate wk */
    mpfr_set_d(kh, (double) k, MPFR_RNDN);
    mpfr_mul(kh, kh, h, MPFR_RNDN);
    mpfr_set((*wk)[k], h, MPFR_RNDN);
    mpfr_sinh_cosh(msinh, mcosh, kh, MPFR_RNDN);
    mpfr_mul(msinh, msinh, piOver2, MPFR_RNDN);
    mpfr_mul(mcosh, mcosh, piOver2, MPFR_RNDN);
    mpfr_cosh(tmp, msinh, MPFR_RNDN);
    mpfr_sqr(tmp, tmp, MPFR_RNDN);
    mpfr_mul((*wk)[k], (*wk)[k], mcosh, MPFR_RNDN);
    mpfr_div((*wk)[k], (*wk)[k], tmp, MPFR_RNDN); flopCount += 9;

    /* calculate xk */
    mpfr_set_d((*xk)[k], 1.0, MPFR_RNDZ);
    mpfr_cosh(tmp, msinh, MPFR_RNDN);
    mpfr_div((*xk)[k], (*xk)[k], tmp, MPFR_RNDZ);
    mpfr_exp(tmp, msinh, MPFR_RNDN);
    mpfr_div((*xk)[k], (*xk)[k], tmp, MPFR_RNDZ); flopCount += 4;

  }
  mpfr_clears(kh, msinh, mcosh, piOver2, tmp, NULL);

  ierr = PetscLogFlops(flopCount);CHKERRQ(ierr);
  PetscFunctionReturn(0);
}

/* generates n weights spaced h apart in the positive direction */
/* includes "center" n=0 weight as first entry */
#undef __FUNCT__
#define __FUNCT__ "SExkwk"
PetscErrorCode SExkwk(PetscInt n, mpfr_t h, mpfr_t **xk, mpfr_t **wk)
{
  PetscErrorCode ierr;
  PetscInt k;
  mpfr_t kh, msinh, mcosh, piOver2, tmp;
  PetscReal alp, bet;
  PetscInt flopCount;
  PetscFunctionBegin;
  flopCount = 0;

  mpfr_inits(kh, msinh, mcosh, piOver2, tmp, NULL);

  // init piOver2
  mpfr_const_pi(piOver2, MPFR_RNDN);
  mpfr_mul_d(piOver2, piOver2, 0.5, MPFR_RNDN); flopCount ++;

  for(k=0; k<n; ++k) {
    /* calculate wk */
    mpfr_set_d(kh, (double) k, MPFR_RNDN);
    mpfr_mul(kh, kh, h, MPFR_RNDN);
    mpfr_tanh((*wk)[k], kh, MPFR_RNDN);
    mpfr_mul((*wk)[k], (*wk)[k], (*wk)[k], MPFR_RNDN);
    mpfr_d_sub((*wk)[k], 1.0, (*wk)[k], MPFR_RNDN);
    mpfr_mul((*wk)[k], (*wk)[k], h, MPFR_RNDN); flopCount += 5;

    /* calculate xk */
    mpfr_tanh((*xk)[k], kh, MPFR_RNDN); flopCount += 1;
    //mpfr_set_d((*xk)[k], 1.0, MPFR_RNDZ);
    //mpfr_cosh(tmp, msinh, MPFR_RNDN);
    //mpfr_div((*xk)[k], (*xk)[k], tmp, MPFR_RNDZ);
    //mpfr_exp(tmp, msinh, MPFR_RNDN);
    //mpfr_div((*xk)[k], (*xk)[k], tmp, MPFR_RNDZ); flopCount += 4;
    //mpfr_d_sub(tmp, 1.0, (*xk)[k], MPFR_RNDN);
    //printf("xk: %15.15e\n1-xk: %15.15e\nwk: %3.3e\n\n", mpfr_get_d((*xk)[k], MPFR_RNDN), mpfr_get_d(tmp, MPFR_RNDN), mpfr_get_d((*wk)[k], MPFR_RNDN));
  }
  mpfr_clears(kh, msinh, mcosh, piOver2, tmp, NULL);

  ierr = PetscLogFlops(flopCount);CHKERRQ(ierr);
  PetscFunctionReturn(0);
}

/* generates n weights spaced h apart in the positive direction */
/* includes "center" n=0 weight as first entry */
#undef __FUNCT__
#define __FUNCT__ "ERFxkwk"
PetscErrorCode ERFxkwk(PetscInt n, mpfr_t h, mpfr_t **xk, mpfr_t **wk)
{
  PetscErrorCode ierr;
  PetscInt k;
  mpfr_t kh, msinh, mcosh, piOver2, tmp;
  PetscReal alp, bet;
  PetscInt flopCount;
  PetscFunctionBegin;
  flopCount = 0;

  mpfr_inits(kh, msinh, mcosh, piOver2, tmp, NULL);

  // init piOver2
  mpfr_const_pi(piOver2, MPFR_RNDN);
  mpfr_sqrt(piOver2, piOver2, MPFR_RNDN);
  mpfr_d_div(piOver2, 2.0, piOver2, MPFR_RNDN);
  //mpfr_mul_d(piOver2, piOver2, 0.5, MPFR_RNDN); flopCount ++;

  for(k=0; k<n; ++k) {
    /* calculate wk */
    mpfr_set_d(kh, (double) k, MPFR_RNDN);
    mpfr_mul(kh, kh, h, MPFR_RNDN);
    mpfr_mul(tmp, kh, kh, MPFR_RNDN);
    mpfr_mul_d(tmp, tmp, -1.0, MPFR_RNDN);
    mpfr_exp((*wk)[k], tmp, MPFR_RNDN);
    mpfr_mul((*wk)[k], (*wk)[k], h, MPFR_RNDN);
    mpfr_mul((*wk)[k], (*wk)[k], piOver2, MPFR_RNDN);

    /* calculate xk */
    mpfr_erf((*xk)[k], kh, MPFR_RNDN);
  }
  mpfr_clears(kh, msinh, mcosh, piOver2, tmp, NULL);

  ierr = PetscLogFlops(flopCount);CHKERRQ(ierr);
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "inte"
PetscErrorCode inte(mpfr_t *x, mpfr_t *val, void *ctx)
{
  PetscErrorCode ierr;
  PetscInt flopCount;
  PetscFunctionBegin;
  flopCount = 0;

  //mpfr_mul(*val, *x, *x, MPFR_RNDN);
  //mpfr_mul(*val, *val, *x, MPFR_RNDN); flopCount += 2;
  mpfr_const_pi(*val, MPFR_RNDN);
  mpfr_mul_d(*val, *val, 2.0, MPFR_RNDN);
  mpfr_mul(*val, *val, *x, MPFR_RNDN);
  mpfr_sin(*val, *val, MPFR_RNDN);
  mpfr_mul(*val, *val, *x, MPFR_RNDN);
  //printf("xk: %15.15f\n", mpfr_get_d(*x, MPFR_RNDN));
  //printf("val: %15.15f\n", mpfr_get_d(*val, MPFR_RNDN));
  //mpfr_set_d(*val, 1.0, MPFR_RNDN);
  //mpfr_set(*val, *x, MPFR_RNDN);

  ierr = PetscLogFlops(flopCount);CHKERRQ(ierr);
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "FindStepSize"
PetscErrorCode FindStepSize(PetscInt prec, PetscInt nPts, mpfr_t *step)
{
  PetscErrorCode ierr;
  const PetscReal tol = 1e-10;
  mpfr_t wk, tmp, h, hl, hr, lwk, rwk, swk, slwk, srwk, error;
  PetscInt cont;
  PetscFunctionBegin;

  if(prec < 1) {
    printf("prec should be positive\n");
    PetscFunctionReturn(1);
  }

  mpfr_inits(wk, tmp, h, hl, hr, lwk, rwk, swk, slwk, srwk, error, NULL);

  mpfr_set_d(hl, 0.0, MPFR_RNDN);
  mpfr_set_d(hr, 2.0, MPFR_RNDN);

  /* make hr large enough */
  mpfr_set_d(hr, 1.0, MPFR_RNDN);
  cont = 1;
  while(cont == 1) {
    ierr = DEwk(nPts, hr, &rwk);CHKERRQ(ierr);
    mpfr_log10(srwk, rwk, MPFR_RNDN);
    if(mpfr_get_d(srwk, MPFR_RNDN) < -2*prec)
      cont = 0;
    else
      mpfr_mul_d(hr, hr, 2.0, MPFR_RNDN); // make hr larger if value not small
  }
  //printf("hr: %15.15f\n", mpfr_get_d(hr, MPFR_RNDN));
  /* make hl small enough */
  mpfr_mul_d(hl, hr, 0.5, MPFR_RNDN);
  cont = 1;
  while(cont == 1) {
    ierr = DEwk(nPts, hl, &lwk);CHKERRQ(ierr);
    mpfr_log10(slwk, lwk, MPFR_RNDN);
    if(mpfr_get_d(slwk, MPFR_RNDN) > -2*prec)
      cont = 0;
    else
      mpfr_mul_d(hl, hl, 0.5, MPFR_RNDN); // make hl larger if value not small
  }
  //printf("hl: %15.15f\n", mpfr_get_d(hl, MPFR_RNDN));
  //ierr = DEwk(nPts, hl, &lwk);CHKERRQ(ierr);
  //ierr = DEwk(nPts, hr, &rwk);CHKERRQ(ierr);

  cont = 1;
  while(cont == 1) {
    // average hl and hr
    mpfr_add(h, hl, hr, MPFR_RNDN);
    mpfr_mul_d(h, h, 0.5, MPFR_RNDN);

    // calculate weight and size
    ierr = DEwk(nPts, h, &wk);CHKERRQ(ierr);
    ierr = DEwk(nPts, hl, &lwk);CHKERRQ(ierr);
    ierr = DEwk(nPts, hr, &rwk);CHKERRQ(ierr);
    mpfr_log10(swk, wk, MPFR_RNDN);
    mpfr_log10(slwk, lwk, MPFR_RNDN);
    mpfr_log10(srwk, rwk, MPFR_RNDN);
    if( mpfr_get_d(swk, MPFR_RNDN) < -2*prec)
      mpfr_set(hr, h, MPFR_RNDN);
    else
      mpfr_set(hl, h, MPFR_RNDN);
    // calculate maximum possible error
    mpfr_sub(error, slwk, srwk, MPFR_RNDN);
    mpfr_abs(error, error, MPFR_RNDN);
    if(mpfr_get_d(error, MPFR_RNDN) < tol)
       cont = 0;
  }

  mpfr_set(*step, h, MPFR_RNDN);

  mpfr_clears(wk, tmp, h, hl, hr, lwk, rwk, swk, slwk, srwk, error, NULL);
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "FindStepSizeSE"
PetscErrorCode FindStepSizeSE(PetscInt prec, PetscInt nPts, mpfr_t *step)
{
  PetscErrorCode ierr;
  const PetscReal tol = 1e-25;
  mpfr_t wk, tmp, h, hl, hr, lwk, rwk, swk, slwk, srwk, error;
  PetscInt cont;
  PetscFunctionBegin;

  if(prec < 1) {
    printf("prec should be positive\n");
    PetscFunctionReturn(1);
  }

  mpfr_inits(wk, tmp, h, hl, hr, lwk, rwk, swk, slwk, srwk, error, NULL);

  mpfr_set_d(hl, 0.0, MPFR_RNDN);
  mpfr_set_d(hr, 2.0, MPFR_RNDN);

  /* make hr large enough */
  mpfr_set_d(hr, 1.0, MPFR_RNDN);
  cont = 1;
  while(cont == 1) {
    ierr = SEwk(nPts, hr, &rwk);CHKERRQ(ierr);
    mpfr_log10(srwk, rwk, MPFR_RNDN);
    if(mpfr_get_d(srwk, MPFR_RNDN) < -2*prec)
      cont = 0;
    else
      mpfr_mul_d(hr, hr, 2.0, MPFR_RNDN); // make hr larger if value not small
  }
  //printf("hr: %15.15f\n", mpfr_get_d(hr, MPFR_RNDN));
  /* make hl small enough */
  mpfr_mul_d(hl, hr, 0.5, MPFR_RNDN);
  cont = 1;
  while(cont == 1) {
    ierr = SEwk(nPts, hl, &lwk);CHKERRQ(ierr);
    mpfr_log10(slwk, lwk, MPFR_RNDN);
    if(mpfr_get_d(slwk, MPFR_RNDN) > -2*prec)
      cont = 0;
    else
      mpfr_mul_d(hl, hl, 0.5, MPFR_RNDN); // make hl larger if value not small
  }
  //printf("hl: %15.15f\n", mpfr_get_d(hl, MPFR_RNDN));
  ierr = SEwk(nPts, hl, &lwk);CHKERRQ(ierr);
  ierr = SEwk(nPts, hr, &rwk);CHKERRQ(ierr);
  //printf("lwk: %3.3e\n", mpfr_get_d(lwk, MPFR_RNDN));
  //printf("rwk: %3.3e\n", mpfr_get_d(rwk, MPFR_RNDN));

  cont = 1;
  while(cont == 1) {
    // average hl and hr
    mpfr_add(h, hl, hr, MPFR_RNDN);
    mpfr_mul_d(h, h, 0.5, MPFR_RNDN);

    // calculate weight and size
    ierr = SEwk(nPts, h, &wk);CHKERRQ(ierr);
    ierr = SEwk(nPts, hl, &lwk);CHKERRQ(ierr);
    ierr = SEwk(nPts, hr, &rwk);CHKERRQ(ierr);
    mpfr_log10(swk, wk, MPFR_RNDN);
    mpfr_log10(slwk, lwk, MPFR_RNDN);
    mpfr_log10(srwk, rwk, MPFR_RNDN);
    if( mpfr_get_d(swk, MPFR_RNDN) < -2*prec)
      mpfr_set(hr, h, MPFR_RNDN);
    else
      mpfr_set(hl, h, MPFR_RNDN);
    // calculate maximum possible error
    mpfr_sub(error, slwk, srwk, MPFR_RNDN);
    mpfr_abs(error, error, MPFR_RNDN);
    if(mpfr_get_d(error, MPFR_RNDN) < tol)
       cont = 0;
    //printf("h: %15.15f\nlwk: %3.3e\nrwk: %3.3e\n", mpfr_get_d(h, MPFR_RNDN), mpfr_get_d(lwk, MPFR_RNDN), mpfr_get_d(rwk, MPFR_RNDN));
  }

  mpfr_set(*step, h, MPFR_RNDN);

  mpfr_clears(wk, tmp, h, hl, hr, lwk, rwk, swk, slwk, srwk, error, NULL);
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "FindStepSizeERF"
PetscErrorCode FindStepSizeERF(PetscInt prec, PetscInt nPts, mpfr_t *step)
{
  PetscErrorCode ierr;
  const PetscReal tol = 1e-25;
  mpfr_t wk, tmp, h, hl, hr, lwk, rwk, swk, slwk, srwk, error;
  PetscInt cont;
  PetscFunctionBegin;

  if(prec < 1) {
    printf("prec should be positive\n");
    PetscFunctionReturn(1);
  }

  mpfr_inits(wk, tmp, h, hl, hr, lwk, rwk, swk, slwk, srwk, error, NULL);

  mpfr_set_d(hl, 0.0, MPFR_RNDN);
  mpfr_set_d(hr, 2.0, MPFR_RNDN);

  /* make hr large enough */
  mpfr_set_d(hr, 1.0, MPFR_RNDN);
  cont = 1;
  while(cont == 1) {
    ierr = ERFwk(nPts, hr, &rwk);CHKERRQ(ierr);
    mpfr_log10(srwk, rwk, MPFR_RNDN);
    if(mpfr_get_d(srwk, MPFR_RNDN) < -2*prec)
      cont = 0;
    else
      mpfr_mul_d(hr, hr, 2.0, MPFR_RNDN); // make hr larger if value not small
  }
  //printf("hr: %15.15f\n", mpfr_get_d(hr, MPFR_RNDN));
  /* make hl small enough */
  mpfr_mul_d(hl, hr, 0.5, MPFR_RNDN);
  cont = 1;
  while(cont == 1) {
    ierr = ERFwk(nPts, hl, &lwk);CHKERRQ(ierr);
    mpfr_log10(slwk, lwk, MPFR_RNDN);
    if(mpfr_get_d(slwk, MPFR_RNDN) > -2*prec)
      cont = 0;
    else
      mpfr_mul_d(hl, hl, 0.5, MPFR_RNDN); // make hl larger if value not small
  }
  //printf("hl: %15.15f\n", mpfr_get_d(hl, MPFR_RNDN));
  ierr = ERFwk(nPts, hl, &lwk);CHKERRQ(ierr);
  ierr = ERFwk(nPts, hr, &rwk);CHKERRQ(ierr);
  //printf("lwk: %3.3e\n", mpfr_get_d(lwk, MPFR_RNDN));
  //printf("rwk: %3.3e\n", mpfr_get_d(rwk, MPFR_RNDN));

  cont = 1;
  while(cont == 1) {
    // average hl and hr
    mpfr_add(h, hl, hr, MPFR_RNDN);
    mpfr_mul_d(h, h, 0.5, MPFR_RNDN);

    // calculate weight and size
    ierr = ERFwk(nPts, h, &wk);CHKERRQ(ierr);
    ierr = ERFwk(nPts, hl, &lwk);CHKERRQ(ierr);
    ierr = ERFwk(nPts, hr, &rwk);CHKERRQ(ierr);
    mpfr_log10(swk, wk, MPFR_RNDN);
    mpfr_log10(slwk, lwk, MPFR_RNDN);
    mpfr_log10(srwk, rwk, MPFR_RNDN);
    if( mpfr_get_d(swk, MPFR_RNDN) < -2*prec)
      mpfr_set(hr, h, MPFR_RNDN);
    else
      mpfr_set(hl, h, MPFR_RNDN);
    // calculate maximum possible error
    mpfr_sub(error, slwk, srwk, MPFR_RNDN);
    mpfr_abs(error, error, MPFR_RNDN);
    if(mpfr_get_d(error, MPFR_RNDN) < tol)
       cont = 0;
    //printf("h: %15.15f\n", mpfr_get_d(h, MPFR_RNDN));
  }
  //printf("h: %15.15f\n", mpfr_get_d(h, MPFR_RNDN));
  //printf("h: %15.15f\nlwk: %3.3e\nrwk: %3.3e\n", mpfr_get_d(h, MPFR_RNDN), mpfr_get_d(lwk, MPFR_RNDN), mpfr_get_d(rwk, MPFR_RNDN));
  mpfr_set(*step, h, MPFR_RNDN);

  mpfr_clears(wk, tmp, h, hl, hr, lwk, rwk, swk, slwk, srwk, error, NULL);
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "DEQuad"
PetscErrorCode DEQuad(PetscErrorCode (*f)(mpfr_t*,mpfr_t*,void*), mpfr_t a, mpfr_t b, PetscInt prec, PetscInt nPts, PetscReal *integral, void *ctx)
{
  PetscErrorCode ierr;
  PetscInt k;
  PetscInt nSide;
  mpfr_t alpha, beta;
  mpfr_t leftXk, rightXk;
  mpfr_t wk;
  mpfr_t *wkVals;
  mpfr_t *xkVals;
  mpfr_t lx, rx;
  mpfr_t leftFx, rightFx;
  mpfr_t pi2;
  mpfr_t kh, h;
  mpfr_t sum;
  PetscInt flopCount;
  PetscFunctionBegin;
  flopCount = 0;

  if(nPts % 2 == 0) {
    nSide = nPts/2; flopCount++;
  }
  if(nPts % 2 == 1) {
    nSide = (nPts-1)/2; flopCount++;
  }

  /* initializations */
  mpfr_inits(alpha, beta, leftXk, rightXk, wk, leftFx, rightFx, pi2, kh, h, sum, lx, rx, NULL);
  wkVals = (mpfr_t*) malloc(sizeof(mpfr_t)*(nSide+1));
  xkVals = (mpfr_t*) malloc(sizeof(mpfr_t)*(nSide+1));
  for(k=0; k<nSide+1; ++k)
    mpfr_inits(xkVals[k], wkVals[k], NULL);

  // init alpha, beta
  mpfr_set(alpha, b, MPFR_RNDN);
  mpfr_sub(alpha, alpha, a, MPFR_RNDN);
  mpfr_mul_d(alpha, alpha, 0.5, MPFR_RNDN);
  mpfr_set(beta, b, MPFR_RNDN);
  mpfr_add(beta, beta, a, MPFR_RNDN);
  mpfr_mul_d(beta, beta, 0.5, MPFR_RNDN); flopCount += 4;

  /* determine right step size */
  ierr = FindStepSize(prec, nSide, &h); // not including in flop count
  //printf("DE step size: %15.15f\n", mpfr_get_d(h, MPFR_RNDN));
  //mpfr_set_d(h, 0.04, MPFR_RNDN);

  /* calculate abscissas, weights */
  ierr = DExkwk(nSide+1, h, &xkVals, &wkVals);CHKERRQ(ierr);

  /* center term */
  mpfr_set_d(sum, 0.0, MPFR_RNDN);
  mpfr_set(lx, b, MPFR_RNDN);
  mpfr_add(lx, lx, a, MPFR_RNDN);
  mpfr_mul_d(lx, lx, .5, MPFR_RNDN);
  f(&lx, &sum, ctx);
  mpfr_mul(sum, sum, wkVals[0], MPFR_RNDN);
  mpfr_mul(sum, sum, alpha,     MPFR_RNDN); flopCount += 4;

  for(k=1; k <= nSide; ++k) {
    // adjust xk to [a,b]
    mpfr_sub_d(lx, xkVals[k], 1.0, MPFR_RNDZ);
    mpfr_mul(lx, lx, alpha, MPFR_RNDU);
    mpfr_add(lx, lx, beta, MPFR_RNDU);
    mpfr_d_sub(rx, 1.0, xkVals[k], MPFR_RNDZ);
    mpfr_mul(rx, rx, alpha, MPFR_RNDD);
    mpfr_add(rx, rx, beta , MPFR_RNDD); flopCount += 6;

    double left = mpfr_get_d(lx, MPFR_RNDN);
    double right = mpfr_get_d(rx, MPFR_RNDN);
    double xk = mpfr_get_d(xkVals[k], MPFR_RNDN);

    /* calculate function values */
    f(&lx, &leftFx , ctx);
    f(&rx, &rightFx, ctx);

    /* update sum */
    mpfr_mul(leftFx, leftFx, wkVals[k], MPFR_RNDN);
    mpfr_mul(rightFx, rightFx, wkVals[k], MPFR_RNDN);
    mpfr_mul(leftFx, leftFx, alpha, MPFR_RNDN);
    mpfr_mul(rightFx, rightFx, alpha, MPFR_RNDN);
    //double l1 = mpfr_get_d(leftFx, MPFR_RNDN);
    //double l2 = mpfr_get_d(rightFx, MPFR_RNDN);
    //printf("leftval: %15.15f\n", l1);
    //printf("rightval: %15.15f\n", l2);
    mpfr_add(sum, sum, leftFx, MPFR_RNDN);
    mpfr_add(sum, sum, rightFx, MPFR_RNDN); flopCount += 6;
  }
  PetscReal prog = mpfr_get_d(sum, MPFR_RNDN);
  //printf("sum: %15.15f\n", prog);

  *integral = prog;

  mpfr_clears(alpha, beta, leftXk, rightXk, wk, leftFx, rightFx, pi2, kh, h, sum, lx, rx, NULL);
  for(k=0; k<nSide+1; ++k)
    mpfr_clears(wkVals[k], xkVals[k], NULL);
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "SEQuad"
PetscErrorCode SEQuad(PetscErrorCode (*f)(mpfr_t*,mpfr_t*,void*), mpfr_t a, mpfr_t b, PetscInt prec, PetscInt nPts, PetscReal *integral, void *ctx)
{
  PetscErrorCode ierr;
  PetscInt k;
  PetscInt nSide;
  mpfr_t alpha, beta;
  mpfr_t leftXk, rightXk;
  mpfr_t wk;
  mpfr_t *wkVals;
  mpfr_t *xkVals;
  mpfr_t lx, rx;
  mpfr_t leftFx, rightFx;
  mpfr_t pi2;
  mpfr_t kh, h;
  mpfr_t sum;
  PetscInt flopCount;
  PetscFunctionBegin;
  flopCount = 0;

  if(nPts % 2 == 0) {
    nSide = nPts/2; flopCount++;
  }
  if(nPts % 2 == 1) {
    nSide = (nPts-1)/2; flopCount++;
  }

  /* initializations */
  mpfr_inits(alpha, beta, leftXk, rightXk, wk, leftFx, rightFx, pi2, kh, h, sum, lx, rx, NULL);
  wkVals = (mpfr_t*) malloc(sizeof(mpfr_t)*(nSide+1));
  xkVals = (mpfr_t*) malloc(sizeof(mpfr_t)*(nSide+1));
  for(k=0; k<nSide+1; ++k)
    mpfr_inits(xkVals[k], wkVals[k], NULL);

  // init alpha, beta
  mpfr_set(alpha, b, MPFR_RNDN);
  mpfr_sub(alpha, alpha, a, MPFR_RNDN);
  mpfr_mul_d(alpha, alpha, 0.5, MPFR_RNDN);
  mpfr_set(beta, b, MPFR_RNDN);
  mpfr_add(beta, beta, a, MPFR_RNDN);
  mpfr_mul_d(beta, beta, 0.5, MPFR_RNDN); flopCount += 4;

  /* determine right step size */
  ierr = FindStepSizeSE(prec, nSide, &h); // not including in flop count
  //printf("step size: %15.15f\n", mpfr_get_d(h, MPFR_RNDN));
  //printf("step size: %15.15f\n", mpfr_get_d(h, MPFR_RNDN));
  //mpfr_set_d(h, 0.04, MPFR_RNDN);

  /* calculate abscissas, weights */
  ierr = SExkwk(nSide+1, h, &xkVals, &wkVals);CHKERRQ(ierr);

  /* center term */
  mpfr_set_d(sum, 0.0, MPFR_RNDN);
  mpfr_set(lx, b, MPFR_RNDN);
  mpfr_add(lx, lx, a, MPFR_RNDN);
  mpfr_mul_d(lx, lx, .5, MPFR_RNDN);
  f(&lx, &sum, ctx);
  mpfr_mul(sum, sum, wkVals[0], MPFR_RNDN);
  mpfr_mul(sum, sum, alpha,     MPFR_RNDN); flopCount += 4;

  for(k=1; k <= nSide; ++k) {
    // adjust xk to [a,b]
    //mpfr_sub_d(lx, xkVals[k], 1.0, MPFR_RNDZ);
    mpfr_mul_d(lx, xkVals[k], -1.0, MPFR_RNDN);
    mpfr_mul(lx, lx, alpha, MPFR_RNDU);
    mpfr_add(lx, lx, beta, MPFR_RNDU);
    //mpfr_d_sub(rx, 1.0, xkVals[k], MPFR_RNDZ);
    mpfr_set(rx, xkVals[k], MPFR_RNDN);
    mpfr_mul(rx, rx, alpha, MPFR_RNDD);
    mpfr_add(rx, rx, beta , MPFR_RNDD); flopCount += 6;

    double left = mpfr_get_d(lx, MPFR_RNDN);
    double right = mpfr_get_d(rx, MPFR_RNDN);
    double xk = mpfr_get_d(xkVals[k], MPFR_RNDN);

    /* calculate function values */
    f(&lx, &leftFx , ctx);
    f(&rx, &rightFx, ctx);

    /* update sum */
    mpfr_mul(leftFx, leftFx, wkVals[k], MPFR_RNDN);
    mpfr_mul(rightFx, rightFx, wkVals[k], MPFR_RNDN);
    mpfr_mul(leftFx, leftFx, alpha, MPFR_RNDN);
    mpfr_mul(rightFx, rightFx, alpha, MPFR_RNDN);
    //double l1 = mpfr_get_d(leftFx, MPFR_RNDN);
    //double l2 = mpfr_get_d(rightFx, MPFR_RNDN);
    //printf("leftval: %15.15f\n", l1);
    //printf("rightval: %15.15f\n", l2);
    mpfr_add(sum, sum, leftFx, MPFR_RNDN);
    mpfr_add(sum, sum, rightFx, MPFR_RNDN); flopCount += 6;
    //printf("sum[%d] = %15.15f\n", k, mpfr_get_d(sum, MPFR_RNDN));
    //printf("wk[%d] = %3.3e\n\n", k, mpfr_get_d(wkVals[k], MPFR_RNDN));
  }
  PetscReal prog = mpfr_get_d(sum, MPFR_RNDN);
  //printf("sum: %15.15f\n", prog);

  *integral = prog;

  mpfr_clears(alpha, beta, leftXk, rightXk, wk, leftFx, rightFx, pi2, kh, h, sum, lx, rx, NULL);
  for(k=0; k<nSide+1; ++k)
    mpfr_clears(wkVals[k], xkVals[k], NULL);
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "ERFQuad"
PetscErrorCode ERFQuad(PetscErrorCode (*f)(mpfr_t*,mpfr_t*,void*), mpfr_t a, mpfr_t b, PetscInt prec, PetscInt nPts, PetscReal *integral, void *ctx)
{
  PetscErrorCode ierr;
  PetscInt k;
  PetscInt nSide;
  mpfr_t alpha, beta;
  mpfr_t leftXk, rightXk;
  mpfr_t wk;
  mpfr_t *wkVals;
  mpfr_t *xkVals;
  mpfr_t lx, rx;
  mpfr_t leftFx, rightFx;
  mpfr_t pi2;
  mpfr_t kh, h;
  mpfr_t sum;
  PetscInt flopCount;
  PetscFunctionBegin;
  flopCount = 0;

  if(nPts % 2 == 0) {
    nSide = nPts/2; flopCount++;
  }
  if(nPts % 2 == 1) {
    nSide = (nPts-1)/2; flopCount++;
  }

  /* initializations */
  mpfr_inits(alpha, beta, leftXk, rightXk, wk, leftFx, rightFx, pi2, kh, h, sum, lx, rx, NULL);
  wkVals = (mpfr_t*) malloc(sizeof(mpfr_t)*(nSide+1));
  xkVals = (mpfr_t*) malloc(sizeof(mpfr_t)*(nSide+1));
  for(k=0; k<nSide+1; ++k)
    mpfr_inits(xkVals[k], wkVals[k], NULL);

  // init alpha, beta
  mpfr_set(alpha, b, MPFR_RNDN);
  mpfr_sub(alpha, alpha, a, MPFR_RNDN);
  mpfr_mul_d(alpha, alpha, 0.5, MPFR_RNDN);
  mpfr_set(beta, b, MPFR_RNDN);
  mpfr_add(beta, beta, a, MPFR_RNDN);
  mpfr_mul_d(beta, beta, 0.5, MPFR_RNDN); flopCount += 4;

  /* determine right step size */
  ierr = FindStepSizeERF(prec, nSide, &h); // not including in flop count
  //mpfr_mul_d(h, h, 0.3, MPFR_RNDN);
  //printf("step size: %15.15f\n", mpfr_get_d(h, MPFR_RNDN));
  //printf("step size: %15.15f\n", mpfr_get_d(h, MPFR_RNDN));
  //mpfr_set_d(h, 0.04, MPFR_RNDN);

  /* calculate abscissas, weights */
  ierr = ERFxkwk(nSide+1, h, &xkVals, &wkVals);CHKERRQ(ierr);

  /* center term */
  mpfr_set_d(sum, 0.0, MPFR_RNDN);
  mpfr_set(lx, b, MPFR_RNDN);
  mpfr_add(lx, lx, a, MPFR_RNDN);
  mpfr_mul_d(lx, lx, .5, MPFR_RNDN);
  f(&lx, &sum, ctx);
  mpfr_mul(sum, sum, wkVals[0], MPFR_RNDN);
  mpfr_mul(sum, sum, alpha,     MPFR_RNDN); flopCount += 4;

  for(k=1; k <= nSide; ++k) {
    // adjust xk to [a,b]
    //mpfr_sub_d(lx, xkVals[k], 1.0, MPFR_RNDZ);
    mpfr_mul_d(lx, xkVals[k], -1.0, MPFR_RNDN);
    mpfr_mul(lx, lx, alpha, MPFR_RNDU);
    mpfr_add(lx, lx, beta, MPFR_RNDU);
    //mpfr_d_sub(rx, 1.0, xkVals[k], MPFR_RNDZ);
    mpfr_mul(rx, xkVals[k], alpha, MPFR_RNDD);
    mpfr_add(rx, rx, beta , MPFR_RNDD); flopCount += 6;

    double left = mpfr_get_d(lx, MPFR_RNDN);
    double right = mpfr_get_d(rx, MPFR_RNDN);
    double xk = mpfr_get_d(xkVals[k], MPFR_RNDN);
    //printf("left: %15.15f\n", left);
    //printf("right: %15.15f\n", right);
    //printf("\n");
    /* calculate function values */
    f(&lx, &leftFx , ctx);
    f(&rx, &rightFx, ctx);

    /* update sum */
    mpfr_mul(leftFx, leftFx, wkVals[k], MPFR_RNDN);
    mpfr_mul(rightFx, rightFx, wkVals[k], MPFR_RNDN);
    mpfr_mul(leftFx, leftFx, alpha, MPFR_RNDN);
    mpfr_mul(rightFx, rightFx, alpha, MPFR_RNDN);
    //double l1 = mpfr_get_d(leftFx, MPFR_RNDN);
    //double l2 = mpfr_get_d(rightFx, MPFR_RNDN);
    //printf("leftval: %15.15f\n", l1);
    //printf("rightval: %15.15f\n", l2);
    mpfr_add(sum, sum, leftFx, MPFR_RNDN);
    mpfr_add(sum, sum, rightFx, MPFR_RNDN); flopCount += 6;
    //printf("sum[%d] = %15.15f\n", k, mpfr_get_d(sum, MPFR_RNDN));
    //printf("wk[%d] = %3.3e\n\n", k, mpfr_get_d(wkVals[k], MPFR_RNDN));
  }
  PetscReal prog = mpfr_get_d(sum, MPFR_RNDN);
  //printf("sum: %15.15f\n", prog);

  *integral = prog;

  mpfr_clears(alpha, beta, leftXk, rightXk, wk, leftFx, rightFx, pi2, kh, h, sum, lx, rx, NULL);
  for(k=0; k<nSide+1; ++k)
    mpfr_clears(wkVals[k], xkVals[k], NULL);
  PetscFunctionReturn(0);
}


#include <math.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <mpfr.h>
#include <petsc.h>
#include "ellipsoid.h"
#include "../sphere/sphere.h"
#include "ellSolv.h"

double calcEnp(EllipsoidalSystem *e, Point *point, int n, int p) {
  //if(point->type == 'c')
  //cartesianToEllipsoidal(e, point);
  int signm = 1;
  int signn = 1;
  if(point->x2 < 0)
    signm = -1;
  if(point->x3 < 0)
    signn = -1;
  double eL, eM, eN;
  calcLame(e, n, p, point->x1, signm, signn, &eL);
  calcLame(e, n, p, point->x2, signm, signn, &eM);
  calcLame(e, n, p, point->x3, signm, signn, &eN);

  return eL*eM*eN;
}

double calcGnp(EllipsoidalSystem *e, Point *positions, double *charges, int nCharges, int n, int p)
{
  clock_t start, diff;
  int msec;
  start = clock();
  double normConstant;
  calcNormalization(e, n, p, &normConstant);
  diff = clock() - start;
  msec = diff * 1000 / CLOCKS_PER_SEC;
  //printf("normConstant took %d seconds and %d milliseconds\n", msec/1000, msec%1000);
  //double normConstant = 1.0;
  double sum = 0;
  double Enp;

  for(int k=0; k<nCharges; ++k) {

    Enp = calcEnp(e, positions+k, n, p);

    sum += charges[k] * Enp;
  }

  sum *= (4*PETSC_PI)/((2.0*n+1.0)*normConstant);

  return sum;
}

double calcFnp(EllipsoidalSystem *e, Point *point, int n, int p)
{
  int signm = 1;
  int signn = 1;
  if(point->x2 < 0)
    signm = -1;
  if(point->x3 < 0)
    signn = -1;

  double enpval = calcEnp(e, point, n, p);
  double ival;
  calcI(e, n, p, point->x1, signm, signn, &ival);
  //printf("ENP: %15.15f\nI: %15.15f\n", enpval, ival);
  return (2*n + 1) * enpval * ival;
}

void calcBnpAndCnpFromGnp(Problem *problem, int n, int p, double Gnp, double *Bnp, double *Cnp)
{
  EllipsoidalSystem *e = problem->e;
  double Ea;
  calcLame(e, n, p, e->a, 1, 1, &Ea);
  double Ia;
  calcI(problem->e, n, p, e->a, 1, 1, &Ia);
  double Fa = (2*n + 1) * Ea * Ia;
  double EaDer;
  calcLameDerivative(e, n, p, e->a, 1, 1, &EaDer);
  double IaDer;
  calcIDerivative(e, n, p, e->a, 1, 1, &IaDer);
  double FaDer = (2*n+1) * (Ea*IaDer + EaDer*Ia); //chain rule?
  double e1 = problem->e1;
  double e2 = problem->e2;

  //calc Bnp
  double first = (Fa/Ea)*(e1 - e2)/(e1*e2);
  first /= (1-(e1/e2)*((EaDer*Fa)/(FaDer*Ea)));
  *Bnp = first*Gnp;

  //calc Cnp
  first = (e1/e2)*(EaDer/FaDer)*(*Bnp);
  first += (Gnp/e2);
  *Cnp = first;
}

double calcCoulomb(Problem *problem, int maxDeg, Point *r)
{
  //FILE *fp = fopen("fnpnorm.txt", "w");
  double Gnp, Fnp, Enp;
  double sum = 0;
  int count = 0;
  for(int n=0; n<=maxDeg; ++n) {
    for(int p=0; p < 2*n+1; ++p) {
      clock_t start = clock(), diff;
      Gnp = calcGnp(problem->e, problem->positions, problem->charges, problem->nCharges, n, p);
      diff = clock() - start;
      int msec = diff * 1000 / CLOCKS_PER_SEC;
      //printf("Gnp took %d seconds %d milliseconds\n", msec/1000, msec%1000);
      start = clock();
      Fnp = calcFnp(problem->e, r, n, p);
      diff = clock() - start;
      msec = diff * 1000 / CLOCKS_PER_SEC;
      //printf("Fnp took %d seconds %d milliseconds\n\n", msec/1000, msec%1000);
      //fprintf(fp, "%8.8e %8.8e\n", (double)count, fabs(Fnp));

      sum += (Gnp*Fnp)/problem->e1;

      count++;
    }
    printf("n: %d\n", n);
    printf("Gnp: %16.16f\n", Gnp);
    printf("Fnp: %16.16f\n", Fnp);
    //printf("enp: %16.16f\n", Enp);
    printf("sum: %16.16f\n", sum);

  }
  //fclose(fp);
  return sum;
}

double calcCoulombEllipsoidalGrid(Problem *problem, int maxDeg, Point *r, int nPoints, double *solution) {

  EllipsoidalSystem *e = problem->e;
  double Gnp, Bnp, Fnp, Enp, Cnp;
  for(int i=0; i<nPoints; ++i)
    solution[i] = 0;
  int count = 0;
  double *fnpvals = (double*) malloc(sizeof(double)*nPoints);

  for(int n=0; n<=maxDeg; ++n) {

    for(int p=0; p < 2*n+1; ++p) {
      Gnp = calcGnp(problem->e, problem->positions, problem->charges, problem->nCharges, n, p);
      calcBnpAndCnpFromGnp(problem, n, p, Gnp, &Bnp, &Cnp);

      for(int k=0; k<nPoints; ++k) {
	//if on exterior
	if(fabs(r[k].x1) >= e->a) {
	  Fnp = calcFnp(problem->e, r+k, n, p);
	  solution[k] += Cnp*Fnp;
	}
	else {
	  Enp = calcEnp(problem->e, r+k, n, p);
	  solution[k] += Bnp*Enp;
	  if(n==3 && p==2) {
	    printf("do we get here2\n");
	    printf("(%d,%d)\n", n, p);
	    printf("Enp: %15.15f\n", Enp);
	    printf("Bnp: %15.15f\n", Bnp);
	    printf("\n");
	  }
	}
      }

      count++;
    }
    //printf("n: %d\n", n);
    //printf("Gnp: %16.16f\n", Gnp);
    //printf("Cnp: %16.16f\n", Cnp);
    //printf("Fnp: %16.16f\n", Fnp);
    //printf("enp: %16.16f\n", Enp);
    //printf("sum: %16.16f\n", solution[0]);

  }
  //fclose(fp);
  return solution[0];
}


#include <math.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>

typedef struct ornsteinuhlenbeckprocess_{
  int steps;
  double dt;
  double sigma;
  double *x;
  gsl_rng *rng;
} OrnsteinUhlenbeckProcess;

void OrnsteinUhlenbeckProcess_run(OrnsteinUhlenbeckProcess *self) {
  int i;
  double sgm = self->sigma * sqrt(self->dt);
  for (i = 1; i < self->steps; ++i){
    self->x[i] = (1 - self->dt) * self->x[i-1] \
      + gsl_ran_gaussian_ziggurat(self->rng, sgm);
  }
}


#ifndef CONVOLUTION_H
#define CONVOLUTION_H

#include <cstddef>

#include <cstring>

#include <iostream>

extern "C" {
  #include <cblas.h>
}

using namespace std;

template<typename T>
constexpr T relu(T x) {
  return (x > 0) ? x : 0;
}

template<typename T>
void mul_mat(T *A, double *B, double *C, size_t m, size_t k, size_t n) {
  for(size_t r = 0; r < m; r++) {
    for(size_t c = 0; c < n; c++) {
      double x = 0.0;
      for(size_t z = 0; z < k; z++) {
        x += A[r * k + z] * B[z * n + c];
      }
      C[r * n + c] = x;
    }
  }
}

template<size_t in_maps, size_t in_height, size_t in_width,
         size_t conv_height, size_t conv_width,
         size_t vert_stride, size_t horiz_stride,
         size_t out_maps>
struct ConvolutionLayer {
  static constexpr size_t conv_size = conv_height * conv_width;
  static constexpr size_t in_map_size = in_height * in_width;
  static constexpr size_t inputs_no = in_maps * in_height * in_width;
  static constexpr size_t parameters_no = out_maps * (conv_width * conv_height * in_maps + 1);
  static constexpr size_t bias_idx = conv_width * conv_height * in_maps;
  static constexpr size_t out_height = (in_height - conv_height) / vert_stride +1;
  static constexpr size_t out_width = (in_width - conv_width) / horiz_stride + 1;
  static constexpr size_t out_map_size = out_height * out_width;
  static constexpr size_t internal_no = out_maps * out_height * out_width;
  static constexpr size_t outputs_no = out_maps * out_height * out_width;

  static constexpr size_t p_idx(size_t out_map, size_t in_map, size_t conv_r, size_t conv_c) {
    return out_map * (in_maps * conv_height * conv_width + 1) + in_map * (conv_height * conv_width)
      + conv_r * conv_width + conv_c;
  }

  static void forward(double* inputs, double * parameters, double* outputs) {
    for (size_t j = 0; j < out_maps; j++) {
      double *pj = &(parameters[j*(conv_height * conv_width * in_maps + 1)]);

      for (size_t out_r = 0; out_r < out_height; out_r++) {
        for (size_t out_c = 0; out_c < out_width; out_c++) {
          const size_t out_idx = j * (out_map_size) + out_r * out_width + out_c;
          double a = 0;
          for (size_t i = 0; i < in_maps; i++) {
            const size_t i_idx = i * conv_size;
            const size_t i2_idx = i * in_map_size;
            for (size_t conv_r = 0; conv_r < conv_height; conv_r++) {
              for (size_t conv_c = 0; conv_c < conv_width; conv_c++) {
                size_t p2_idx =  i_idx + conv_r * conv_width + conv_c;
                size_t in_idx =
                  i2_idx + (out_r * vert_stride + conv_r) * in_width + out_c * horiz_stride + conv_c;
                a += pj[p2_idx] * inputs[in_idx];
              }
            }
          }
          a += pj[bias_idx];
          outputs[out_idx] = relu(a);
        }
      }
    }
  }
};

template<size_t in_maps, size_t in_height, size_t in_width,
         size_t conv_height, size_t conv_width,
         size_t vert_stride, size_t horiz_stride,
         size_t out_maps>
struct ConvolutionLayer2 {
  static constexpr size_t in_map_size = in_height * in_width;
  static constexpr size_t inputs_no = in_maps * in_map_size;
  static constexpr size_t out_height = (in_height - conv_height) / vert_stride +1;
  static constexpr size_t out_width = (in_width - conv_width) / horiz_stride + 1;
  static constexpr size_t out_map_size = out_height * out_width;
  static constexpr size_t internal_no = out_maps * out_map_size;
  static constexpr size_t outputs_no = out_maps * out_map_size;
  static constexpr size_t conv_size = conv_height * conv_width;
  static constexpr size_t parameters_no = out_maps * (in_maps * conv_size + 1);

  static constexpr size_t p_idx(size_t out_map, size_t in_map, size_t conv_r, size_t conv_c) {
    return out_map * (in_maps * conv_height * conv_width + 1) + in_map * (conv_height * conv_width)
      + conv_r * conv_width + conv_c;
  }

  static void forward(double* inputs, double *parameters, double* outputs) {
    double* _in = (double*) malloc(sizeof(double) * in_maps * conv_size * out_map_size);

    for (size_t out_r = 0; out_r < out_height; out_r++) {
      for (size_t out_c = 0; out_c < out_width; out_c++) {
        for (size_t i = 0; i < in_maps; i++) {
          for (size_t conv_r = 0; conv_r < conv_height; conv_r++) {
            for (size_t conv_c = 0; conv_c < conv_width; conv_c++) {
              size_t in_idx =
                i * in_map_size +
                (out_r * vert_stride + conv_r) * in_width +
                out_c * horiz_stride + conv_c;
              size_t row_idx = i * conv_size + conv_r * conv_width + conv_c;
              size_t _in_idx = row_idx * out_map_size + out_r * out_width + out_c;
              _in[_in_idx] = inputs[in_idx];
            }
          }
        }
      }
    }

    for (size_t j = 0; j < out_maps;j++) {
      double bias = parameters[(j+1) * (in_maps * conv_size + 1) - 1];
      for (size_t i = 0; i < out_map_size; i++) {
        outputs[j * out_map_size + i] = bias;
      }
    }

    cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans,
                out_maps, out_map_size, in_maps * conv_size,
                1.0, parameters, in_maps * conv_size + 1,
                _in, out_map_size,
                1.0, outputs, out_map_size);

    for(size_t j = 0; j < out_maps * out_map_size; j++) {
      outputs[j] = relu(outputs[j]);
    }
  }
};

#endif


#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_spline.h>

#define N 8

int
main (void)
{
  int i;
  double xi, yi;
  double x[N] = { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0 };
  double y[N] = { 0.0, 0.0, 2.0, 0.0,-1.0,-2.0,-1.0, 0.0 };

  printf ("#m=0,S=2\n");

  for (i = 0; i < N; i++)
    {
      printf ("%g %g\n", x[i], y[i]);
    }

  printf ("#m=1,S=0\n");

  {
    gsl_interp_accel *acc = gsl_interp_accel_alloc ();
    gsl_spline *spline = gsl_spline_alloc (gsl_interp_akima, N);
    gsl_spline_init (spline, x, y, N);

    for (xi = x[0]; xi < x[N-1]; xi += 0.01)
      {
        double yi = gsl_spline_eval (spline, xi, acc);
        printf ("%g %g\n", xi, yi);
      }
    gsl_spline_free (spline);
    gsl_interp_accel_free(acc);
  }
}


#include "ridc.h"
#include <stdlib.h>
#include <stdio.h>
#include <cmath>
#include <gsl/gsl_linalg.h>

#ifndef _BRUSSELATOR_H_
#define _BRUSSELATOR_H_

class Brusselator_GSL : public ODE {
public:
  Brusselator_GSL(int my_neq, int my_nt, double my_ti, double my_tf, double my_dt) {
    neq = my_neq;
    nt = my_nt;
    ti = my_ti;
    tf = my_tf;
    dt = my_dt;
  }

  void rhs(double t, double *u, double *f) {
    /** user implemented rhs function, u'=rhs(t,u)
       @return (by reference) f: rhs(t,u)
       @param t current time step
       @param u solution u at time t
       @param f rhs(t,u)
    */
    double A = 1.0;
    double B = 3.0;
    double alpha = 0.02;
    double dx = 1.0/(neq+1);
    int Nx = neq/2;

    f[0] = A+u[0]*u[0]*u[Nx] -(B+1.0)*u[0] +alpha/dx/dx*(u[1]-2*u[0]+1.0);
    f[Nx-1] = A+u[Nx-1]*u[Nx-1]*u[2*Nx-1] -(B+1.0)*u[Nx-1]
      + alpha/dx/dx*(1.0-2*u[Nx-1]+u[Nx-2]);

    f[Nx] = B*u[0]- u[0]*u[0]*u[Nx] + alpha/dx/dx*(u[Nx+1]-2*u[Nx]+3.0);
    f[2*Nx-1] = B*u[Nx-1]- u[Nx-1]*u[Nx-1]*u[2*Nx-1]
      + alpha/dx/dx*(3.0-2*u[2*Nx-1]+u[2*Nx-2]);

    for (int i=1; i<Nx-1; i++) {
      f[i] = A + u[i]*u[i]*u[Nx+i] -(B+1.0)*u[i] +alpha/dx/dx*(u[i+1]-2*u[i]+u[i-1]);
      f[Nx+i] = B*u[i]- u[i]*u[i]*u[Nx+i] + alpha/dx/dx*(u[Nx+i+1]-2*u[Nx+i]+u[Nx+i-1]);
    }
  }

  void step(double t, double * u, double * unew) {
    /** user implemented step function, for advancing the solution from t to t+dt
       @return (by reference) unew: solution at time t+dt
       @param t current time step
       @param u solution u at time t
       @param unew solution at time t+dt
    */
    double tnew = t + dt;

    double NEWTON_TOL = 1.0e-14;
    int NEWTON_MAXSTEP = 1000;

    double * J;
    double * stepsize;
    double * uguess;

    stepsize = new double[neq];
    uguess = new double[neq];

    J = new double[neq*neq];

    // GSL
    gsl_matrix_view m;
    gsl_vector_view b;
    gsl_vector *x = gsl_vector_alloc(neq);
    gsl_permutation *p = gsl_permutation_alloc(neq);

    // set initial condition
    for (int i=0; i<neq; i++) {
      uguess[i] = u[i];
    }

    double maxstepsize;

    int counter=0;
    int * pivot = new int[neq];

    while (1) {

      // create rhs
      newt(tnew,u,uguess,stepsize);

      // compute numerical Jacobian
      jac(tnew,uguess,J);

      m = gsl_matrix_view_array(J,neq,neq);
      b = gsl_vector_view_array(stepsize,neq);

      int s; // presumably, this is some flag info

      gsl_linalg_LU_decomp (&m.matrix,p,&s);
      gsl_linalg_LU_solve (&m.matrix,p,&b.vector, x);

      // check for convergence
      maxstepsize = 0.0;
      for (int i = 0; i<neq; i++) {
	uguess[i] = uguess[i] - x->data[i];
	if (std::abs(stepsize[i])>maxstepsize) {
	  maxstepsize = std::abs(x->data[i]);
	}
      }

      // if update sufficiently small enough
      if ( maxstepsize < NEWTON_TOL) {
	break;
      }

      counter++;
      //error if too many steps
      if (counter > NEWTON_MAXSTEP) {
	fprintf(stderr,"max Newton iterations reached\n");
	exit(42);
      }
    } // end Newton iteration

    for (int i=0; i<neq; i++) {
      unew[i] = uguess[i];
    }

    delete [] pivot;
    delete [] uguess;
    delete [] stepsize;
    delete [] J;

    gsl_permutation_free (p);
    gsl_vector_free (x);
  }

 private:
  void newt(double t, double *uprev, double *uguess,
	    double *g){
    /** Helper function to compute the next Newton step
       for solving a system of equations

       @return (by reference) g how far from zero we are
       @param t current time step
       @param uguess current solution guess
       @param uprev solution at previous time step
       @param g how far from zero we are, returned by reference
    */
    rhs(t,uguess,g);
    for (int i =0; i<neq; i++) {
      g[i] = uguess[i]-uprev[i]-dt*g[i];
    }
  }

  void jac(double t, double *u, double *J){
    /** Helper function to the Jacobian matrix (using finite differences)
       for advancing the solution from time t(n) to t(n+1) using an
       implicit Euler step on a system of equations

       @return (by reference) J the Jacobian for the Newton step
       @param t current time step
       @param u function value at the current time step
       @param J Jacobian, returned by reference
    */
    double d = 1e-5; // numerical Jacobian approximation
    double *u1;
    double *f1;
    double *f;

    // need to allocate memory
    u1 = new double[neq];
    f1 = new double[neq];
    f = new double[neq];

    // note, instead of using newt, use rhs for efficiency
    rhs(t,u,f);

    for (int i = 0; i<neq; i++) {
      for (int j = 0; j<neq; j++) {
	u1[j] = u[j];
      }
      u1[i] = u1[i] + d;

      rhs(t,u1,f1);

      for (int j = 0; j<neq; j++) {
	J[j*neq+i] = -dt*(f1[j]-f[j])/d;
      }
      J[i*neq+i] = 1.0+J[i*neq+i];
    }

    // need to delete memory
    delete [] u1;
    delete [] f1;
    delete [] f;
  }

};

#endif // _BRUSSELATOR_H_


#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <mpfr.h>
#include <petsc.h>

#include "../ellipsoid/ellipsoid.h"
#include "../ellipsoid/ellSolv.h"
#include "../sphere/sphere.h"
#include "../sphere/sphSolv.h"
#include "../constants.h"

#undef __FUNCT__
#define __FUNCT__ "CoulombExact"
PetscErrorCode CoulombExact(PetscReal eps1, Vec srcXYZ, Vec srcMag, Vec tarXYZ, Vec sol)
{
  PetscErrorCode ierr;
  PetscReal srcX, srcY, srcZ;
  PetscReal tarX, tarY, tarZ;
  PetscReal dx2, dy2, dz2;
  PetscReal val, mag;
  const PetscScalar *srcXYZArray;
  const PetscScalar *srcMagArray;
  const PetscScalar *tarXYZArray;
  PetscScalar *solArray;
  PetscInt nSrc, nTar;
  PetscInt i, k;
  PetscFunctionBeginUser;

  ierr = VecGetSize(srcMag, &nSrc);CHKERRQ(ierr);
  ierr = VecGetSize(sol, &nTar);CHKERRQ(ierr);

  ierr = VecGetArrayRead(srcXYZ, &srcXYZArray);CHKERRQ(ierr);
  ierr = VecGetArrayRead(tarXYZ, &tarXYZArray);CHKERRQ(ierr);
  ierr = VecGetArrayRead(srcMag, &srcMagArray);CHKERRQ(ierr);
  ierr = VecGetArray    (sol   , &solArray   );CHKERRQ(ierr);
  for(i=0; i<nTar; ++i) {
    /* get target X,Y,Z */
    tarX = tarXYZArray[3*i+0];
    tarY = tarXYZArray[3*i+1];
    tarZ = tarXYZArray[3*i+2];
    for(k=0; k<nSrc; ++k) {
      /* get source X,Y,Z */
      srcX = srcXYZArray[3*k+0];
      srcY = srcXYZArray[3*k+1];
      srcZ = srcXYZArray[3*k+2];
      mag  = srcMagArray[k];

      dx2 = (tarX-srcX)*(tarX-srcX);
      dy2 = (tarY-srcY)*(tarY-srcY);
      dz2 = (tarZ-srcZ)*(tarZ-srcZ);

      val = mag/(eps1*PetscSqrtReal(dx2 + dy2 + dz2));
      solArray[i] += val;
    }
  }
  ierr = VecRestoreArrayRead(srcXYZ, &srcXYZArray);CHKERRQ(ierr);
  ierr = VecRestoreArrayRead(tarXYZ, &tarXYZArray);CHKERRQ(ierr);
  ierr = VecRestoreArrayRead(srcMag, &srcMagArray);CHKERRQ(ierr);
  ierr = VecRestoreArray    (sol   , &solArray   );CHKERRQ(ierr);

  PetscFunctionReturn(0);
}


/* integration/jacobi.c
 *
 * Copyright (C) 2017 Konrad Griessinger, Patrick Alken
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/*
 * The code in this module is based on IQPACK, specifically the LGPL
 * implementation found in HERMITE_RULE:
 * https://people.sc.fsu.edu/~jburkardt/c_src/hermite_rule/hermite_rule.html
 */

#include <stdio.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_integration.h>
#include <gsl/gsl_sf_gamma.h>

static int
jacobi_check(const size_t n, const gsl_integration_fixed_params * params)
{
  (void) n;

  if (fabs(params->b - params->a) <= GSL_DBL_EPSILON)
    {
      GSL_ERROR("|b - a| too small", GSL_EDOM);
    }
  else if (params->a >= params->b)
    {
      GSL_ERROR("lower integration limit must be smaller than upper limit", GSL_EDOM);
    }
  else if (params->alpha <= -1.0 || params->beta <= -1.0)
    {
      GSL_ERROR("alpha and beta must be > -1", GSL_EDOM);
    }
  else
    {
      return GSL_SUCCESS;
    }
}

static int
jacobi_init(const size_t n, double * diag, double * subdiag, gsl_integration_fixed_params * params)
{
  const double absum = params->beta + params->alpha;
  const double abdiff = params->beta - params->alpha;
  const double a2b2 = absum * abdiff; /* beta^2 - alpha^2 */
  size_t i;

  /* construct the diagonal and subdiagonal elements of Jacobi matrix */
  diag[0] = abdiff/(absum + 2.0);
  subdiag[0] = 2.0*sqrt((params->alpha + 1.0)*(params->beta + 1.0)/(absum + 3.0))/(absum + 2.0);
  for (i = 1; i < n; i++)
    {
      diag[i] = a2b2 / ( (absum + 2.0*i) * (absum + 2.0*i + 2.0) );
      subdiag[i] = sqrt ( 4.0*(i + 1.0) * (params->alpha + i + 1.0) * (params->beta + i + 1.0) * (absum + i + 1.0) / ( pow((absum + 2.0*i + 2.0), 2.0) - 1.0 ) ) / ( absum + 2.0*i + 2.0 );
    }

  params->zemu = pow(2.0, absum + 1.0) * gsl_sf_gamma(params->alpha + 1.0) * gsl_sf_gamma(params->beta + 1.0) / gsl_sf_gamma(absum + 2.0);
  params->shft = 0.5*(params->b + params->a);
  params->slp = 0.5*(params->b - params->a);
  params->al = params->alpha;
  params->be = params->beta;

  return GSL_SUCCESS;
}

static const gsl_integration_fixed_type jacobi_type =
{
  jacobi_check,
  jacobi_init
};

const gsl_integration_fixed_type *gsl_integration_fixed_jacobi = &jacobi_type;


#include <stdlib.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_monte.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_complex.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_complex_math.h>
#include <gsl/gsl_cblas.h>
#include <gsl/gsl_blas.h>

#ifndef gauge_config
#include "config.h"
#define gauge_config 1

#define sq(x) ((x)*(x))
#define cb(x) ((x)*(x)*(x))
#endif

// SU(3) Matrix
// only the first two rows carries useful information (Lang p.80)
// note that the first two rows must be orthonormal
// use su3_normalize() for this
typedef struct{
  gsl_complex a1;
  gsl_complex a2;
  gsl_complex a3;
  gsl_complex b1;
  gsl_complex b2;
  gsl_complex b3;
} su3_matrix;

// SU(3) matrix methods --------------------------------------

// print matrix
void su3_print(const su3_matrix A){
  printf("%f + i*%f \t", GSL_REAL(A.a1), GSL_IMAG(A.a1));
  printf("%f + i*%f \t", GSL_REAL(A.a2), GSL_IMAG(A.a2));
  printf("%f + i*%f \n", GSL_REAL(A.a3), GSL_IMAG(A.a3));
  printf("%f + i*%f \t", GSL_REAL(A.b1), GSL_IMAG(A.b1));
  printf("%f + i*%f \t", GSL_REAL(A.b2), GSL_IMAG(A.b2));
  printf("%f + i*%f \n", GSL_REAL(A.b3), GSL_IMAG(A.b3));
}

// the identity matrix
su3_matrix su3_unit(){
  su3_matrix u;
  u.a1=GSL_COMPLEX_ONE;
  u.a2=GSL_COMPLEX_ZERO;
  u.a3=GSL_COMPLEX_ZERO;
  u.b1=GSL_COMPLEX_ZERO;
  u.b2=GSL_COMPLEX_ONE;
  u.b3=GSL_COMPLEX_ZERO;
  return u;
}

// normalize matrix
su3_matrix su3_norm(const su3_matrix A){
  su3_matrix result;
  // normalize first row
  double norm = sqrt(gsl_complex_abs2(A.a1)+gsl_complex_abs2(A.a2)+gsl_complex_abs2(A.a3));
  result.a1 = gsl_complex_div(A.a1,gsl_complex_rect(norm,0));
  result.a2 = gsl_complex_div(A.a2,gsl_complex_rect(norm,0));
  result.a3 = gsl_complex_div(A.a3,gsl_complex_rect(norm,0));

  // orthogonize second row
  gsl_complex dprod = gsl_complex_add(gsl_complex_add(gsl_complex_mul(A.b1,gsl_complex_conjugate(result.a1)),
                      gsl_complex_mul(A.b2,gsl_complex_conjugate(result.a2))),
                      gsl_complex_mul(A.b3,gsl_complex_conjugate(result.a3)));
  result.b1 = gsl_complex_sub(A.b1,gsl_complex_mul(result.a1,dprod));
  result.b2 = gsl_complex_sub(A.b2,gsl_complex_mul(result.a2,dprod));
  result.b3 = gsl_complex_sub(A.b3,gsl_complex_mul(result.a3,dprod));

  // normalize second row
  norm = sqrt(gsl_complex_abs2(result.b1)+gsl_complex_abs2(result.b2)+gsl_complex_abs2(result.b3));
  result.b1 = gsl_complex_div(result.b1,gsl_complex_rect(norm,0));
  result.b2 = gsl_complex_div(result.b2,gsl_complex_rect(norm,0));
  result.b3 = gsl_complex_div(result.b3,gsl_complex_rect(norm,0));
  return result;
}

// multiply matrices
su3_matrix su3_mul(const su3_matrix A, const su3_matrix B){
  su3_matrix result;
  gsl_complex w1,w2,w3;

  // Third row of B as a cross product (c.f. Lang p.80)
  w1 = gsl_complex_sub( gsl_complex_mul(gsl_complex_conjugate(B.a2),gsl_complex_conjugate(B.b3)), gsl_complex_mul(gsl_complex_conjugate(B.a3),gsl_complex_conjugate(B.b2)) );
  w2 = gsl_complex_sub( gsl_complex_mul(gsl_complex_conjugate(B.a3),gsl_complex_conjugate(B.b1)), gsl_complex_mul(gsl_complex_conjugate(B.a1),gsl_complex_conjugate(B.b3)) );
  w3 = gsl_complex_sub( gsl_complex_mul(gsl_complex_conjugate(B.a1),gsl_complex_conjugate(B.b2)), gsl_complex_mul(gsl_complex_conjugate(B.a2),gsl_complex_conjugate(B.b1)) );

  result.a1 = gsl_complex_add(gsl_complex_add(gsl_complex_mul(A.a1,B.a1),gsl_complex_mul(A.a2,B.b1)), gsl_complex_mul(A.a3,w1));
  result.a2 = gsl_complex_add(gsl_complex_add(gsl_complex_mul(A.a1,B.a2),gsl_complex_mul(A.a2,B.b2)), gsl_complex_mul(A.a3,w2));
  result.a3 = gsl_complex_add(gsl_complex_add(gsl_complex_mul(A.a1,B.a3),gsl_complex_mul(A.a2,B.b3)), gsl_complex_mul(A.a3,w3));

  result.b1 = gsl_complex_add(gsl_complex_add(gsl_complex_mul(A.b1,B.a1),gsl_complex_mul(A.b2,B.b1)), gsl_complex_mul(A.b3,w1));
  result.b2 = gsl_complex_add(gsl_complex_add(gsl_complex_mul(A.b1,B.a2),gsl_complex_mul(A.b2,B.b2)), gsl_complex_mul(A.b3,w2));
  result.b3 = gsl_complex_add(gsl_complex_add(gsl_complex_mul(A.b1,B.a3),gsl_complex_mul(A.b2,B.b3)), gsl_complex_mul(A.b3,w3));

  return result;
}

// inverse matrix
su3_matrix su3_inv(const su3_matrix A){
  su3_matrix result;
  gsl_complex w1,w2,w3;

  // Third row of B as a cross product (c.f. Lang p.80)
  w1 = gsl_complex_sub( gsl_complex_mul(gsl_complex_conjugate(A.a2),gsl_complex_conjugate(A.b3)), gsl_complex_mul(gsl_complex_conjugate(A.a3),gsl_complex_conjugate(A.b2)) );
  w2 = gsl_complex_sub( gsl_complex_mul(gsl_complex_conjugate(A.a3),gsl_complex_conjugate(A.b1)), gsl_complex_mul(gsl_complex_conjugate(A.a1),gsl_complex_conjugate(A.b3)) );
  w3 = gsl_complex_sub( gsl_complex_mul(gsl_complex_conjugate(A.a1),gsl_complex_conjugate(A.b2)), gsl_complex_mul(gsl_complex_conjugate(A.a2),gsl_complex_conjugate(A.b1)) );

  result.a1 = gsl_complex_sub( gsl_complex_mul(A.b2,w3), gsl_complex_mul( A.b3,w2) );
  result.a2 = gsl_complex_sub( gsl_complex_mul(A.a3,w2), gsl_complex_mul( A.a2,w3) );
  result.a3 = gsl_complex_sub( gsl_complex_mul(A.a2,A.b3), gsl_complex_mul( A.a3,A.b2) );

  result.b1 = gsl_complex_sub( gsl_complex_mul(A.b3,w1), gsl_complex_mul( A.b1,w3) );
  result.b2 = gsl_complex_sub( gsl_complex_mul(A.a1,w3), gsl_complex_mul( A.a3,w1) );
  result.b3 = gsl_complex_sub( gsl_complex_mul(A.a3,A.b1), gsl_complex_mul( A.a1,A.b3) );

  return result;
}

// generate random matrix
su3_matrix su3_rand(const gsl_rng* r){
  su3_matrix result;
  result.a1 = gsl_complex_rect(1,eps*(gsl_rng_uniform(r)*2-1));
  result.a2 = gsl_complex_rect(0,eps*(gsl_rng_uniform(r)*2-1));
  result.a3 = gsl_complex_rect(0,eps*(gsl_rng_uniform(r)*2-1));

  result.b1 = gsl_complex_rect(0,eps*(gsl_rng_uniform(r)*2-1));
  result.b2 = gsl_complex_rect(1,eps*(gsl_rng_uniform(r)*2-1));
  result.b3 = gsl_complex_rect(0,eps*(gsl_rng_uniform(r)*2-1));
  return su3_norm(result);
}

double su3_trace(const su3_matrix A){
  gsl_complex c3;
  c3 = gsl_complex_sub( gsl_complex_mul(gsl_complex_conjugate(A.a1),gsl_complex_conjugate(A.b2)), gsl_complex_mul(gsl_complex_conjugate(A.a2),gsl_complex_conjugate(A.b1)) );

  return GSL_REAL(gsl_complex_add(gsl_complex_add(A.a1,A.b2),c3));
}

// end of SU(3) matrix methods ---------------------------------------------