/*
 * ---------------------------------------------------------------------
 * Copyright (C) 2012, 2014 Tino Kluge (ttk448 at gmail.com)
 *
 *  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 2
 *  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, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------
 */

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <boost/multi_array.hpp>
#include <fftw3.h>
#include "dvs_reconstruction/poisson_solver/laplace.h"

#ifdef TIME_REPORT
#include <time.h>
#include <sys/time.h>
namespace pde
{

double stoptime(void)
{
   struct timeval t;
   gettimeofday(&t,NULL);
   return (double) t.tv_sec + t.tv_usec/1000000.0;
}
}
#endif // TIME_REPORT

namespace
{

// convenience function: square
inline double sqr(double x)
{
   return x*x;
}

} // namespace

// 2d-array utility functions
// --------------------------
namespace arr
{

// helper function: [0,1] uniform random generator
double runif()
{
   return (double) rand()/(RAND_MAX+1.0);
}
// fill vector with random values
void runif(std::vector<double>& X)
{
   size_t n=X.size();
   for(size_t i=0; i<n; i++) {
      X[i]=runif();
   }
}
// fill 2d-array with random values
void runif(boost::multi_array<double,2>& X)
{
   size_t n1=X.shape()[0];
   size_t n2=X.shape()[1];
   for(size_t i=0; i<n1; i++) {
      for(size_t j=0; j<n2; j++) {
         X[i][j]=runif();
      }
   }
}

// print the matrix
void print(const boost::multi_array<double,2>& X)
{
   size_t n1=X.shape()[0];
   size_t n2=X.shape()[1];
   for(size_t i=0; i<n1; i++) {
      for(size_t j=0; j<n2; j++) {
         printf("%9.5f ", X[i][j]);
      }
      printf("\n");
   }
}

// L^2 difference between two vectors
double diff(const std::vector<double>& X,
            const std::vector<double>& Y,
            bool allow_shift)
{

   assert(X.size()==Y.size());
   size_t n=X.size();

   double c=0.0;
   if(allow_shift) {
      c=X[1]-Y[1];
   }
   double sum=0.0;
   for(size_t i=0; i<n; i++) {
      sum+=sqr(X[i]-Y[i]-c);
   }
   return sqrt(sum);
}

// L^2 difference between vector and a constant
double diff(const std::vector<double>& X, double c)
{
   size_t n=X.size();
   double sum=0.0;
   for(size_t i=0; i<n; i++) {
      sum+=sqr(X[i]-c);
   }
   return sqrt(sum);
}

// L^2 difference between two 2d-arrays
// allow_shift: removes a constant shift between 2 matrices,
//    i.e.  comparing X[][] and Y[][] + (X[1][1]-Y[1][1])
// ignore_corner: ignores all 4 corners, X[0][0], ...
double diff(const boost::multi_array<double,2>& X,
            const boost::multi_array<double,2>& Y,
            bool allow_shift, bool ignore_corner)
{

   size_t n1=X.shape()[0];
   size_t n2=X.shape()[1];

   assert(Y.shape()[0]==n1 && n1>1);
   assert(Y.shape()[1]==n2 && n2>1);

   double c=0.0;
   if(allow_shift) {
      c=X[1][1]-Y[1][1];
   }
   double sum=0.0;
   for(size_t i=0; i<n1; i++) {
      for(size_t j=0; j<n2; j++) {
         if( !(ignore_corner==true && (i==0 || i==n1-1) && (j==0 || j==n2-1)) )
            sum+=sqr(X[i][j]-Y[i][j]-c);
      }
   }
   return sqrt(sum);
}

} // namespace arr

namespace pde
{

// sets the number of threads for the fftw solver
int fftw_threads(int n)
{
   int ret=fftw_init_threads();
   fftw_plan_with_nthreads(n);
   return ret;
}
// cleans up memory used by fftw for threads
void fftw_clean()
{
   fftw_cleanup();
   fftw_cleanup_threads();
}

// sets boundary vectors to a uniform value
void set_boundary_to_uniform(
   std::vector<double>& bd1a, std::vector<double>& bd1b,
   std::vector<double>& bd2a, std::vector<double>& bd2b,
   double value, size_t n1, size_t n2)
{

   bd1a.resize(n2);
   bd1b.resize(n2);
   bd2a.resize(n1);
   bd2b.resize(n1);
   for(size_t i=0; i<n2; i++) {
      bd1a[i]=value;
      bd1b[i]=value;
   }
   for(size_t i=0; i<n1; i++) {
      bd2a[i]=value;
      bd2b[i]=value;
   }
}

// saves the 4 boundaries/edges of a 2-d array as individual vectors
// note, the 4 corners/vertices are not saved and the resulting
// vectors are of size n-2
void get_boundary(std::vector<double>& bd1a, std::vector<double>& bd1b,
                  std::vector<double>& bd2a, std::vector<double>& bd2b,
                  const boost::multi_array<double,2>& X,
                  double h1, double h2, types::boundary boundary)
{
   size_t n1=X.shape()[0];
   size_t n2=X.shape()[1];
   assert(n1>2 && n2>2);

   // allocating memory for boundary vectors
   bd1a.resize(n2-2);
   bd1b.resize(n2-2);
   bd2a.resize(n1-2);
   bd2b.resize(n1-2);

   // reading boundary values
   if(boundary==types::Dirichlet) {
      for(size_t i=1; i<n2-1; i++) {
         bd1a[i-1] = X[0][i];
         bd1b[i-1] = X[n1-1][i];
      }
      for(size_t i=1; i<n1-1; i++) {
         bd2a[i-1] = X[i][0];
         bd2b[i-1] = X[i][n2-1];
      }
   } else if(boundary==types::Neumann) {
      for(size_t i=1; i<n2-1; i++) {
         bd1a[i-1] = (X[0][i]-X[2][i]) / (2.0*h1);
         bd1b[i-1] = (X[n1-1][i]-X[n1-3][i]) / (2.0*h1);
      }
      for(size_t i=1; i<n1-1; i++) {
         bd2a[i-1] = (X[i][0]-X[i][2]) / (2.0*h2);
         bd2b[i-1] = (X[i][n2-1]-X[i][n2-3]) / (2.0*h2);
      }
   } else {
      assert(false);
   }
}

// set boundary of matrix X according to conditions
// can either add boundaries to the array (resize), or overwrite the
// current boundary values
// boundary vectors shall not contain the 4 corners (vertices), ie need to
// be of size n-2 of the resulting array
void set_boundary(boost::multi_array<double,2>& X, double h1, double h2,
                  const std::vector<double>& bd1a, const std::vector<double>& bd1b,
                  const std::vector<double>& bd2a, const std::vector<double>& bd2b,
                  types::boundary boundary, bool add)
{

   size_t n1=X.shape()[0];
   size_t n2=X.shape()[1];
   assert(n1>0 && n2>0);

   if(add==true) {
      // add boundary --> grow array
      X.resize(boost::extents[n1+2][n2+2]);  // element preserving resize
      n1=X.shape()[0];
      n2=X.shape()[1];
      // shifting numbers inside the array to make space for boundary
      for(int i=n1-3; i>=0; i--) {
         for(int j=n2-3; j>=0; j--) {
            X[i+1][j+1]=X[i][j];
         }
      }
   }

   // check the boundary vectors are of the correct size
   assert( bd1a.size()==bd1b.size() && bd1a.size()==n2-2 );
   assert( bd2a.size()==bd2b.size() && bd2a.size()==n1-2 );

   // setting boundary values
   if(boundary==types::Dirichlet) {
      for(int i=0; i<(int)n2; i++) {
         // include corner cases, which are otherwise undetermined
         int idx = std::min(std::max(i-1,0),(int)n2-3);
         X[0][i]    = bd1a[idx];
         X[n1-1][i] = bd1b[idx];
      }
      for(size_t i=1; i<n1-1; i++) {
         X[i][0]    = bd2a[i-1];
         X[i][n2-1] = bd2b[i-1];
      }
   } else if(boundary==types::Neumann) {
      for(int i=0; i<(int)n2; i++) {
         // include corner cases, which are otherwise undetermined
         int idx = std::min(std::max(i-1,0),(int)n2-3);
         X[0][i]    = X[2][i]    + 2.0*h1*bd1a[idx];
         X[n1-1][i] = X[n1-3][i] + 2.0*h1*bd1b[idx];
      }
      for(size_t i=1; i<n1-1; i++) {
         X[i][0]    = X[i][2]    + 2.0*h2*bd2a[i-1];
         X[i][n2-1] = X[i][n2-3] + 2.0*h2*bd2b[i-1];
      }
   } else {
      assert(false);
   }
}
// specialised version where all boundaries have the same value
void set_boundary(boost::multi_array<double,2>& X, double h1, double h2,
                  double bdvalue, types::boundary bdcond, bool add)
{
   size_t n1=X.shape()[0];
   size_t n2=X.shape()[1];
   // set the correct size of the boundary vectors
   size_t m1,m2;
   if(add==true) {
      m1=n1;
      m2=n2;
   } else {
      assert(n1>=2 && n2>=2);
      m1=n1-2;
      m2=n2-2;
   }
   // fill boundary vectors
   std::vector<double> bd1a,bd1b,bd2a,bd2b;
   set_boundary_to_uniform(bd1a,bd1b,bd2a,bd2b,bdvalue,m1,m2);

   // call the general routine
   set_boundary(X,h1,h2,bd1a,bd1b,bd2a,bd2b,bdcond,add);
}

// PDE functions to solve the discrete Poisson's Equation
// ------------------------------------------------------

// discrete Gradient operator on a uniform grid
// given u(x,y), it returns DX = a_1 du/dx, DY = a_2 du/dy,
// using one-sided finite differences
// U[i][j]  = u(x_i, y_j), i=0...n-1
// DX[i][j] = a1*(U[i+1][j]-U[i][j])/h, i=0...n-2
void grad(boost::multi_array<double,2>& DX, boost::multi_array<double,2>& DY,
          const boost::multi_array<double,2>& U,
          double a1, double a2, double h1, double h2)
{
   assert(U.shape()[0]>1 && U.shape()[1]>1);
   size_t n1=U.shape()[0]-1;
   size_t n2=U.shape()[1]-1;
   DX.resize(boost::extents[n1][n2]);
   DY.resize(boost::extents[n1][n2]);
   for(size_t i=0; i<n1; i++) {
      for(size_t j=0; j<n2; j++) {
         DX[i][j] = (a1/h1) * (U[i+1][j]-U[i][j]);
         DY[i][j] = (a2/h2) * (U[i][j+1]-U[i][j]);
      }
   }
}
// discrete Gradient operator on a uniform grid
// same as above, but the input U is assumed to contain only inner grid points
// and the boundary grid points are inferred from the boundary condition
// U[i][j] = u(x_i, y_j), i=0...n-1
// DX[i][j] = a1*(U[i][j]-U[i-1][j])/h, i=0...n+1
void grad(boost::multi_array<double,2>& DX, boost::multi_array<double,2>& DY,
          const boost::multi_array<double,2>& U,
          double a1, double a2, double h1, double h2,
          const std::vector<double>& bd1a, const std::vector<double>& bd1b,
          const std::vector<double>& bd2a, const std::vector<double>& bd2b,
          types::boundary boundary)
{
   // this is lazy and very memory inefficient
   // TODO: replace with a direct calculation
   boost::multi_array<double,2> V=U;
   pde::set_boundary(V,h1,h2,bd1a,bd1b,bd2a,bd2b,boundary,true);
   grad(DX,DY,V,a1,a2,h1,h2);
}
// simplified version where only one boundary value can be specified
void grad(boost::multi_array<double,2>& DX, boost::multi_array<double,2>& DY,
          const boost::multi_array<double,2>& U,
          double a1, double a2, double h1, double h2,
          double bdvalue, types::boundary bdcond)
{
   size_t n1=U.shape()[0];
   size_t n2=U.shape()[1];

   // fill boundary vectors
   std::vector<double> bd1a,bd1b,bd2a,bd2b;
   set_boundary_to_uniform(bd1a,bd1b,bd2a,bd2b,bdvalue,n1,n2);

   // call the general routine
   grad(DX,DY,U,a1,a2,h1,h2,bd1a,bd1b,bd2a,bd2b,bdcond);
}

// discrete Divergence operator on a uniform grid
// given u(x,y), v(x,y), it returns f = a_1 du/dx + a_2 dv/dx,
// using one-sided finite differences
void div(boost::multi_array<double,2>& F,
         const boost::multi_array<double,2>& U,
         const boost::multi_array<double,2>& V,
         double a1, double a2, double h1, double h2)
{
   assert(U.shape()[0]==V.shape()[0]);
   assert(U.shape()[1]==V.shape()[1]);
   assert(U.shape()[0]>1 && U.shape()[1]>1);
   size_t n1=U.shape()[0]-1;
   size_t n2=V.shape()[1]-1;
   F.resize(boost::extents[n1][n2]);
   for(size_t i=0; i<n1; i++) {
      for(size_t j=0; j<n2; j++) {
         F[i][j] = (a1/h1) * (U[i+1][j+1]-U[i][j+1])
                   + (a2/h2) * (V[i+1][j+1]-V[i+1][j]);
      }
   }
}

// discrete Laplace operator on a uniform grid
// given u, it returns f = a_1 u_xx + a_2 u_yy
// note, the Laplace operator can only be applied to inner grid points
// and so the result F has two grid points less in each dimension, i.e.
// U[i][j] = u(x_i, y_j)
// F[i][j] = f(x_{i+1}, y_{i+1})
void laplace(boost::multi_array<double,2>& F,
             const boost::multi_array<double,2>& U,
             double a1, double a2, double h1, double h2)
{
   assert(U.shape()[0]>2 && U.shape()[1]>2);
   size_t n1=U.shape()[0]-2;
   size_t n2=U.shape()[1]-2;
   F.resize(boost::extents[n1][n2]);
   for(size_t i=0; i<n1; i++) {
      for(size_t j=0; j<n2; j++) {
         // index [i][j] in F corresponds to [i+1][j+1] in U
         F[i][j] = a1 * (U[i][j+1] - 2.0*U[i+1][j+1] + U[i+2][j+1]) / (h1*h1)
                   + a2 * (U[i+1][j] - 2.0*U[i+1][j+1] + U[i+1][j+2]) / (h2*h2);
      }
   }
}
// discrete Laplace operator on a uniform grid
// the input U is assumed to contain only inner grid points and the
// boundary grid points are inferred from the boundary condition
// U[i][j] = u(x_i, y_j)
// F[i][j] = f(x_i, y_i)
void laplace(boost::multi_array<double,2>& F,
             const boost::multi_array<double,2>& U,
             double a1, double a2, double h1, double h2,
             const std::vector<double>& bd1a, const std::vector<double>& bd1b,
             const std::vector<double>& bd2a, const std::vector<double>& bd2b,
             types::boundary boundary)
{
   size_t n1=U.shape()[0];
   size_t n2=U.shape()[1];
   assert(n1>0 && n2>0);
   assert(bd1a.size()==bd1b.size() && bd1a.size()==n2);
   assert(bd2a.size()==bd2b.size() && bd2a.size()==n1);

   F.resize(boost::extents[n1][n2]);
   for(int i=0; i<(int)n1; i++) {
      for(int j=0; j<(int)n2; j++) {
         double Um1, U0, Up1;       // U[i-1], U[i], U[i+1]

         // first dimension
         Um1 = U[std::max(i-1,0)][j];
         U0  = U[i][j];
         Up1 = U[std::min(i+1,(int)n1-1)][j];
         if(i==0) {
            if(boundary==types::Dirichlet) {
               Um1 = bd1a[j];
            } else if(boundary==types::Neumann) {
               Um1 = U[1][j] + 2.0*h1*bd1a[j];
            }
         } else if(i==(int)n1-1) {
            if(boundary==types::Dirichlet) {
               Up1 = bd1b[j];
            } else if(boundary==types::Neumann) {
               Up1 = U[n1-2][j] + 2.0*h1*bd1b[j];
            }
         }
         F[i][j] = a1 * (Um1 - 2.0*U0 + Up1) / (h1*h1);

         // second dimension
         Um1 = U[i][std::max(j-1,0)];
         Up1 = U[i][std::min(j+1,(int)n2-1)];
         if(j==0) {
            if(boundary==types::Dirichlet) {
               Um1 = bd2a[i];
            } else if(boundary==types::Neumann) {
               Um1 = U[i][1] + 2.0*h2*bd2a[i];
            }
         } else if(j==(int)n2-1) {
            if(boundary==types::Dirichlet) {
               Up1 = bd2b[i];
            } else if(boundary==types::Neumann) {
               Up1 = U[i][n2-2] + 2.0*h2*bd2b[i];
            }
         }
         F[i][j] += a2 * (Um1 - 2.0*U0 + Up1) / (h2*h2);
      }
   }
}
// simplified version where only one boundary value can be specified
void laplace(boost::multi_array<double,2>& F,
             const boost::multi_array<double,2>& U,
             double a1, double a2, double h1, double h2,
             double bdvalue, types::boundary bdcond)
{

   size_t n1=U.shape()[0];
   size_t n2=U.shape()[1];
   assert(n1>0 && n2>0);

   // fill boundary vectors
   std::vector<double> bd1a,bd1b,bd2a,bd2b;
   set_boundary_to_uniform(bd1a,bd1b,bd2a,bd2b,bdvalue,n1,n2);

   // call the general routine
   laplace(F,U,a1,a2,h1,h2,bd1a,bd1b,bd2a,bd2b,bdcond);
}

// Neumann boundary condition with a general right hand side F will not
// have a solution, however by looking at F we can say what the minimum
// L2-norm error will be for 0-Neumann boundary conditions:
// return = min ||Laplace U - F||
// min is taken over all U consistent with 0-Neumann condition
double neumann_error(const boost::multi_array<double,2>& F)
{
   size_t n1=F.shape()[0];
   size_t n2=F.shape()[1];
   assert(n1>1 && n2>1);

   // assuming 0 boundary conditions, otherwise would have to adjust
   // right hand side F as in poisolve()

   // calculate min L2 error ||Laplace U - F|| = |\hat F[0]| * ||EV[0]||
   // where \hat F, are the coordinates of F in EV-space,
   // and can simply be calculated as follows
   double sum=0.0;
   double fac=1.0;
   for(size_t i=0; i<n1; i++) {
      for(size_t j=0; j<n2; j++) {
         fac=1.0;
         if(j==0 || j==n2-1)
            fac*=0.5;
         if(i==0 || i==n1-1)
            fac*=0.5;
         sum+=fac*F[i][j];
      }
   }
   double F00 = sum/((n1-1)*(n2-1));      // \hat F [0][0] (EV space)
   double norm_ev=sqrt((double)(n1*n2));  // EV[0]=(1,...,1) --> norm=sqrt(n)
   double l2_error=F00*norm_ev;
   return l2_error;
}

// given a right hand side F, we can find a constant Neumann-boundary
// value which will have a solution U, so that Laplace U = F,
// returns the boundary value
double neumann_compat(const boost::multi_array<double,2>& F,
                      double a1, double a2, double h1, double h2)
{
   size_t n1=F.shape()[0];
   size_t n2=F.shape()[1];

   double l2_error=neumann_error(F);
   double norm_ev=sqrt((double)(n1*n2));  // EV[0]=(1,...,1) --> norm=sqrt(n)
   double F00 = l2_error/norm_ev;         // \hat F [0][0] (EV space)

   // with non-zero Neumann boundary condition, rhs F is modified,
   // as in poisolve(), so we can calculate the exact boundary
   // value to make the l2_error zero
   double bd = F00 / (2.0*a1/(h1*(n1-1)) + 2.0*a2/(h2*(n2-1)));

   return bd;
}

// solves the 2D Poisson equation: a1 u_xx + a2 u_yy = f
//
// discretised with uniform grid, x_{i+1} = x_i + h1, y_{i+1} = y_i + h2
// input: rhs       F[i][j] = f(x_i, y_j)
// output: solution  U[i][j] = u(x_i, y_j), ie only inner points by default
//
//
//
// boundary condition:
// - lower and upper boundary in dimension 1: bd1a, bd1b
// - lower and upper boundary in dimension 2: bd2a, bd2b
// - e.g. bd1a[i] refers to U[-1][i]
double poisolve( boost::multi_array<double,2>& U,
                 const boost::multi_array<double,2>& F,
                 double a1, double a2, double h1, double h2,
                 const std::vector<double>& bd1a, const std::vector<double>& bd1b,
                 const std::vector<double>& bd2a, const std::vector<double>& bd2b,
                 types::boundary boundary, bool add_boundary_to_solution)
{

#ifdef TIME_REPORT
   double t0=stoptime();
   double t1=t0;
   double t2;
#endif

   size_t n1=F.shape()[0];
   size_t n2=F.shape()[1];
   assert(n1>0 && n2>0);

   // adjust right hand side F with boundary condition (nothing to do for =0)
   boost::multi_array<double,2> rhs = F;
   assert( bd1a.size()==bd1b.size() && bd1a.size()==n2 );
   assert( bd2a.size()==bd2b.size() && bd2a.size()==n1 );
   {
      // factors for boundary adjustment to rhs
      double c1 = 0.0, c2 = 0.0;
      if(boundary==types::Dirichlet) {
         c1=a1/sqr(h1);
         c2=a2/sqr(h2);
      } else if(boundary==types::Neumann) {
         c1=2.0*a1/h1;
         c2=2.0*a2/h2;
      } else {
         assert(false);
      }
      // adjust rhs with boundary conditions
      for(size_t i=0; i<n2; i++) {
         rhs[0][i]    -= c1 * bd1a[i];
         rhs[n1-1][i] -= c1 * bd1b[i];
      }
      for(size_t i=0; i<n1; i++) {
         rhs[i][0]    -= c2 * bd2a[i];
         rhs[i][n2-1] -= c2 * bd2b[i];
      }
   }

#ifdef TIME_REPORT
   t2=stoptime();
   printf("poisolve(): %5.0f ms: rhs boundary conditions\n",(t2-t1)*1000.0);
   t1=t2;
#endif

   // transform rhs into EV space (inverse fft, in-place)
   {
      fftw_plan p;
      double fft_norm=0.0;  // i.e. FFT(FFT(x)) = fft_norm * x

      if(boundary==types::Dirichlet) {
         // DST-I = FFTW_RODFT00 (note, additional factor of 2 in fftw)
         p=fftw_plan_r2r_2d(n1, n2, &(rhs[0][0]), &(rhs[0][0]),
                            FFTW_RODFT00, FFTW_RODFT00, FFTW_ESTIMATE);
         fftw_execute(p);
         fftw_destroy_plan(p);
         fft_norm = 4.0*((n1+1)*(n2+1));

      } else if(boundary==types::Neumann) {
         // EV space is similar to DCT-I but without the factor of 1/2
         // for the 1st and last element

         // DCT-I = REDFT00 (note, additional factor of 2 in fftw)
         // so that DCT-I*DCT-I = 2(n-1) I (and not the usual (n-1)/2)
         p=fftw_plan_r2r_2d(n1, n2, &(rhs[0][0]), &(rhs[0][0]),
                            FFTW_REDFT00, FFTW_REDFT00, FFTW_ESTIMATE);
         fftw_execute(p);
         fftw_destroy_plan(p);

         for(size_t i=0; i<n1; i++) {
            rhs[i][0]    *= 0.5;
            rhs[i][n2-1] *= 0.5;
         }
         for(size_t j=0; j<n2; j++) {
            rhs[0][j]    *= 0.5;
            rhs[n1-1][j] *= 0.5;
         }
         fft_norm = 4.0*((n1-1)*(n2-1));

      } else {
         assert(false);
      }
      // scale so we get the inverse fft
      for(size_t i=0; i<n1; i++) {
         for(size_t j=0; j<n2; j++) {
            rhs[i][j] *= (1.0/fft_norm);      // div is more expensive than mul
         }
      }
   }

#ifdef TIME_REPORT
   t2=stoptime();
   printf("poisolve(): %5.0f ms: rhs to EV space (inv fft)\n",(t2-t1)*1000.0);
   t1=t2;
#endif

   // calculate eigenvalues of the linear operators L
   std::vector<double> lambda1(n1);
   std::vector<double> lambda2(n2);
   if(boundary==types::Dirichlet) {
      for(size_t i=0; i<n1; i++)
         lambda1[i] = -4.0*sqr( sin((M_PI*(i+1))/(2.0*(n1+1))) );
      for(size_t i=0; i<n2; i++)
         lambda2[i] = -4.0*sqr( sin((M_PI*(i+1))/(2.0*(n2+1))) );
   } else if(boundary==types::Neumann) {
      for(size_t i=0; i<n1; i++)
         lambda1[i] = -4.0*sqr( sin((M_PI*i)/(2.0*(n1-1))) );
      for(size_t i=0; i<n2; i++)
         lambda2[i] = -4.0*sqr( sin((M_PI*i)/(2.0*(n2-1))) );
   } else {
      assert(false);
   }

   // solve the equation for U in EV space
   double error=0.0;
   U.resize(boost::extents[n1][n2]);
   for(size_t i=0; i<n1; i++) {
      for(size_t j=0; j<n2; j++) {
         double div = (a1*lambda1[i]/(h1*h1) + a2*lambda2[j]/(h2*h2));
         if(div==0.0) {
            // here we need rhs[i][j] == 0 or else there is no solution
            // calculate the L2-norm of the error, need to know norm of EV
            // we know div==0.0, only for Neumann and EV 0, ||EV[0]||^2=n
            // however, since fftw has an extra factor 2, ||EV[0]||^2=4n
            double norm_ev = 16.0*n1*n2;     // TODO: make this more general
            error+=sqr(rhs[i][j])*norm_ev;
            // U[i][j] is arbitrary here
            U[i][j] = 0.0;
         } else {
            U[i][j] = rhs[i][j] / div;
         }
      }
   }

   // free rhs memory as it's no longer needed
   rhs.resize(boost::extents[0][0]);

#ifdef TIME_REPORT
   t2=stoptime();
   printf("poisolve(): %5.0f ms: solve equation in EV space\n",(t2-t1)*1000.0);
   t1=t2;
#endif

   // transform U from EV space into canonical space (fft, in-place)
   {
      fftw_plan p = nullptr;

      if(boundary==types::Dirichlet) {
         // DST-I = FFTW_RODFT00 (note, additional factor of 2 in fftw)
         // so that DST-I*DST-I = 2(n+1) I (and not the usual (n+1)/2)
         p=fftw_plan_r2r_2d(n1, n2, &(U[0][0]), &(U[0][0]),
                            FFTW_RODFT00, FFTW_RODFT00, FFTW_ESTIMATE);
         fftw_execute(p);

      } else if(boundary==types::Neumann) {
         // EV space is similar to DCT-I but without the factor of 1/2
         // for the 1st and last element

         // apply factor 2
         for(size_t i=0; i<n1; i++) {
            U[i][0]    *= 2.0;
            U[i][n2-1] *= 2.0;
         }
         for(size_t j=0; j<n2; j++) {
            U[0][j]    *= 2.0;
            U[n1-1][j] *= 2.0;
         }
         // DCT-I = REDFT00 (note, additional factor of 2 in fftw)
         p=fftw_plan_r2r_2d(n1, n2, &(U[0][0]), &(U[0][0]),
                            FFTW_REDFT00, FFTW_REDFT00, FFTW_ESTIMATE);
         fftw_execute(p);

      } else {
         assert(false);
      }
      fftw_destroy_plan(p);
   }

#ifdef TIME_REPORT
   t2=stoptime();
   printf("poisolve(): %5.0f ms: solution to normal space (fft)\n",(t2-t1)*1000.0);
   t1=t2;
#endif

   // by default, U only contains the inner grid points of the solution
   // however, we can also add the boundary if needed (inefficient)
   if(add_boundary_to_solution) {
      set_boundary(U,h1,h2,bd1a,bd1b,bd2a,bd2b,boundary,true);
   }

#ifdef TIME_REPORT
   t2=stoptime();
   printf("poisolve(): %5.0f ms: total time\n", (t2-t0)*1000.0);
#endif

   return sqrt(error);
}

// Poisson solver, assuming uniform boundary
double poisolve(boost::multi_array<double,2>& U,
                const boost::multi_array<double,2>& F,
                double a1, double a2, double h1, double h2,
                double bound_value,
                types::boundary boundarycondition,
                bool add_boundary_to_solution)
{

   size_t n1=F.shape()[0];
   size_t n2=F.shape()[1];

   // fill boundary vectors
   std::vector<double> bd1a,bd1b,bd2a,bd2b;
   set_boundary_to_uniform(bd1a,bd1b,bd2a,bd2b,bound_value,n1,n2);

   // call the general solver
   return poisolve(U,F,a1,a2,h1,h2,bd1a,bd1b,bd2a,bd2b,
                   boundarycondition,add_boundary_to_solution);
}

} // namespace pde


#ifndef EIGEN_SOLVER_H
#define EIGEN_SOLVER_H

#include <cassert>
#include <armadillo>

namespace willow { namespace qcmol {

class EigenSolver
{
public:

  EigenSolver (): m_is_init(false), m_eig_vals(), m_eig_vecs() {}

  // Sinvh.n_rows = nbf
  // Sinvh.n_cols = nmo
  // F.n_rows = nbf
  // F.n_cols = nbf
  // solve WF(nmo, nmo) = Sinv^T(nmo,nbf) F(nbf,nbf) Sinv(nbf,nmo)
  // dsyevd(WF) or eig_sym ()
  // save m_eig_vecs (nbf,nmo) = Sinvh(nbf,nmo)*Cvec(nmo,nmo)
  //
  EigenSolver  (const arma::mat Sinvh, const arma::mat F):
    m_is_init(true),
    m_eig_vals(Sinvh.n_cols),
    m_eig_vecs(Sinvh.n_rows, Sinvh.n_cols) {

    compute (Sinvh, F);

  }

  void compute (const arma::mat Sinvh, const arma::mat F) {

    const int nbf = Sinvh.n_rows;
    const int nmo = Sinvh.n_cols;

    if (!m_is_init) {
      m_is_init = true;
      m_eig_vals.set_size (nmo);
      m_eig_vecs.set_size (nbf, nmo);
    }

    // F'
    arma::mat wf = Sinvh.t()*F*Sinvh; // (nmo, nmo)

    // Update Orbitals and Energies
    arma::mat eg_vecs;
    bool ok = arma::eig_sym (m_eig_vals, eg_vecs, wf);

    // Transform back to non-orthogonal basis
    m_eig_vecs = Sinvh*eg_vecs; // (nbf, nmol)

  }

  arma::vec eigenvalues() const {
    assert (m_is_init);
    return m_eig_vals;
  }

  arma::mat eigenvectors() const {
    assert (m_is_init);
    return m_eig_vecs;
  }

protected:
  bool      m_is_init;
  arma::vec m_eig_vals;
  arma::mat m_eig_vecs;
};

} } // namespace willow::qcmol

#endif


/*
// Copyright 2018 University of Liege
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Authors:
// - Adrien Crovato
*/

//// Wake panels creation
// Compute wake panel collocation point, surface, vertices, normal, longitudinal, transverse and perpendicular
// vectors from data contained into sGrid
// Wake panels extend to a constant x coordinate, based on the geometry
//
// I/O:
// - sGrid: temporary dynamic array containing body panel vertices
// - bPan: body panels (structure)
// - wPan: wake panels (structure)

#include <iostream>
#include <Eigen/Dense>
#include "create_wake.h"

#define  NDIM 3

using namespace std;
using  namespace Eigen;

void create_wake(MatrixX3d &sGrid, Network &bPan, Network &wPan) {

    // Temporary variables
    double norm = 0;
    Vector3d v1(NDIM), v2(NDIM);
    double dwnstrm;
    int c2, c3;

    //// Begin
    cout << "Creating wake panels... " << flush;

    // Set number of panels (currently only handles predefined wakes)
    wPan.nC = 2;
    wPan.nC_ = 1;
    wPan.nS = bPan.nS;
    wPan.nS_ = bPan.nS_;
    wPan.nP = wPan.nS_;

    // Resize network matrices
    wPan.CG.resize(wPan.nP, NDIM);
    wPan.v0.resize(wPan.nP, NDIM);
    wPan.v1.resize(wPan.nP, NDIM);
    wPan.v2.resize(wPan.nP, NDIM);
    wPan.v3.resize(wPan.nP, NDIM);
    wPan.S.resize(wPan.nP, 1);
    wPan.n.resize(wPan.nP, NDIM);
    wPan.l.resize(wPan.nP, NDIM);
    wPan.t.resize(wPan.nP, NDIM);
    wPan.p.resize(wPan.nP, NDIM);

    // Downstream coordinate of wake panels = tip x-coord+ 5*chord
    dwnstrm = sGrid(sGrid.rows()-1,0) + 5*sGrid(0,0);

    // Collocations points, corners and vectors
    for (int j = 0; j < wPan.nP; ++j) {

        c2 = j * bPan.nC;
        c3 = (j+1) * bPan.nC;

        wPan.v0(j,0) = dwnstrm; // pts[i][k]
        wPan.v1(j,0) = sGrid(c2,0); // pts[i+1][k]
        wPan.v2(j,0) = sGrid(c3,0); // pts[i+1][k+1]
        wPan.v3(j,0) = dwnstrm; // pts[i][k+1]
        wPan.v0(j,1) = sGrid(c2,1);
        wPan.v1(j,1) = sGrid(c2,1);
        wPan.v2(j,1) = sGrid(c3,1);
        wPan.v3(j,1) = sGrid(c3,1);
        wPan.v0(j,2) = sGrid(c2,2);
        wPan.v1(j,2) = sGrid(c2,2);
        wPan.v2(j,2) = sGrid(c3,2);
        wPan.v3(j,2) = sGrid(c3,2);

        wPan.CG(j,0) = (wPan.v0(j,0) + wPan.v1(j,0) + wPan.v2(j,0) + wPan.v3(j,0)) / 4;
        wPan.CG(j,1) = (wPan.v1(j,1) + wPan.v2(j,1)) / 2;
        wPan.CG(j,2) = (wPan.v1(j,2) + wPan.v2(j,2)) / 2;

        wPan.l(j,0) = (wPan.v0(j,0) + wPan.v3(j,0) - wPan.v1(j,0) - wPan.v2(j,0)) / 4;
        wPan.l(j,1) = 0;
        wPan.l(j,2) = 0;
        wPan.l.row(j) /= wPan.l.row(j).norm();

        wPan.t(j,0) = (wPan.v2(j,0) - wPan.v1(j,0)) / 2;
        wPan.t(j,1) = (wPan.v2(j,1) - wPan.v1(j,1)) / 2;
        wPan.t(j,2) = (wPan.v2(j,2) - wPan.v1(j,2)) / 2;
        wPan.t.row(j) /= wPan.t.row(j).norm();
    }
    // Surfaces and normals
    for (int j = 0; j < wPan.nP; ++j) {
        v1(0) = wPan.v2(j,0) - wPan.v0(j,0);
        v2(0) = wPan.v1(j,0) - wPan.v3(j,0);
        v1(1) = wPan.v2(j,1) - wPan.v0(j,1);
        v2(1) = wPan.v1(j,1) - wPan.v3(j,1);
        v1(2) = wPan.v2(j,2) - wPan.v0(j,2);
        v2(2) = wPan.v1(j,2) - wPan.v3(j,2);

        wPan.n.row(j) = v1.cross(v2);
        norm = wPan.n.row(j).norm();

        wPan.S(j) = norm/2;
        wPan.n.row(j) /= norm;
    }
    // Perpendicular vector
    for (int j = 0; j < wPan.nP; ++j)
        wPan.p.row(j) = wPan.n.row(j).cross(wPan.l.row(j));

    //// Control display
    cout << "Done!" << endl;
    cout << "Wake panels extend to " << dwnstrm << " in the x direction" << endl;
    #ifdef VERBOSE
        cout << "Collocation points: " << wPan.CG.rows() << 'X' << wPan.CG.cols() << endl;
        for (int i = 0; i < wPan.nP; ++i)
            cout << i << ' ' << wPan.CG(i,0) << ' ' << wPan.CG(i,1) << ' ' << wPan.CG(i,2) << endl;
        cout << "Panel surfaces: " << wPan.S.rows() << 'X' << wPan.S.cols() << endl;
        for (int i = 0; i < wPan.nP; ++i)
            cout << i << ' ' << wPan.S(i) << endl;
        cout << "Unit normals: " << wPan.n.rows() << 'X' << wPan.n.cols() << endl;
        for (int i = 0; i < wPan.nP; ++i)
            cout << i << ' ' << wPan.n(i,0) << ' ' << wPan.n(i,1) << ' ' << wPan.n(i,2) << endl;
        cout << "Unit longitudinal vectors: " << wPan.l.rows() << 'X' << wPan.l.cols() << endl;
        for (int i = 0; i < wPan.nP; ++i)
            cout << i << ' ' << wPan.l(i,0) << ' ' << wPan.l(i,1) << ' ' << wPan.l(i,2) << endl;
        cout << "Unit transverse vectors: " << wPan.t.rows() << 'X' << wPan.t.cols() << endl;
        for (int i = 0; i < wPan.nP; ++i)
            cout << i << ' ' << wPan.t(i,0) << ' ' << wPan.t(i,1) << ' ' << wPan.t(i,2) << endl;
        cout << "Unit perpendicular vectors: " << wPan.p.rows() << 'X' << wPan.p.cols() << endl;
        for (int i = 0; i < wPan.nP; ++i)
            cout << i << ' ' << wPan.p(i,0) << ' ' << wPan.p(i,1) << ' ' << wPan.p(i,2) << endl;
    #endif
    cout << endl;
}


#ifndef LIMEX_HH
#define LIMEX_HH

#include <fstream>
#include <iostream>

#include <boost/timer/timer.hpp>

#include "dune/istl/solvers.hh"

#include "fem/embedded_errorest.hh"
#include "fem/iterate_grid.hh"
#include "fem/hierarchicErrorEstimator.hh"
#include "fem/hierarchicspace.hh"
#include "fem/istlinterface.hh"

#include "dune/istl/solvers.hh"
#include "dune/istl/preconditioners.hh"

#include "timestepping/extrapolation.hh"
#include "timestepping/semieuler.hh"

#include "linalg/factorization.hh"
#include "linalg/umfpack_solve.hh"
// #include "linalg/mumps_solve.hh"
// #include "linalg/superlu_solve.hh"

#include "linalg/trivialpreconditioner.hh"
#include "linalg/direct.hh"
#include "linalg/triplet.hh"
#include "linalg/iluprecond.hh"
#include "linalg/additiveschwarz.hh"
// #include "linalg/hyprecond.hh"
#include "linalg/jacobiPreconditioner.hh"

#include "utilities/enums.hh"

#include "io/vtk.hh"

bool fabscompare( double a, double b ) { return fabs( a ) < fabs( b ) ; }

DirectType xyz = DirectType::UMFPACK;

namespace Kaskade
{

  template <class Functional>
  struct CardioD2
  {
    template <int row, int col>
    class D2: public Functional::template D2<row,col>
    {
      typedef typename Functional::template D2<row,col> d2;

      public:
	static bool const present = d2::present && (row==0) && (col==0);
	static bool const lumped = true;
    };
  };

/**
 * \ingroup timestepping
 * \brief Extrapolated linearly implicit Euler method.
 *
 * This class implements the extrapolated linearly implicit Euler
 * method for integrating time-dependent evolution problems. The
 * implementation follows Deuflhard/Bornemann Chapter 6.4.3.
 */
template <class Eq>
class Limex
{
public:
  typedef Eq EvolutionEquation;
  typedef typename EvolutionEquation::AnsatzVars::VariableSet State;

private:
  typedef SemiLinearizationAt<SemiImplicitEulerStep<EvolutionEquation> > Linearization;
  typedef VariationalFunctionalAssembler<Linearization> Assembler;

  // for hierarchic error estimator
  typedef FEFunctionSpace<ContinuousHierarchicExtensionMapper<double,typename Eq::AnsatzVars::Grid::LeafGridView> > SpaceEx;
  typedef boost::fusion::vector< typename SpaceType<typename Eq::AnsatzVars::Spaces,0>::type const*, SpaceEx const*> ExSpaces;

//   two components, 1 space -- how to generalize?
//   typedef boost::fusion::vector<VariableDescription<1,1,0>, VariableDescription<1,1,1> > ExVariableDescriptions;

  // 1 component, 1 space -- how to generalize?
  typedef boost::fusion::vector<VariableDescription<1,1,0> > ExVariableDescriptions;

  typedef VariableSetDescription<ExSpaces,ExVariableDescriptions> ExVariableSet;
  typedef HierarchicErrorEstimator<Linearization,ExVariableSet,ExVariableSet,CardioD2<Linearization> > ErrorEstimator;
  typedef VariationalFunctionalAssembler<ErrorEstimator> EstimatorAssembler;

//   typedef typename EstimatorAssembler::template AnsatzVariableRepresentation<> Ansatz;
//   typedef typename EstimatorAssembler::template TestVariableRepresentation<> Test;
/*  typedef typename ExVariableSet::template CoefficientVectorRepresentation<0,1>::type ExCoefficientVectors;*/

  typedef typename Eq::AnsatzVars::Grid::Traits::LeafIndexSet IS ;

public:
  /**
   * Constructs an ODE integrator. The arguments eq and ansatzVars
   * have to exist during the lifetime of the integrator.
   */
  Limex(GridManager<typename EvolutionEquation::AnsatzVars::Grid>& gridManager_,
        EvolutionEquation& eq_, typename EvolutionEquation::AnsatzVars const& ansatzVars_, DirectType st=DirectType::UMFPACK):
    gridManager(gridManager_), ansatzVars(ansatzVars_), eq(&eq_,0),
    assembler(gridManager,ansatzVars.spaces),
    iteSteps(10000), iteEps(1e-6), extrap(0),
    rhsAssemblyTime(0.0), matrixAssemblyTime(0.0), factorizationTime(0.0), solutionTime(0.0), adaptivityTime(0.0),
    solverType(st)
    {}

  /**
   * In order to maintain compatibility with existing code,
   * this overload is needed, as non const reference parameters (refinements) can not
   * have default values. All ist does is call the original method with a temporary
   * parameter.
   */
  State const& step(State const& x, double dt, int order,
                    std::vector<std::pair<double,double> > const& tolX)
  {
    std::vector<std::vector<bool> > tmp ;
    return step(x,dt,order,tolX,tmp);
  }

  /**
   * Computes a state increment that advances the given state in
   * time. The time in the given evolution equation is increased by
   * dt.
   *
   * \param x the initial state to be evolved
   * \param dt the time step
   * \param order the extrapolation order >= 0 (0 corresponds to the linearly implicit Euler)
   * \param tolX
   * \param refinements keep track of the cells marked for refinement
   * \return the state increment (references an internal variable that will be
   *         invalidated by a subsequent call of step)
   *
   * \todo (i) check for B constant, do not reassemble matrix in this case
   *       (ii) implement fixed point iteration instead of new factorization
   *            in case B is not constant
   */
  State const& step(State const& x, double dt, int order,
                    std::vector<std::pair<double,double> > const& tolX,

		    std::vector< std::vector<bool> > &refinements )
  {
    boost::timer::cpu_timer timer;

    std::vector<double> stepFractions(order+1);
    for (int i=0; i<=order; ++i) stepFractions[i] = 1.0/(i+1); // harmonic sequence
    extrap.clear();

    typedef typename EvolutionEquation::AnsatzVars::Grid Grid;

    int const dim = EvolutionEquation::AnsatzVars::Grid::dimension;
    int const nvars = EvolutionEquation::AnsatzVars::noOfVariables;
    int const neq = EvolutionEquation::TestVars::noOfVariables;
    size_t  nnz = assembler.nnz(0,neq,0,nvars,false);
    size_t  size = ansatzVars.degreesOfFreedom(0,nvars);

    std::vector<double> rhs(size), sol(size);

    State dx(x), dxsum(x), tmp(x);
    double const t = eq.time();

    double estTime = 0 ;

    eq.temporalEvaluationRange(t,t+dt);

    typedef AssembledGalerkinOperator<Assembler,0,neq,0,neq> Op;

    bool iterative = true ;
    int fill_lev = /*0*/ /*1*/2;

    typedef typename EvolutionEquation::AnsatzVars::template CoefficientVectorRepresentation<0,neq>::type
      CoefficientVectors;
    typedef typename EvolutionEquation::TestVars::template CoefficientVectorRepresentation<0,neq>::type
      LinearSpaceX;

    for (int i=0; i<=order; ++i) {
      double const tau = stepFractions[i]*dt;
      eq.setTau(tau);
      eq.time(t);

      // mesh adaptation loop. Just once if i>0.
      bool accurate = true;
      int refinement_count = 0 ;

      do {
        // Evaluate and factorize matrix B(t)-tau*J
        dx/* * */= 0;
        timer.start();

// 	assembler.assemble(Linearization(eq,x,x,dx)/*,(Assembler::VALUE|Assembler::RHS|Assembler::MATRIX),4*/);

// #ifndef KASKADE_SEQUENTIAL
// 	gridManager.enforceConcurrentReads(std::is_same<Grid,Dune::UGGrid<dim> >::value);
// 	assembler.setNSimultaneousBlocks(40);
// 	assembler.setRowBlockFactor(2.0);
// #endif
// 	std::cout << "assemble linear system ..." ; std::cout.flush();
	assembler.assemble(Linearization(eq,x,x,dx)/*,(Assembler::VALUE|Assembler::RHS|Assembler::MATRIX),4*/);
// 	std::cout << " done\n"; std::cout.flush();

	matrixAssemblyTime += (double)(timer.elapsed().user)/1e9;

	Op A(assembler);
// 	std::cout << "solve linear system\n"; std::cout.flush();

	if (iterative)
	{
	  timer.start();

	  CoefficientVectors  solution(EvolutionEquation::AnsatzVars::template CoefficientVectorRepresentation<0,neq>::init(ansatzVars.spaces)
	    );
	  solution = 0;
	  CoefficientVectors rhs(assembler.rhs());

	  ILUKPreconditioner<Op> p(A,fill_lev,0);
// 	  ILUTPreconditioner<Op> p(A,240,1e-2,0);
// 	  JacobiPreconditioner<Op> p(A,1.0);

	  Dune::BiCGSTABSolver<LinearSpaceX> cg(A,p,1e-7,2000,0); //verbosity: 0-1-2 (nothing-start/final-every it.)
	  Dune::InverseOperatorResult res;
	  cg.apply(solution,rhs,res);
	  if ( !(res.converged) || (res.iterations == 2001) ) {
	    std::cout << "   no of iterations in cg = " << res.iterations << std::endl;
	    std::cout << " convergence status of cg = " << res.converged  << std::endl;
	    assert(0);
	  }
	  dx.data = solution.data;
	  solutionTime += (double)(timer.elapsed().user)/1e9;
	}
	else
	{
	  // direct solution
	  timer.start();
// 	  assembler.toTriplet(0,neq,0,nvars,ridx.begin(),cidx.begin(),data.begin(),false);
	  MatrixAsTriplet<double> triplet = A.template get<MatrixAsTriplet<double> >();

	  Factorization<double> *matrix = 0;
	  switch (solverType) {
	    case DirectType::UMFPACK:
	      matrix = new UMFFactorization<double>(size,0,triplet.ridx,triplet.cidx,triplet.data);
	      break;
	    case DirectType::UMFPACK3264:
	      matrix = new UMFFactorization<double>(size,0,triplet.ridx,triplet.cidx,triplet.data);
	      break;
// 	    case MUMPS:
// 	      matrix = new MUMPSFactorization<double>(size,0,triplet.ridx,triplet.cidx,triplet.data);
// 	      break;
// 	    case SUPERLU:
// 	      matrix = new SUPERLUFactorization<double>(size,0,triplet.ridx,triplet.cidx,triplet.data);
// 	      break;
	    default:
	      throw -321;
	      break;
	  }
	  factorizationTime += (double)(timer.elapsed().user)/1e9;

	  // First right hand side (j=0) has been assembled together with matrix.
	  timer.start();
	  A.getAssembler().toSequence(0,neq,rhs.begin());
	  for (int k=0; k<rhs.size(); ++k) assert(std::isfinite(rhs[k]));
	  matrix->solve(rhs,sol);
	  delete matrix;
	  for (int k=0; k<sol.size(); ++k) assert(std::isfinite(sol[k]));
	  dx.read(sol.begin());
	  solutionTime += (double)(timer.elapsed().user)/1e9;

// 	  Factorization<double> *matrix = 0;
// 	  switch (solverType)
// 	    {
// 	      case UMFPACK:
// 		matrix = new UMFFactorization<double>(size,0,ridx,cidx,data);
// 		break;
// 	      case UMFPACK3264:
// 		matrix = new UMFFactorization<double>(size,0,ridx,cidx,data);
// 		break;
// 	      case MUMPS:
// 		matrix = new MUMPSFactorization<double>(size,0,ridx,cidx,data);
// 		break;
// 	      case SUPERLU:
// 		matrix = new SUPERLUFactorization<double>(size,0,ridx,cidx,data);
// 		break;
// 	      default:
// 		throw -321;
// 		break;
// 	    }
// 	  factorizationTime += (double)(timer.elapsed().user)/1e9;
//
// 	  // First right hand side (j=0) has been assembled together with matrix.
// 	  timer.start();
// 	  assembler.toSequence(0,neq,rhs.begin());
// 	  for (size_t k=0; k<rhs.size(); ++k) assert(finite(rhs[k]));
// 	  matrix->solve(rhs,sol);
// 	  delete matrix;
// 	  for (size_t k=0; k<sol.size(); ++k) assert(finite(sol[k]));
// 	  dx.read(sol.begin());
// 	  solutionTime += (double)(timer.elapsed().user)/1e9;
	  //end: direct solution
	}

        // Mesh adaptation only if requested and only for the full
        // implicit Euler step (first stage).

	typedef typename EvolutionEquation::AnsatzVars::GridView::template Codim<0>::Iterator CellIterator ;

	accurate = true ;
	timer.start();
	IS const& is = gridManager.grid().leafIndexSet();
	std::vector<double> errorDistribution(is.size(0),0.0);
	double maxErr = 0.0, errNorm = 0.0 ;

        if (!tolX.empty() && i==0) {

	  // embeddedErrorEstimator can not be used for linear finite elements
	  // use HierarchicErrorEstimator instead
	  // preparation for HierarchicErrorEstimator
	  if(true) // to avoid mesh adaptation for exVariables
	  {
	  //TODO: this should be using the gridviews/index sets of the original space, not the leaf!
	  SpaceEx  spaceEx(gridManager,gridManager.grid().leafGridView(), boost::fusion::at_c<0>(ansatzVars.spaces)->mapper().getOrder()+1);
	  typename SpaceType<typename Eq::AnsatzVars::Spaces,0>::type spaceH1 = *(boost::fusion::at_c<0>(ansatzVars.spaces)) ;
	  ExSpaces exSpaces(&spaceH1,&spaceEx);
	  std::string exVarNames[2] = { "ev", "ew" };
	  ExVariableSet exVariableSet(exSpaces, exVarNames);
	  EstimatorAssembler estAssembler(gridManager,exSpaces);

	  tmp/* * */= 0 ;
// 	  estAssembler.assemble(ErrorEstimator(Linearization(eq,x,x,tmp/*dx*/),dx));
	  estAssembler.assemble(ErrorEstimator(semiLinearization(eq,x,x,/*dx*/tmp),dx));

	  int const estNvars = ErrorEstimator::AnsatzVars::noOfVariables;
// 	  std::cout << "estimator: nvars = " << estNvars << "\n";
	  int const estNeq = ErrorEstimator::TestVars::noOfVariables;
// 	  std::cout << "estimator:   neq = " << estNeq << "\n";
	  size_t  estNnz = estAssembler.nnz(0,estNeq,0,estNvars,false);
	  size_t  estSize = exVariableSet.degreesOfFreedom(0,estNvars);

	  std::vector<int> estRidx(estNnz), estCidx(estNnz);
	  std::vector<double> estData(estNnz), estRhs(estSize), estSolVec(estSize);

	  estAssembler.toSequence(0,estNeq,estRhs.begin());

	  typedef typename ExVariableSet::template CoefficientVectorRepresentation<0,estNeq>::type ExCoefficientVectors;

	  // iterative solution of error estimator
// 	  timer.start();
// 	  AssembledGalerkinOperator<EstimatorAssembler> E(estAssembler);
// 	  typename Dune::InverseOperatorResult estRes;
// 	  typename Test::type estRhside( Test::rhs(estAssembler) ) ;
// 	  typename Ansatz::type estSol( Ansatz::init(estAssembler) ) ;
// 	  estSol = 1.0 ;
// 	  JacobiPreconditioner<EstimatorAssembler> jprec(estAssembler, 1.0);
// 	  jprec.apply(estSol,estRhside); //single Jacobi iteration
// 	  estTime += (double)(timer.elapsed().user)/1e9;
// 	  estSol.write(estSolVec.begin());

	  typedef AssembledGalerkinOperator<EstimatorAssembler> AssEstOperator;
	  AssEstOperator agro(estAssembler);
	  Dune::InverseOperatorResult estRes;
	  ExCoefficientVectors estRhside(estAssembler.rhs());
	  ExCoefficientVectors estSol(ExVariableSet::template CoefficientVectorRepresentation<0,estNeq>::init(exVariableSet.spaces));
	  estSol = 1.0 ;
	  JacobiPreconditioner<AssEstOperator> jprec(agro, 1.0);
	  jprec.apply(estSol,estRhside); //single Jacobi iteration
// 	  estTime += (double)(timer.elapsed().user)/1e9;
	  estSol.write(estSolVec.begin());

	  //--

	  // Transfer error indicators to cells.
	  CellIterator ciEnd = ansatzVars.gridView.template end<0>() ;
	  for (CellIterator ci=ansatzVars.gridView.template begin<0>(); ci!=ciEnd; ++ci) {
	    typedef typename SpaceEx::Mapper::GlobalIndexRange GIR;
	    double err = 0.0;
	    GIR gix = spaceEx.mapper().globalIndices(*ci);
	    for (typename GIR::iterator j=gix.begin(); j!=gix.end(); ++j)
	      err += fabs(boost::fusion::at_c<0>(estSol.data)[*j]);
	    // only for 1st component -- second in monodomain is ODE
	    errorDistribution[is.index(*ci)] = err;
	    if (fabs(err)>maxErr) maxErr = fabs(err);
	  }
// 	  std::cout << "maxErr: " << maxErr << "\n";

// 	  for( size_t cno = 0 ; cno < is.size(0); cno++ )
// 	    std::cout << cno << "\t\t" << errorDistribution[cno] << "\n";
// 	  std::cout.flush();

	  //TODO: estimate only error in PDE, not ODE!
	  // what about relative accuracy?
	  for (size_t k = 0; k < estRhs.size() ; k++ ) errNorm +=  fabs( estRhs[k] * estSolVec[k] );
	  // this is for all variables?!
// 	  errNorm = tau/*dt*/*sqrt(errNorm);
	  std::cout << "errNorm = " << errNorm << std::endl ;
	  std::cout.flush();

	  }

	  double overallTol = tolX[0].first ;

// 	  for( int i = 0 ; i < 1 /*nvars*/ ; i++ )
// 	    overallTol += tolX[i].first*tolX[i].first ;
// 	  overallTol = sqrt(overallTol);

// 	  std::cout << "overallTol = " << overallTol << std::endl ;

// 	  alpha = 0 ; // for uniform refinement

	  int nRefMax = /*10*/7 ;/*7*//*5*//*3*//*15*/ /*4*/ /*5*/ /*10*/
	  double fractionOfCells = 0.05; /*0.1,0.2*/
	  unsigned long noToRefine = 0, noToCoarsen = 0;
	  double alpha = 1.0; /*0.5; //test april 13*/

	  double errLevel = 0.5*maxErr ; //0.75
// 	  double minRefine = 0.05;

// 	  if (minRefine>0.0)
// 	  {
// 	    std::vector<double> eSort(errorDistribution);
// 	    std::sort(eSort.begin(),eSort.end(),fabscompare);
// 	    int minRefineIndex = minRefine*(eSort.size()-1);
// 	    double minErrLevel = fabs(eSort[minRefineIndex])+1.0e-15;
// 	    if (minErrLevel<errLevel)
// 	      errLevel = minErrLevel;
// 	  }

// 	  double minRefine = fractionOfCells; //* gridManager.grid().size(0);
// 	  if (minRefine >0.0)
// 	  {
// 	    std::vector<double> eSort(errorDistribution);
// 	    std::sort(eSort.begin(),eSort.end(),fabscompare);
// 	    int minRefineIndex = minRefine*(eSort.size()-1);
// 	    double minErrLevel = fabs(eSort[minRefineIndex])+1.0e-15;
// 	    if (minErrLevel<errLevel)
// 	      errLevel = minErrLevel;
// 	  }

	  size_t maxNoOfVertices =/* 500000*/ /*2000000*//*12000*/30000;
	  size_t maxNoOfElements = 2000000;
	  int maxLevel = /*20*/6 /*25*/ /*18*/; // additional refinement levels

	  if( errNorm > overallTol && refinement_count < nRefMax
	    && gridManager.grid().size(/*dim*/0) < /*maxNoOfVertices*/maxNoOfElements )
	    //current setup: for 2D BFGS; for 3D fibrillation: use maxNoOfElements
	  {
// 	    accurate = false;
// 	    std::vector<bool> toRefine( is.size(0), false ) ;
// 	    size_t noCells = gridManager.grid().size(0);
// // 	    std::cout << is.size(0) << "   " << noCells << "\n"; std::cout.flush();
// 	    for( size_t cno = 0 ; cno < noCells ; cno++ )
// 	    {
// 	      if (fabs(errorDistribution[cno]) >= alpha*errLevel)
// 	      {
// 		noToRefine++;
// 		toRefine[cno] = true ;
// 	      }
// 	    }
	    std::vector<bool> toRefine( is.size(0), false ) ; //for adaptivity in compression

	    accurate = false ;
	    size_t noCells = gridManager.grid().size(0);

// 	    // Nagaiah paper:
// 	    unsigned minLevel = 10 ; // do not coarsen below that level
// 	    CellIterator ciEnd = ansatzVars.gridView.template end<0>() ;
// 	    for (CellIterator ci=ansatzVars.gridView.template begin<0>(); ci!=ciEnd; ++ci)
// 	    {
// 	      double vol = sqrt( ci->geometry().volume() );
// 	      if (fabs(errorDistribution[is.index(*ci)])/vol >= 0.2 /*0.1*/ )
// 	      {
// 		if( ci->level() < maxLevel && gridManager.grid().size(dim) < maxNoOfVertices )
// 		{
// 		  gridManager.mark(1,*ci);
// 		  noToRefine++;
// 		}
// 	      }
// 	      else if (fabs(errorDistribution[is.index(*ci)])/vol < 0.1 /* /2 */ && ci->level() > minLevel )
// 	      {
// 		gridManager.mark(-1,*ci);
// 		noToCoarsen++;
// 	      }
// 	    }
// 	    std::cout << "refine: " << noToRefine << "    coarsen: " << noToCoarsen << "\n";
// 	    gridManager.countMarked();

// 	    last implementation was the following:
	    unsigned long noToRefineOld = 0;
	    do{
	      noToRefineOld = noToRefine;
	      for( size_t cno = 0 ; cno < noCells ; cno++ )
	      {
		      if (fabs(errorDistribution[cno]) >= alpha*errLevel)
		      { // TODO: check and bugfix this!
			      if( !toRefine[cno] ) noToRefine++;
			      toRefine[cno] = true ;
		      }
	      }
// 		std::cout << "alpha: " << alpha << "\trefine: " << noToRefine << "\n";
// 		std::cout.flush();
		alpha *= 0.75; // refine more aggressively: reduce alpha by larger amount
	    } while ( noToRefine < fractionOfCells * noCells && (!noToRefineOld == noToRefine) ) ;
//   	    std::cout << "to refine: " << noToRefine << "\n";
	    std::cout.flush();

// 	    alpha = 0.5; //50
// 	    do {
// 	      for( size_t cno = 0 ; cno < noCells ; cno++ )
// 		if (fabs(errorDistribution[cno]) > alpha*maxErr/*overallTol/noCells*/) {
// 		    /*if( !toRefine[cno] )*/ noToRefine++;
// 		    toRefine[cno] = true;
// 		  }
//
// 		  alpha *=0.9;
// 	    } while (noToRefine < fractionOfCells * noCells);

// 	    long int nRefined = (long int) std::count( toRefine.begin(), toRefine.end(), true );
// 	    std::cout << "   noToRefine = " << nRefined << std::endl;
// 	    refinements.push_back(toRefine);

	    for (CellIterator ci=ansatzVars.gridView.template begin<0>(); ci!=ansatzVars.gridView.template end<0>(); ++ci)
	      if( toRefine[is.index(*ci)] && ci->level() < maxLevel )  gridManager.mark(1,*ci);

	    bool refok = gridManager.adaptAtOnce();  // something has been refined?
	    if( !refok )
	    {
	      // nothing has been refined
	      std::cout << "nothing has been refined (probably due to maxLevel constraint), stopping\n";
	      accurate = true ;
	    }

	    if (!accurate) {
	      refinement_count++ ;
	      nnz = assembler.nnz(0,neq,0,nvars,false);
	      size = ansatzVars.degreesOfFreedom(0,nvars);
	      rhs.resize(size);
	      sol.resize(size);
	    }
	  }
	  else
	  {
	   if( errNorm > overallTol && refinement_count > nRefMax )
	     std::cout << "max no of refinements exceeded\n";
// 	   if( errNorm > overallTol && gridManager.grid().size(2) > maxNoOfVertices )
// 	     std::cout << "max no of nodes exceeded\n";

	   accurate = true ;
	  }
        }
      } while (!accurate);
      adaptivityTime += (double)(timer.elapsed().user)/1e9;

//       std::cout << gridManager.grid().size(dim) << " nodes on " << gridManager.grid().maxLevel() << "levels\n";

      dxsum = dx;

      // propagate by linearly implicit Euler
      for (int j=1; j<=i; ++j) {
	// Assemble new right hand side tau*f(x_j)
        eq.time(eq.time()+tau);
	tmp = x; tmp += dxsum;
        State zero(x); zero /* * */= 0;
        timer.start();
// 	gridManager.enforceConcurrentReads(true);
// 	assembler.setNSimultaneousBlocks(40);
// 	assembler.setRowBlockFactor(2.0);
        assembler.assemble(Linearization(eq,tmp,x,zero),Assembler::RHS|Assembler::MATRIX,4);
        rhsAssemblyTime += (double)(timer.elapsed().user)/1e9;

	Op A(assembler);

	if( !iterative )
	{
	  timer.start();
	  //direct solution
	  A.getAssembler().toSequence(0,neq,rhs.begin());
	  MatrixAsTriplet<double> triplet = A.template get<MatrixAsTriplet<double> >();
	  Factorization<double> *matrix = 0;
	  switch (solverType) {
	    case DirectType::UMFPACK:
	      matrix = new UMFFactorization<double>(size,0,triplet.ridx,triplet.cidx,triplet.data);
	      break;
	    case DirectType::UMFPACK3264:
	      matrix = new UMFFactorization<double>(size,0,triplet.ridx,triplet.cidx,triplet.data);
	      break;
// 	    case MUMPS:
// 	      matrix = new MUMPSFactorization<double>(size,0,triplet.ridx,triplet.cidx,triplet.data);
// 	      break;
// 	    case SUPERLU:
// 	      matrix = new SUPERLUFactorization<double>(size,0,triplet.ridx,triplet.cidx,triplet.data);
// 	      break;
	    default:
	      throw -321;
	      break;
	  }
	  factorizationTime += (double)(timer.elapsed().user)/1e9;
	  timer.start();
	  matrix->solve(rhs,sol);
	  delete matrix;
	  for (int k=0; k<rhs.size(); ++k) assert(std::isfinite(rhs[k]));
	  for (int k=0; k<sol.size(); ++k) assert(std::isfinite(sol[k]));
	  dx.read(sol.begin());
	  solutionTime += (double)(timer.elapsed().user)/1e9;
	}
	else
	{
	  timer.start();
	  CoefficientVectors  solution(EvolutionEquation::AnsatzVars::template
		    CoefficientVectorRepresentation<0,neq>::init(ansatzVars.spaces) );
	  solution = 0;
	  // 	  assembler.assemble(linearization(F,x));
	  CoefficientVectors rhs(assembler.rhs());

	  ILUKPreconditioner<Op> p(A,fill_lev,0);
// 	  ILUTPreconditioner<Op> p(A,240,1e-2,0);
// 	  JacobiPreconditioner<Op> p(A,1.0);

	  Dune::BiCGSTABSolver<LinearSpaceX> cg(A,p,1e-7,2000,0); //verbosity: 0-1-2 (nothing-start/final-every it.)
	  Dune::InverseOperatorResult res;
	  cg.apply(solution,rhs,res);
	  if ( !(res.converged) || (res.iterations == 2001) ) {
	    std::cout << "   no of iterations in cg = " << res.iterations << std::endl;
	    std::cout << " convergence status of cg = " << res.converged  << std::endl;
	    assert(0);
	  }
	  dx.data = solution.data;
// 	// iterative solution
//         timer.start();
// 	typedef typename Assembler::template TestVariableRepresentation<>::type Rhs;
// 	typedef typename Assembler::template AnsatzVariableRepresentation<>::type Sol;
// 	Sol solution(Assembler::template AnsatzVariableRepresentation<>::init(assembler));
// 	Rhs rhside(Assembler::template TestVariableRepresentation<>::rhs(assembler));
// 	AssembledGalerkinOperator<Assembler,0,1,0,1> A(assembler, false);
// 	typename AssembledGalerkinOperator<Assembler,0,1,0,1>::matrix_type tri(A.getmat());
//
//
//         typedef typename Assembler::template TestVariableRepresentation<>::type LinearSpace;
// 	Dune::InverseOperatorResult res;
// 	solution = 1.0;
//      	JacobiPreconditioner<Assembler,0,1,0> p(assembler,1.0);
// // 	TrivialPreconditioner<LinearSpace> trivial;
// 	Dune::CGSolver<LinearSpace> cg(A,/*trivial*/p,iteEps,iteSteps,0);
// 	cg.apply(solution,rhside,res);
// 	A.applyscaleadd(-1.0,rhside,solution);
// 	double slntime = (double)(timer.elapsed().user)/1e9;
// 	solutionTime += slntime ;
// 	dx.data = solution.data ;
// 	// end: iterative solution
	}

        dxsum += dx;
        solutionTime += (double)(timer.elapsed().user)/1e9;
      }

      // insert into extrapolation tableau
      extrap.push_back(dxsum,stepFractions[i]);

      // restore initial time
      eq.time(t);
    }

    return extrap.back();
  }

  /**
   * Estimates the time discretization error of the previously
   * computed step by taking the difference between the diagonal and
   * subdiagonal extrapolation values of maximal order. This requires
   * that order>1 has been given for the last step.
   */
  std::vector<std::pair<double,double> > estimateError(State const& x,int i, int j) const
  {
    assert(extrap.size()>1);

    std::vector<std::pair<double,double> > e(ansatzVars.noOfVariables);

    relativeError(typename EvolutionEquation::AnsatzVars::Variables(),extrap[i].data,
                  extrap[j].data,x.data,
                  ansatzVars.spaces,eq.scaling(),e.begin());

    return e;
  }

  template <class OutStream>
  void reportTime(OutStream& out) const {
    out << "Limex time: " << matrixAssemblyTime << "s matrix assembly\n"
        << "            " << rhsAssemblyTime << "s rhs assembly\n"
        << "            " << factorizationTime << "s factorization\n"
        << "            " << solutionTime << "s solution\n"
	<< "            " << adaptivityTime << "s adaptivity\n";
  }

  void advanceTime(double dt) { eq.time(eq.time()+dt); }

private:
  GridManager<typename EvolutionEquation::AnsatzVars::Grid>& gridManager;
  typename EvolutionEquation::AnsatzVars const& ansatzVars;
  SemiImplicitEulerStep<EvolutionEquation>      eq;
  Assembler                                     assembler;
  int iteSteps ;
  double iteEps ;

public:
  ExtrapolationTableau<State>  extrap;
  double rhsAssemblyTime, matrixAssemblyTime, factorizationTime, solutionTime, adaptivityTime;
  DirectType solverType;
};

} //namespace Kaskade
#endif


#ifndef SCALARREWRITE_HPP
#define SCALARREWRITE_HPP

#include "scalarbase.hpp"
#include "exprrewrite.hpp"
#include "exprmatch.hpp"
#include <algorithm>
//#include <boost/logiclogic/tribool>
#include "scalarrange.hpp"

bool isgenexp(const expr &e) {
	if (e.isleaf()) {
		if (e.asleaf().type()==typeid(matchleaf)) {
			matchleaf ml = MYany_cast<matchleaf>(e.asleaf());
			if (std::dynamic_pointer_cast<matchany>(ml)
			    || std::dynamic_pointer_cast<matchvar>(ml)
			    || std::dynamic_pointer_cast<matchconstwrt>(ml)
			    || std::dynamic_pointer_cast<matchnonconstwrt>(ml))
				return true;
		}
		return false;
	}
	auto n = std::dynamic_pointer_cast<matchlabelop>(e.asnode());
	if (!n) return false;
	if (e.children().size()!=1) return false;
	return isgenexp(e.children()[0]);
}

expr chainpatternmod(const expr &ex) {
	return ex.map([](const expr &e) {
			if (e.isleaf()) return optional<expr>{};
			auto n = e.asnode();
			if (n==pluschain || n==multiplieschain) {
				auto bop = std::dynamic_pointer_cast<opchain>(n)->baseop;
				auto ch = e.children();
				auto last = ch.back();
				if (isgenexp(last))
					return optional<expr>{in_place,std::make_shared<matchassocop>(std::make_shared<matchremainderop>(bop)),
							ch};
				else return optional<expr>{in_place,std::make_shared<matchassocop>(bop),
							ch};
			}
			return optional<expr>{};
		});
}

struct sortchildren : public rewriterule {
	virtual ruleptr clone() const { return std::make_shared<sortchildren>(*this); }
	optional<vset> vars;
	std::vector<op> cops;

	sortchildren(std::vector<op> comops)
		: cops(comops), vars{} { }
	sortchildren(std::vector<op> comops, vset v)
		: cops(comops), vars(in_place,std::move(v)) {}

	int typeorder(const expr &e) const {
		// TODO: use map/unordered_map
		if (isconst(e)) return 0;
		int add = (isconstexpr(e,vars) ? 0 : 13);
		if (e.isleaf()) return 1+add;
		auto &op = e.asnode();
		if (op==plusop || op==pluschain) return 2+add;
		if (op==multipliesop || op==multiplieschain) return 3+add;
		if (op==powerop) return 4+add;
		if (op==logop) return 5+add;
		//if (op==switchop) return 6+add;
		if (op==condop) return 7+add;
		if (op==condeqop) return 8+add;
		if (op==absop) return 9+add;
		if (op==derivop) return 10+add;
		if (op==integrateop) return 11+add;
		if (op==evalatop) return 12+add;
		return 13+add;
	}

	virtual void setvars(const vset &v) {
		vars = v;
	}

	int secondordering(const expr &e1, const expr &e2) const {
		if (isconst(e1)) {
			scalarreal v1 = getconst<scalarreal>(e1);
			scalarreal v2 = getconst<scalarreal>(e2);
			if (v1<v2) return -1;
			if (v1>v2) return +1;
			return 0;
		}
		if (isvar(e1))
			return MYany_cast<var>(e1.asleaf())->name
					.compare(MYany_cast<var>(e2.asleaf())->name);
		if (e1.isleaf()) return 0;
		auto &ch1 = e1.children();
		auto &ch2 = e2.children();
		for(int i=std::min(ch1.size(),ch2.size())-1;i>=0;i--) {
			int res = exprcmp(ch1[i],ch2[i]);
			if (res!=0) return res;
		}
		return ch1.size()-ch2.size();
	}

	int exprcmp(const expr &e1, const expr &e2) const {
		int o1 = typeorder(e1), o2 = typeorder(e2);
		if (o1!=o2) return o1-o2;
		return secondordering(e1,e2);
	}

	virtual optional<expr> apply(const expr &e) const {
		if (e.isleaf()) return {};
		if (std::find(cops.begin(),cops.end(),e.asnode())==cops.end())
			return {};
		auto &ch = e.children();
		if (ch.size()<2) return {};
		for(int i=1;i<ch.size();i++) {
			if (exprcmp(ch[i-1],ch[i])>0) {
				std::vector<expr> che = ch;
				std::sort(che.begin(),che.end(),
						[this](const expr &e1, const expr &e2) {
							return exprcmp(e1,e2)<0;
						}
				);
				return optional<expr>{in_place,e.asnode(),che};
			}
		}
		return {};
	}
};

scalarset<scalarreal> rangeprop(const expr &e) {
	if (isconst(e)) {
		return {getconst<scalarreal>(e)};
	}
	if (isop(e,pluschain)) {
		auto &ch = e.children();
		if (ch.empty()) return {-std::numeric_limits<scalarreal>::infinity(),
					std::numeric_limits<scalarreal>::infinity()};
		auto ret = rangeprop(ch[0]);
		for(int i=1;i<ch.size();i++)
			ret = ret.combine(rangeprop(ch[i]),
					[](const range<scalarreal> &a, const range<scalarreal> &b) {
						return range<scalarreal>{a.first+b.first,
									a.second+b.second}; });
		return ret;
	}
	if (isop(e,multiplieschain)) {
		auto &ch = e.children();
		if (ch.empty()) return {-std::numeric_limits<scalarreal>::infinity(),
					std::numeric_limits<scalarreal>::infinity()};
		auto ret = rangeprop(ch[0]);
		for(int i=1;i<ch.size();i++)
			ret = ret.combine(rangeprop(ch[i]),
					[](const range<scalarreal> &a, const range<scalarreal> &b) {
						auto v1=a.first*b.first, v2=a.second*b.second,
							v3 = a.first*b.second, v4=a.second*b.first;
						return range<scalarreal>{
							std::min(std::min(v1,v2),std::min(v3,v4)),
							std::max(std::max(v1,v2),std::max(v3,v4))};
							});
		return ret;
	}
	if (isop(e,absop)) {
		return rangeprop(e.children()[0]).modify(
				[](const range<scalarreal> &a) {
					if (a.first>=0) return a;
					if (a.second<=0) return range<scalarreal>{-a.second,-a.first};
					return range<scalarreal>{scalarreal{0},std::max(-a.first,a.second)};
					});
	}
	if (isop(e,powerop)) {
		return rangeprop(e.children()[0]).combinemult(
				rangeprop(e.children()[1]),
				[](const range<scalarreal> &b, const range<scalarreal> &p) {
					scalarset<scalarreal> ret;
					limpt<scalarreal> zero(scalarreal{0});
					if (b.first>zero) {
						auto v1=pow(b.first,p.first),
							v2=pow(b.second,p.second),
							v3=pow(b.first,p.second),
							v4=pow(b.second,p.first);
						ret.x.emplace(
							std::min(std::min(v1,v2),std::min(v3,v4)),
							std::max(std::max(v1,v2),std::max(v3,v4)));
						return ret;
					}
					if (b.second>zero) {
						auto v1=pow(zero,p.first),
							v2=pow(b.second,p.second),
							v3=pow(zero,p.second),
							v4=pow(b.second,p.first);
						v1.closed = v3.closed = false;
						ret.x.emplace(
							std::min(std::min(v1,v2),std::min(v3,v4)),
							std::max(std::max(v1,v2),std::max(v3,v4)));
					}
					// what to do with negative base???
					if (p.first.pt==p.second.pt) { // single exponent
						if (p.first.pt.isint()) {
							auto v1 = pow(zero,p.first);
							auto v2 = pow(b.first,p.first);
							ret.x.emplace(v1,v2);
						}
					} else {
						limpt<scalarreal> b2 = b.second.pt>=zero ? zero :
								(b.second.pt.isint() ? b.second :
									(limpt<scalarreal>{
										b.first.pt.floor()}));
						limpt<scalarreal> b1 = b2-limpt<scalarreal>{scalarreal{1}};
						if (b1>b.first) {
							auto v1 = pow(b2,p.first);
							auto v2 = pow(b1,p.first);
							auto v3 = pow(b2,p.second);
							auto v4 = pow(b1,p.second);
							ret.x.emplace(
								std::min(std::min(v1,v2),
									std::min(v3,v4)),
								std::max(std::max(v1,v2),
									std::max(v3,v4)));
						} else {
							auto v1 = pow(b2,p.first);
							auto v3 = pow(b2,p.second);
							ret.x.emplace(v1,v3);
						}
					}
					/* not sure why this was the code... seems very wrong
					 * p not even mentioned!
					 */
					/*
					for(auto ip = b.first.closed
								|| !b.first.pt.iseven() ?
							ceil(b.first.pt/2)*2
							: ceil(b.first.pt/2+1)*2;
							ip<0 && b.second>ip;ip+=2) {
						std::cout << "looking at " << tostring(b.first.pt) << ' ' << tostring(b.second.pt) << ' ' << tostring(ip) << std::endl;
						ret.x.emplace(
							b.first<=0 && b.second>=0 ?
								scalarreal{0} : pow(
									std::min(abs(b.first),
										abs(b.second)),ip),
							pow(std::max(abs(b.first),
									abs(b.second)),ip));
					}
					*/

				});
	}
	if (isop(e,logop)) {
		return rangeprop(e.children()[0]).modify(
				[](range<scalarreal> a) {
					a.first.pt = log(a.first.pt);
					a.second.pt = log(a.second.pt);
					return a;
					});
	}
	if (isop(e,heavisideop)) {
		return rangeprop(e.children()[0]).modify(
				[](const range<scalarreal> &a) {
					return range<scalarreal>{a.first < 0 ? 0 : 1,
								a.second < 0 ? 0 : 1};
					return a;
				});
	}
	if (isop(e,diracop)) {
		return rangeprop(e.children()[0]).modify(
				[](const range<scalarreal> &a) {
					return range<scalarreal>{scalarreal{0},
						a.overlap(range<scalarreal>(0,0)) ?
							std::numeric_limits<scalarreal>::infinity()
							: scalarreal{0}};
				});
	}
	return {-std::numeric_limits<scalarreal>::infinity(),
					std::numeric_limits<scalarreal>::infinity()};
}

// perhaps these should do their own simplification first...

bool ispos(const expr &e) {
	auto rs = rangeprop(e);
	if (rs.x.empty()) return false; //???
	for(auto &r : rs.x)
		if (r.first<=0) return false;
	return true;
}
bool isneg(const expr &e) {
	auto rs = rangeprop(e);
	if (rs.x.empty()) return false; //???
	for(auto &r : rs.x)
		if (r.second>=0) return false;
	return true;
}
bool isnonneg(const expr &e) {
	auto rs = rangeprop(e);
	if (rs.x.empty()) return false; //???
	for(auto &r : rs.x)
		if (r.first<0) return false;
	return true;
}
bool isnonpos(const expr &e) {
	auto rs = rangeprop(e);
	if (rs.x.empty()) return false; //???
	for(auto &r : rs.x)
		if (r.second>0) return false;
	return true;
}
bool isconst(const expr &e, scalarreal k) {
	auto rs = rangeprop(e);
	if (rs.x.empty()) return false; //???
	for(auto &r : rs.x)
		if (r.first!=k || r.second!=k) return false;
	return true;
}
bool iseven(const expr &e) {
	auto rs = rangeprop(e);
	if (rs.x.empty()) return false; //???
	for(auto &r : rs.x)
		if (r.first!=r.second || !r.first.pt.iseven()) return false;
	return true;
}
bool isodd(const expr &e) {
	auto rs = rangeprop(e);
	if (rs.x.empty()) return false; //???
	for(auto &r : rs.x)
		if (r.first!=r.second || !r.first.pt.isodd()) return false;
	return true;
}

struct simpcond : public rewriterule {
	virtual ruleptr clone() const { return std::make_shared<simpcond>(*this); }
	virtual optional<expr> apply(const expr &e) const {
		if (!isop(e,condop) && !isop(e,condeqop)) return {};
		auto rngset = rangeprop(e.children()[0]);
		if (isop(e,condop)) {
			bool hasless=false, hasgreq=false;
			for(auto &rng : rngset.x) {
				if (rng.first<0) {
					hasless=true;
					if (hasgreq) break;
				}
				if (rng.second>=0) {
					hasgreq=true;
					if (hasless) break;
				}
			}
			if (!hasgreq) return optional<expr>{in_place,e.children()[1]};
			if (!hasless) return optional<expr>{in_place,e.children()[2]};
			return {};
		} else {
			bool hasnotzero=false, haszero=false;
			for(auto &rng : rngset.x) {
				if (rng.first!=0 || rng.second!=0) {
					hasnotzero=true;
					if (haszero) break;
				}
				if (rng.first<=0 && rng.second>=0) {
					haszero=true;
					if (hasnotzero) break;
				}
			}
			if (!hasnotzero) return optional<expr>{in_place,e.children()[1]};
			if (!haszero) return optional<expr>{in_place,e.children()[2]};
			return {};
		}
	}
};

/*
struct normswitch : public rewriterule {
	virtual ruleptr clone() const { return std::make_shared<normswitch>(*this); }
	virtual optional<expr> apply(const expr &e) const {
		if (!isop(e,switchop)) return {};
		auto &c0 = e.children()[0];
		expr tosub = scalar(0);
		if (c0.isleaf()) {
			if (!isconst(c0) || getconst<scalarreal>(c0)==0)
				return {};
			tosub = c0;
		} else {
			if (!isop(c0,pluschain) || c0.children().empty()
					|| !isconst(c0.children()[0])
					|| getconst<scalarreal>(c0.children()[0])==0)
				return {};
			tosub = c0.children()[0];
		}
		std::vector<expr> newch(e.children());
		for (int i=0;i<newch.size();i+=2)
			newch[i] = newch[i] - tosub;
		return optional<expr>{in_place,e.asnode(),newch};
	}
};

struct liftswitch : public rewriterule {
	virtual ruleptr clone() const { return std::make_shared<liftswitch>(*this); }
	static expr replacech(const expr &e, int chi, const expr &ch) {
		auto &ech = e.children();
		std::vector<expr> newch;
		newch.reserve(ech.size());
		for(int i=0;i<ech.size();i++)
			if (i==chi) newch.emplace_back(ch);
			else newch.emplace_back(ech[i]);
		return {e.asnode(),newch};
	}

	virtual optional<expr> apply(const expr &e) const {
		if (e.isleaf() || e.asnode()==switchop) return {};
		auto &ch = e.children();
		for(int i=0;i<ch.size();i++)
			if (isop(ch[i],switchop)) {
				auto &sch = ch[i].children();
				std::vector<expr> newch;
				for(int j=0;j<sch.size();j+=2) {
					newch.emplace_back(sch[j]);
					newch.emplace_back(replacech(e,i,sch[j+1]));
				}
				return optional<expr>{in_place,switchop,newch};
			}
		return {};
	}
};

struct squeezeswitch : public rewriterule {
	virtual ruleptr clone() const { return std::make_shared<squeezeswitch>(*this); }
	virtual optional<expr> apply(expr &e) const {
		if (!isop(e,switchop)) return {};
		auto &ch = e.children();
		if (ch.size()<5) {
			if (ch[1]==ch[3]) // technically, this might expand
				// the domain (if ch[0] is undefined in places)
				return optional<expr>{in_place,ch[1]};
			return {};
		}
		for(int i=2;i<ch.size()-1;i+=2)
			if (ch[i-1]==ch[i+1]) {
				std::vector<expr> newch;
				newch.reserve(ch.size()-2);
				for(int j=0;j<i;j++) newch.emplace_back(ch[j]);
				for(int j=i+2;j<ch.size();j++) newch.emplace_back(ch[j]);
				return optional<expr>{in_place,e.asnode(),newch};
			}
		return {};
	}
};

struct mergeswitch : public rewriterule {
	virtual ruleptr clone() const { return std::make_shared<mergeswitch>(*this); }
	static bool ismergeable(const expr &e, const expr &te, bool checkte=true) {
		if (!isop(e,switchop)) return false;
		auto &ch = e.children();
		if (checkte && !(ch[0]==te)) return false;
		scalarreal lastk = 0;
		for(int i=2;i<ch.size();i+=2) {
			if (!isconst(ch[i])) return false;
			if (i>2) {
				scalarreal newk = getconst<scalarreal>(ch[i]);
				if (newk<lastk) return false;
				lastk = newk;
			}
		}
		return true;
	}

	virtual optional<expr> apply(expr &e) const {
		if (!ismergeable(e,e,false)) return {};
		auto &ch = e.children();
		int mchi = -1;
		scalarreal preth = -std::numeric_limits<scalarreal>::infinity();
		scalarreal postth = std::numeric_limits<scalarreal>::infinity();
		for(int i=1;i<ch.size();i+=2)
			if (ismergeable(ch[i],ch[0])) {
				mchi = i;
				if (i>1) preth = getconst<scalarreal>(ch[i-1]);
				if (i+1 < ch.size()) postth = getconst<scalarreal>(ch[i+1]);
				break;
			}
		if (mchi==-1) return {};

		auto &mch = ch[mchi].children();
		std::vector<expr> newch;
		for(int i=0;i<mchi;i++)
			newch.emplace_back(ch[i]);
		newch.reserve(ch.size()+mch.size()-2);
		for(int i=1;i<mch.size();i+=2) {
			scalarreal th = (i+1<mch.size() ? getconst<scalarreal>(mch[i+1])
							: std::numeric_limits<scalarreal>::infinity());
			if (th > preth) {
				newch.emplace_back(mch[i]);
				if (th < postth && i+1<mch.size()) newch.emplace_back(mch[i+1]);
				else break;
			}
		}
		for(int i=mchi+1;i<ch.size();i++)
			newch.emplace_back(ch[i]);
		return optional<expr>{in_place,e.asnode(),newch};
	}
};
*/

ruleptr SRR(const expr &s, const expr &p) {
	return SR(chainpatternmod(s),p);
}

template<typename F>
ruleptr SRR(const expr &s, const expr &p, F &&f) {
	return SR(chainpatternmod(s),p,std::forward<F>(f));
}

template<typename T>
struct bigopexpand : public rewriterule {
	virtual ruleptr clone() const { return std::make_shared<bigopexpand>(*this); }
	int nterms;
	op bigop, chainop;
	T id;

	bigopexpand(int n, op bop, op cop, T i) : nterms(n), bigop(bop), chainop(cop), id(i) {}

	virtual optional<expr> apply(const expr &e) const {
		if (!isop(e,bigop)) return {};
		auto &ch = e.children();
		if (!isconst(ch[2]) || !isconst(ch[3])) return {};
		scalarreal x0 = getconst<scalarreal>(ch[2]);
		scalarreal x1 = getconst<scalarreal>(ch[3]);
		if (x1 < x0) return optional<expr>{in_place,scalar(id)};
		if (x1 >= x0+nterms) return {};
		std::vector<expr> terms;
		for(scalarreal x=x0;x<=x1;x+=1)
			terms.emplace_back(substitute(ch[1],ch[0],scalar(x)));
		return optional<expr>{in_place,chainop,terms};
	}
};

expr x_ = newvar<scalarreal>("x_");
expr notx_{makematchleaf<matchconstwrt>(vset{{getvar(x_)}})};
expr k1_ = L(1,notx_);
expr k2_ = L(2,notx_);
expr k3_ = L(3,notx_);
expr k4_ = L(4,notx_);
expr k5_ = L(5,notx_);
expr k6_ = L(6,notx_);
expr k7_ = L(7,notx_);
expr k8_ = L(8,notx_);
expr k9_ = L(9,notx_);

struct tableintegrate : public rewriterule {
	virtual ruleptr clone() const { return std::make_shared<tableintegrate>(*this); }
	std::vector<std::pair<expr,expr>> antiderivs;

	tableintegrate(std::vector<std::pair<expr,expr>> ader)
			: antiderivs(std::move(ader)) {}

	virtual optional<expr> apply(const expr &e) const {
		if (!isop(e,integrateop)) return {};
		auto &ch = e.children();
		expr newint = substitute(ch[1],ch[0],x_);
		for(auto &ad : antiderivs) {
			auto res = match(newint,ad.first);
			if (res) {
				expr ader = substitute(ad.second,*res);
				expr ret = substitute(ader,x_,ch[3])
					- substitute(ader,x_,ch[2]);
				return optional<expr>{in_place,std::move(ret)};
			}
		}
		return {};
	}
};

template<typename E1, typename E2>
std::pair<expr,expr> ADR(E1 &&e, E2 &&ad) {
	return std::make_pair(chainpatternmod(std::forward<E1>(e)),
			std::forward<E2>(ad));
}

std::vector<std::pair<expr,expr>> stdantiderivs
	{{
		 ADR(  k1_,
	            P1_*x_   ),

		 ADR(  x_,
		       x_*x_/2   ),

		 // TODO:
		 // when k1_<0 this doesn't work if the integral goes over the pole (@ x_=0)
		 // similar problem with the other rational function antiderivatives below!
		 ADR(  pow(x_,k1_),
			  ifeqthenelse(P1_+1,log(abs(x_)),pow(x_,P1_+1)/(P1_+1))   ),

		 ADR(  pow(x_ + k2_,k3_),
			  ifeqthenelse(P3_+1, log(abs(x_+P2_)),
							pow(x_+P2_,P3_+1)/(P3_+1))),

		 ADR(  pow(k1_*x_ + k2_,k3_),
			  ifeqthenelse(P1_,pow(P2_,P3_)*x_,
				  ifeqthenelse(P3_+1, log(abs(P1_*x_+P2_))/P1_,
					  			pow(P1_*x_+P2_,P3_+1)/(P3_+1)/P1_))),

		 // added to make example in proposal work... needs to be
		 // more general
		 ADR(  pow(k1_,k2_*x_),
				 ifeqthenelse(P2_,x_,
					 pow(P1_,P2_*x_)/(P2_*log(P1_)))),

	 }};

// all of the next few "helpers" should perhaps be moved elsewhere
// they may also need to be made more efficient and perhaps given
// their own algebraic symbols to be reasonable about directly
// (for instance, we probably don't want to be evaluating "factorial"
//  or "choose" directly)
expr factorial(const expr &n) {
	expr i = newvar<scalarreal>();
	return prod(i,i,scalar(1),n);
}

expr nchoosek(const expr &n, const expr k) {
	return factorial(n)/(factorial(k)*factorial(n-k));
}

expr B0(const expr &m) {
	expr k=newvar<scalarreal>(), v=newvar<scalarreal>();

	// pos series
	//return sum(sum(pow(scalar(-1),v)*nchoosek(k,v)*pow(v+1,m)/(k+1),v,scalar(0),k),k,scalar(0),m);
	// neg series
	return sum(sum(pow(scalar(-1),v)*nchoosek(k,v)*pow(v,m)/(k+1),v,scalar(0),k),k,scalar(0),m);
}

// Bernoulli polynomial
expr B(const expr &n, const expr &m) {
	expr k = newvar<scalarreal>();
	return sum(nchoosek(n,k)*B0(n-k)*pow(m,k),k,scalar(0),n);
}

expr psum(const expr &p, const expr &n) {
	expr k = newvar<scalarreal>();
	return sum(nchoosek(p,k)*B0(p-k)*pow(-1,p-k)/(k+1)*pow(n,k+1),k,scalar(0),p);
}

// TODO:  will need to be separated out into general and specific to scalars
//std::vector<ruleptr>

ruleset basicscalarrules
	{{toptr<trivialconsteval>(),
	  toptr<scopeeval>(),
	  toptr<sortchildren>(std::vector<op>{pluschain,multiplieschain}),

  SRR(E1_ - E2_                          ,  P1_ + -1*P2_                   ),
  SRR(-E1_                               ,  -1*P1_                         ),

  SRR(E1_ / E2_                          ,  P1_ * pow(P2_,-1)              ),

  /*
  SRR(E1_ + (E2_ + E3_)                  ,  P1_ + P2_ + P3_                  ),
  SRR( E1_ + (E2_ + E3_) + E4_           ,  P1_ + P2_ + P3_ + P4_            ),

  SRR(E1_ * (E2_ * E3_)                  ,  P1_ * P2_ * P3_                  ),
  SRR( E1_ * (E2_ * E3_) * E4_           ,  P1_ * P2_ * P3_ * P4_            ),
  */
  toptr<collapsechain>(pluschain,true),
  toptr<collapsechain>(multiplieschain,true),
  toptr<constchaineval>(pluschain),
  toptr<constchaineval>(multiplieschain),
  toptr<simpcond>(),
  /*
  toptr<normswitch>(),
  toptr<mergeswitch>(),
  toptr<squeezeswitch>(),
  toptr<liftswitch>(),
  */

  // some of these are only true "almost everywhere"
  // and might need to be removed for some applications
  // (or, we need a "domain" to be propagated with the expr)
  //
  SRR( 0 + E1_                          ,  P1_                             ),
  SRR( -0 + E1_                          ,  P1_                             ),
  SRR( 1 * E1_                          ,  P1_                             ),
  SRR( 0 * E1_                          ,  scalar(0)                   ),
  SRR( -0 * E1_                          ,  scalar(0)                   ),
  SRR( pow(E1_,1)                       ,  P1_                             ),
  SRR( pow(1,E1_)                       ,  scalar(1)                   ),
  SRR( pow(0,E1_)                       ,  ifthenelse(P1_,
				scalar(std::numeric_limits<scalarreal>::infinity()),
				scalar(0))                                           ),
  SRR( pow(E1_,0)                       ,  scalar(1)                   ),

  SRR( E1_ + E1_                          ,  2*P1_                           ),
  SRR( E1_ + E1_ + E2_                    ,  2*P1_ + P2_                     ),

  SRR( E1_ * E1_                          ,  pow(P1_,2)                    ),
  SRR( E1_ * E1_ * E2_                    ,  pow(P1_,2)*P2_                ),

  SRR( log(pow(E1_,E2_))                  ,  P2_*log(P1_)                    ),
  SRR( log(E1_*E2_)                       ,  log(P1_) + log(P2_)             ),

  SRR( (W2_ + W3_)*E1_                    ,  P1_*P2_ + P1_*P3_               ),
  SRR( (K2_ + W3_)*E1_                    ,  P1_*P2_ + P1_*P3_               ),
  SRR( (W2_ + K3_)*E1_                    ,  P1_*P2_ + P1_*P3_               ),

  SRR( K1_*E2_ + K3_*E2_                  ,  (P1_+P3_) * P2_                 ),
  SRR( K1_*E2_ + K3_*E2_ + E4_            ,  (P1_+P3_) * P2_ + P4_           ),
  SRR( E2_ + K3_*E2_                      ,  (scalar(1)+P3_) * P2_       ),
  SRR( E2_ + K3_*E2_ + E4_                ,  (scalar(1)+P3_) * P2_ + P4_ ),

  SRR( pow(E1_,E2_) * pow(E1_,E3_)        ,  pow(P1_,P2_+P3_)                ),
  SRR( pow(E1_,E2_) * pow(E1_,E3_) * E4_  ,  pow(P1_,P2_+P3_)*P4_            ),
  SRR( E1_ * pow(E1_,E3_)                 ,  pow(P1_,scalar(1)+P3_)      ),
  SRR( E1_ * pow(E1_,E3_) * E4_           ,  pow(P1_,scalar(1)+P3_)*P4_  ),

  SRR( pow(pow(W1_,E2_),E3_)              ,  pow(P1_,P2_*P3_)                ),
  SRR( pow(pow(K1_,W2_),W3_)              ,  pow(P1_,P2_*P3_)                ),
  SRR( pow(pow(K1_,W2_),K3_)              ,  pow(pow(P1_,P3_),P2_)           ),
  SRR( pow(K1_,K2_*E3_)                   ,  pow(pow(P1_,P2_),P3_)           ),

  SRR( pow(E1_*W2_,E3_)                   ,  pow(P1_,P3_)*pow(P2_,P3_)       ),
  SRR( pow(W1_*E2_,E3_)                   ,  pow(P1_,P3_)*pow(P2_,P3_)       ),
  SRR( pow(K1_,E3_)*pow(K2_,E3_)          ,  pow(P1_*P2_,P3_)                ),

  SRR( log(ifthenelse(E1_,E2_,E3_))    ,  ifthenelse(P1_,log(P2_),log(P3_))  ),

  SRR( abs(E1_)                           ,  P1_ ,
   [](const exprmap &m) { return isnonneg(m.at(1)); } ),

  SRR( abs(E1_)                           ,  -P1_ ,
   [](const exprmap &m) { return isnonpos(m.at(1)); } ),
	 }};

ruleset derivscalarrules{{
  SRR( deriv(E1_,V2_,V3_)                 ,  scalar(0),
      [](const exprmap &m) { return isconstexpr(m.at(1),getvar(m.at(2))); }),
  SRR( deriv(V1_,V1_,V3_)                 ,  scalar(1)                   ),

  SRR( deriv(E1_+E2_,V3_,V4_)             ,
		                           deriv(P1_,P3_,P4_) + deriv(P2_,P3_,P4_) ),

  SRR( deriv(E1_*E2_,V3_,V4_)             ,
		     deriv(P1_,P3_,P4_)*evalat(P2_,P3_,P4_)
		  + evalat(P1_,P3_,P4_)* deriv(P2_,P3_,P4_)                        ),

  SRR( deriv(pow(E1_,E2_),V3_,V4_)        ,
	  evalat(pow(P1_,P2_),P3_,P4_)*evalat(log(P1_),P3_,P4_)*deriv(P2_,P3_,P4_)
   + evalat(P2_,P3_,P4_)*evalat(pow(P1_,P2_-1),P3_,P4_)*deriv(P1_,P3_,P4_) ),

  SRR( deriv(log(E1_),V2_,V3_)            ,
		                          deriv(P1_,P2_,P3_) / evalat(P1_,P2_,P3_) ),

  SRR( deriv(abs(E1_),V2_,V3_)            ,
		  deriv(P1_,P2_,P3_)*ifthenelse(P1_,scalar(-1),scalar(1)) ),

  SRR( deriv(expr{heavisideop,E1_},V2_,V3_),
				 evalat(expr{diracop,P1_},P2_,P3_)*deriv(P1_,P2_,P3_) ),

  // not sure this is right with diracop
  SRR( deriv(ifthenelse(E1_,E2_,E3_),V4_,V5_)   ,
		  evalat(expr{diracop,P1_},P4_,P5_)*deriv(P1_,P4_,P5_)*(E2_-E3_)
		  + ifthenelse(P1_,deriv(P2_,P4_,P5_),deriv(P3_,P4_,P5_))           ),

  SRR( deriv(ifeqthenelse(E1_,E2_,E3_),V4_,V5_)  ,
		  ifeqthenelse(evalat(P1_,P4_,P5_),
			  deriv(P2_,P4_,P5_),deriv(P3_,P4_,P5_))                       ),

  SRR( deriv(integrate(E1_,E2_,E3_,E4_),E5_,E6_)  ,
		  integrate(deriv(P1_,P5_,P6_),P2_,
					evalat(P3_,P5_,P6_),evalat(P4_,P5_,P6_))
		  + deriv(P4_,P5_,P6_)*evalat(evalat(P1_,P2_,P4_),P5_,P6_)
		  - deriv(P3_,P5_,P6_)*evalat(evalat(P1_,P2_,P3_),P5_,P6_)          ),
}};

ruleset integralscalarrules {{
  SRR( integrate(E1_+E2_,E3_,E4_,E5_), integrate(P1_,P3_,P4_,P5_) + integrate(P2_,P3_,P4_,P5_) ),

  SRR( integrate(E1_*E2_,E3_,E4_,E5_), P1_ * integrate(P2_,P3_,P4_,P5_) ,
  		[](const exprmap &m) { return isconstexpr(m.at(1),getvar(m.at(3))); } ),

  SRR( integrate(E1_*E2_,E3_,E4_,E5_), integrate(P1_,P3_,P4_,P5_) * P2_ ,
  		[](const exprmap &m) { return isconstexpr(m.at(2),getvar(m.at(3))); } ),

  SRR( integrate(ifthenelse(E1_+E2_*E3_,E4_,E5_),E3_,E6_,E7_) ,
		  ifthenelse(-P1_/P2_ - P6_,
			  		integrate(P5_,P3_,P6_,P7_),
					ifthenelse(-P1_/P2_ - P7_,
						integrate(P5_,P3_,P6_,-P1_/P2_)
						+ integrate(P4_,P3_,-P1_/P2_,P7_),
						integrate(P4_,P3_,P6_,P7_))),
		  [](const exprmap &m) {
		  	return isconstexpr(m.at(1),getvar(m.at(3)))
					&& isconstexpr(m.at(2),getvar(m.at(3))); } ),

  toptr<tableintegrate>(stdantiderivs),

  SRR( sum(E1_,V2_,E3_,E4_)               , scalar(0),
		  [](const exprmap &m) { return isneg(E4_-E3_); } ),
  SRR( prod(E1_,V2_,E3_,E4_)               , scalar(1),
		  [](const exprmap &m) { return isneg(E4_-E3_); } ),

  // perhaps need "max(0,P4_-P3_+1)"??
  SRR( sum(E1_,V2_,E3_,E4_)               , P1_*(P4_-P3_+1),
      [](const exprmap &m) { return isconstexpr(m.at(1),getvar(m.at(2))); }   ),

  SRR( sum(E1_*E2_,V3_,E4_,E5_)           , P1_*sum(P2_,P3_,P4_,P5_),
	 [](const exprmap &m) { return isconstexpr(m.at(1),getvar(m.at(3))); }   ),
  SRR( sum(E1_*E2_,V3_,E4_,E5_)           , sum(P1_,P3_,P4_,P5_)*P2_,
	 [](const exprmap &m) { return isconstexpr(m.at(2),getvar(m.at(3))); }   ),
}};

ruleset sumprodscalarrules {{
  toptr<bigopexpand<scalarreal>>(3,sumop,pluschain,0),
  toptr<bigopexpand<scalarreal>>(3,prodop,multiplieschain,1),

  SRR( sum(V2_,V2_,E3_,E4_)      , ifthenelse(P4_-P3_,scalar(0),(P4_+1-P3_)*(P4_+P3_)/2)                  ),
  SRR( sum((V2_+E1_),V2_,E3_,E4_) , ifthenelse(P4_-P3_,scalar(0),(P4_+1-P3_)*(P4_+P3_+2*P1_)/2)              ,
	  [](const exprmap &m) { return isconstexpr(m.at(1),getvar(m.at(2))); } ),

  SRR( sum(pow(V2_,2),V2_,E3_,E4_)
	  , ifthenelse(P4_-P3_,scalar(0),scalarreal{1,6}*(P4_-P3_+1) + scalarreal{1,2}*(pow(P4_,2)-pow(P3_-1,2))
	  		+ scalarreal{1,3}*(pow(P4_,3)-pow(P3_-1,3)))                   ),
  SRR( sum(pow(V2_+E1_,2),V2_,E3_,E4_)
	  , ifthenelse(P4_-P3_,scalar(0),scalarreal{1,6}*(P4_-P3_+1) + scalarreal{1,2}*(pow(P4_,2)-pow(P3_-1,2))
	  		+ scalarreal{1,3}*(pow(P4_,3)-pow(P3_-1,3))
			+ pow(P1_,2)*(P4_-P3_) + P1_*(pow(P4_,2)-pow(P3_,2))          ),
	  [](const exprmap &m) { return isconstexpr(m.at(1),getvar(m.at(2))); } ),

  // Faulhaber's formula
  SRR( sum(pow(V1_,E2_),V1_,E3_,E4_)      ,
	//	 			(B(P2_+1,P4_+1) - B(P2_+1,P3_+1))/(P2_+1) ,
		ifthenelse(P4_-P3_,scalar(0),psum(P2_,P4_) - psum(P2_,P3_-1)),
  	[](const exprmap &m) { return isconstexpr(m.at(2),getvar(m.at(1))); }   ),
  SRR( sum(pow((V1_+E5_),E2_),V1_,E3_,E4_)      ,
	//	 			(B(P2_+1,P4_+1) - B(P2_+1,P3_+1))/(P2_+1) ,
		ifthenelse(P4_-P3_,scalar(0),psum(P2_,P4_+P5_) - psum(P2_,P3_+P5_-1)),
  	[](const exprmap &m) { return isconstexpr(m.at(2),getvar(m.at(1)))
				&& isconstexpr(m.at(5),getvar(m.at(1))); }   ),
}};

ruleset numericevalrules {{
		  toptr<consteval>(),
	 }};

ruleset scalarruleset = basicscalarrules
			+ derivscalarrules
			+ integralscalarrules
			+ sumprodscalarrules
			+ numericevalrules;
#endif


#include <iomanip>
#include <Eigen/Dense>
#include <Exception.hh>
#include "HartreeFock.hh"
#include "HFConverger.hh"
#include "exceptions.hh"

const int HartreeFock::default_max_iter = 100;
const double HartreeFock::default_tolerance = 1.e-10;

HartreeFock::HartreeFock(): _max_iter(default_max_iter),
	_tolerance(default_tolerance), _conv_method("diis"),  _status(),
	_orbitals(), _orb_ener(), _density() {}

HartreeFock::HartreeFock(const Basis& basis, const Geometry& geometry,
	int multiplicity, bool restricted): _max_iter(default_max_iter),
	_tolerance(default_tolerance), _conv_method("diis"), _status(),
	_orbitals(), _orb_ener(), _density()
{
	iterate(basis, geometry, multiplicity, restricted);
}

void HartreeFock::iterate(const Basis& basis, const Geometry& geometry,
	int multiplicity, bool restricted)
{
	_status.reset();
	_restricted = restricted;
	setMultiplicity(geometry, multiplicity);

	double nuc_rep = geometry.nuclearRepulsion();
	std::cout << "nuclear repulsion = " << nuc_rep << "\n";

	Eigen::MatrixXd H = basis.kineticEnergy()
		+ basis.nuclearAttraction(geometry.positions(), geometry.charges());
	const Eigen::MatrixXd& S = basis.overlap();

	if (restricted)
		iterateRestricted(basis, H, S, nuc_rep);
	else
		iterateUnrestricted(basis, H, S, nuc_rep);
}

void HartreeFock::iterateRestricted(const Basis& basis,
	const Eigen::MatrixXd& H, const Eigen::MatrixXd& S,
	double nuc_rep)
{
	calcOrbitals(H, S);

	_energy = nuc_rep;
	if (_nr_double > 0) _energy += 2 * _orb_ener.head(_nr_double).sum();
	if (_nr_single > 0) _energy += _orb_ener.segment(_nr_double, _nr_single).sum();
	std::cout << "Energy: " << _energy << "\n";

	Eigen::MatrixXd J, K;
	HFConverger::Ptr converger = HFConverger::create(_conv_method, *this,
		basis);
	for (int iter = 0; iter < _max_iter; ++iter)
	{
		double last_energy = _energy;

		const Eigen::MatrixXd& D = density();
		basis.twoElectron(D, J, K);
		_energy = (H + 0.5*(J - 0.5*K)).cwiseProduct(D).sum() + nuc_rep;
		std::cout << std::setw(3) << iter << " "
			<< std::setw(20) << std::setprecision(15) << std::fixed << _energy << " "
			<< std::setw(13) << std::setprecision(6) << std::scientific << converger->error()
			<< "\n";
		//std::cout << "orbital energies: " << _orb_ener.transpose() << "\n";

		if (std::abs(_energy - last_energy) < _tolerance)
		{
			_status.set(ENERGY_CONVERGED);
			return;
		}

		Eigen::MatrixXd F = H + J - 0.5*K;
		converger->step(F, _energy);
		calcOrbitals(F, S);
	}

	throw NoConvergence();
}

void HartreeFock::iterateUnrestricted(const Basis& basis,
	const Eigen::MatrixXd& H, const Eigen::MatrixXd& S,
	double nuc_rep)
{
	int nr_func = basis.size();

	calcOrbitals(H, H, S);
	_energy = nuc_rep + _orb_ener.head(_nr_alpha).sum()
		+ _orb_ener.segment(nr_func, _nr_beta).sum();
	std::cout << "Energy: " << _energy << "\n";

	Eigen::MatrixXd Ja, Ka, Jb, Kb;
	HFConverger::Ptr converger = HFConverger::create(_conv_method, *this,
		basis);
	for (int iter = 0; iter < _max_iter; ++iter)
	{
		double last_energy = _energy;

		Eigen::MatrixXd::ConstColsBlockXpr Da = density().leftCols(nr_func);
		Eigen::MatrixXd::ConstColsBlockXpr Db = density().rightCols(nr_func);
		basis.twoElectron(Da, Ja, Ka);
		basis.twoElectron(Db, Jb, Kb);

		double energyA = (H + 0.5*(Ja+Jb-Ka)).cwiseProduct(Da).sum();
		double energyB = (H + 0.5*(Ja+Jb-Kb)).cwiseProduct(Db).sum();
		_energy = energyA + energyB + nuc_rep;

		std::cout << std::setw(3) << iter << " "
			<< std::setw(20) << std::setprecision(15) << std::fixed << _energy << " "
			<< std::setw(13) << std::setprecision(6) << std::scientific << converger->error() << "\n";
		//std::cout << "orbital energies: " << _orb_ener.transpose() << "\n";

		if (std::abs(_energy - last_energy) < _tolerance)
		{
			_status.set(ENERGY_CONVERGED);
			return;
		}

		Eigen::MatrixXd Fa = H + Ja + Jb - Ka;
		Eigen::MatrixXd Fb = H + Ja + Jb - Kb;
		converger->step(Fa, Fb, _energy);
		calcOrbitals(Fa, Fb, S);
	}

	throw NoConvergence();
}

double HartreeFock::energy()
{
	return _energy;
}

void HartreeFock::setMultiplicity(const Geometry& geometry, int multiplicity)
{
	int nr_elec = geometry.totalCharge();

	if (multiplicity < 1)
		// Use lowest possible spin multiplicity
		multiplicity = 1 + nr_elec % 2;
	else if (nr_elec % 2 == multiplicity % 2 || multiplicity > nr_elec+1)
		throw Li::Exception("Multiplicity does not match number of electrons");

	_nr_single = multiplicity - 1;
	_nr_double = (nr_elec - _nr_single) / 2;
	if (!_restricted)
	{
		_nr_alpha = _nr_double + _nr_single;
		_nr_beta = _nr_double;
	}
}

void HartreeFock::calcDensity()
{
	if (!_status.test(ORBITALS_CURRENT))
		throw Li::Exception("No orbitals to compute density matrix with");

	int nr_func = _orbitals.rows();
	if (_restricted)
	{
		_density.resize(nr_func, nr_func);
		for (int j = 0; j < nr_func; j++)
		{
			for (int i = 0; i <= j; i++)
			{
				double sum = 0.0;
				if (_nr_double)
					sum += 2 * _orbitals.row(i).head(_nr_double).dot(
						_orbitals.row(j).head(_nr_double));
				if (_nr_single)
					sum += _orbitals.row(i).segment(_nr_double, _nr_single).dot(
						_orbitals.row(j).segment(_nr_double, _nr_single));
				_density(j,i) = _density(i,j) = sum;
			}
		}
        }
        else
	{
		_density.resize(nr_func, 2*nr_func);
		if (_nr_alpha == 0)
		{
			_density.leftCols(nr_func).setZero();
		}
		else
		{
			Eigen::MatrixXd::ColsBlockXpr occ
				= _orbitals.leftCols(_nr_alpha);
			_density.leftCols(nr_func) = occ * occ.transpose();
		}
		if (_nr_beta == 0)
		{
			_density.rightCols(nr_func).setZero();
		}
		else
		{
			Eigen::Block<Eigen::MatrixXd> occ
				= _orbitals.block(0, nr_func, nr_func, _nr_beta);
			_density.rightCols(nr_func) = occ * occ.transpose();
		}
	}

	_status.set(DENSITY_CURRENT);
}

void HartreeFock::calcOrbitals(const Eigen::MatrixXd& F,
	const Eigen::MatrixXd& S)
{
	Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd> solver(F, S);
	_orbitals = solver.eigenvectors();
	_orb_ener = solver.eigenvalues();
	_status.set(ORBITALS_CURRENT);
	_status.reset(DENSITY_CURRENT);
}

void HartreeFock::calcOrbitals(const Eigen::MatrixXd& Fa,
	const Eigen::MatrixXd& Fb, const Eigen::MatrixXd& S)
{
	int nr_func = S.rows();
	_orbitals.resize(nr_func, 2*nr_func);
	_orb_ener.resize(2*nr_func);

	Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd> solver(Fa, S);
	_orbitals.leftCols(nr_func) = solver.eigenvectors();
	_orb_ener.head(nr_func) = solver.eigenvalues();
	solver.compute(Fb, S);
	_orbitals.rightCols(nr_func) = solver.eigenvectors();
	_orb_ener.tail(nr_func) = solver.eigenvalues();

	_status.set(ORBITALS_CURRENT);
	_status.reset(DENSITY_CURRENT);
}