/*
  Copyright 2011 Larry Gritz and the other authors and contributors.
  All Rights Reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are
  met:
  * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
  * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.
  * Neither the name of the software's owners nor the names of its
    contributors may be used to endorse or promote products derived from
    this software without specific prior written permission.
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

  (This is the Modified BSD License)
*/

/// \file
/// Implementation of ImageBufAlgo algorithms.

#include <iostream>
#include <cmath>

#include <boost/scoped_array.hpp>

#include <OpenEXR/ImathFun.h>
#include <OpenEXR/ImathColor.h>
using Imath::Color3f;

#include "fmath.h"
#include "imagebuf.h"
#include "imagebufalgo.h"
#include "dassert.h"

template<class T>
inline Imath::Vec3<T>
powf (const Imath::Vec3<T> &x, float y)
{
    return Imath::Vec3<T> (powf (x[0], y), powf (x[1], y), powf (x[2], y));
}

OIIO_NAMESPACE_ENTER
{

namespace
{

static bool
same_size (const ImageBuf &A, const ImageBuf &B)
{
    const ImageSpec &a (A.spec()), &b (B.spec());
    return (a.width == b.width && a.height == b.height &&
            a.depth == b.depth && a.nchannels == b.nchannels);
}

#define LAPLACIAN_MAX_LEVELS 8

class LaplacianPyramid
{
public:
    LaplacianPyramid (float *image, int _width, int _height)
        : w(_width), h(_height)
    {
        level[0].insert (level[0].begin(), image, image+w*h);
        for (int i = 1;  i < LAPLACIAN_MAX_LEVELS;  ++i)
            convolve (level[i], level[i-1]);
    }

    ~LaplacianPyramid () { }

    float value (int x, int y, int lev) const {
	return level[std::min (lev, LAPLACIAN_MAX_LEVELS-1)][y*w + x];
    }

private:
    int w, h;
    std::vector<float> level[LAPLACIAN_MAX_LEVELS];

    // convolve image b with the kernel and store it in a
    void convolve (std::vector<float> &a, const std::vector<float> &b) {
        const float kernel[] = {0.05f, 0.25f, 0.4f, 0.25f, 0.05f};
        a.resize (b.size());
        for (int y = 0, index = 0;  y < h;  ++y) {
            for (int x = 0;  x < w;  ++x, ++index) {
                a[index] = 0.0f;
                for (int i = -2;  i <= 2;  ++i) {
                    for (int j = -2;  j<= 2;  ++j) {
                        int nx = abs(x+i);
                        int ny = abs(y+j);
                        if (nx >= w)
                            nx=2*w-nx-1;
                        if (ny >= h)
                            ny=2*h-ny-1;
                        a[index] += kernel[i+2] * kernel[j+2] * b[ny * w + nx];
                    }
                }
            }
        }
    }
};

// Adobe RGB (1998) with reference white D65 -> XYZ
// matrix is from http://www.brucelindbloom.com/
inline Color3f
AdobeRGBToXYZ (const Color3f &rgb)
{
    return Color3f (rgb[0] * 0.576700f  + rgb[1] * 0.185556f  + rgb[2] * 0.188212f,
                    rgb[0] * 0.297361f  + rgb[1] * 0.627355f  + rgb[2] * 0.0752847f,
                    rgb[0] * 0.0270328f + rgb[1] * 0.0706879f + rgb[2] * 0.991248f);
}

/// Convert a color in XYZ space to LAB space.
///
static Color3f
XYZToLAB (const Color3f xyz)
{
    // Reference white point
    static const Color3f white (0.576700f + 0.185556f + 0.188212f,
                                0.297361f + 0.627355f + 0.0752847f,
                                0.0270328f + 0.0706879f + 0.991248f);
    const float epsilon = 216.0f / 24389.0f;
    const float kappa = 24389.0f / 27.0f;

    Color3f r = xyz / white;
    Color3f f;
    for (int i = 0; i < 3; i++) {
        if (r[i] > epsilon)
            f[i] = powf (r[i], 1.0f / 3.0f);
        else
            f[i] = (kappa * r[i] + 16.0f) / 116.0f;
    }
    return Color3f (116.0f * f[1] - 16.0f,    // L
                    500.0f * (f[0] - f[1]),   // A
                    200.0f * (f[1] - f[2]));  // B
}

// Contrast sensitivity function (Barten SPIE 1989)
static float
contrast_sensitivity (float cyclesperdegree, float luminance)
{
    float a = 440.0f * powf ((1.0f + 0.7f / luminance), -0.2f);
    float b = 0.3f * powf ((1.0f + 100.0f / luminance), 0.15f);
    return a * cyclesperdegree * expf(-b * cyclesperdegree)
             * sqrtf(1.0f + 0.06f * expf(b * cyclesperdegree));
}

// Visual Masking Function from Daly 1993
inline float
mask (float contrast)
{
    float a = powf (392.498f * contrast, 0.7f);
    float b = powf (0.0153f * a, 4.0f);
    return powf (1.0f + b, 0.25f);
}

// Given the adaptation luminance, this function returns the
// threshold of visibility in cd per m^2
// TVI means Threshold vs Intensity function
// This version comes from Ward Larson Siggraph 1997
static float
tvi (float adaptation_luminance)
{
    // returns the threshold luminance given the adaptation luminance
    // units are candelas per meter squared
    float r;
    float log_a = log10f(adaptation_luminance);
    if (log_a < -3.94f)
        r = -2.86f;
    else if (log_a < -1.44f)
        r = powf(0.405f * log_a + 1.6f , 2.18f) - 2.86f;
    else if (log_a < -0.0184f)
        r = log_a - 0.395f;
    else if (log_a < 1.9f)
        r = powf(0.249f * log_a + 0.65f, 2.7f) - 0.72f;
    else
        r = log_a - 1.255f;
    return powf (10.0f, r);
}

}

int
ImageBufAlgo::compare_Yee (const ImageBuf &img0, const ImageBuf &img1,
                           float luminance, float fov)
{
    const ImageSpec &spec (img0.spec());
    ASSERT (spec.format == TypeDesc::FLOAT);
    ASSERT (same_size (img0, img1));
    int nscanlines = spec.height * spec.depth;
    int npels = nscanlines * spec.width;

    bool luminanceOnly = false;

    // assuming colorspaces are in Adobe RGB (1998), convert to LAB
    boost::scoped_array<Color3f> aLAB (new Color3f[npels]);
    boost::scoped_array<Color3f> bLAB (new Color3f[npels]);
    boost::scoped_array<float> aLum (new float[npels]);
    boost::scoped_array<float> bLum (new float[npels]);
    ImageBuf::ConstIterator<float,float> pix0 (img0);
    ImageBuf::ConstIterator<float,float> pix1 (img1);
    for (int i = 0;  pix0.valid();  ++i, pix0++) {
        pix1.pos (pix0.x(), pix0.y());  // ensure alignment
        Color3f RGB, XYZ;
        RGB.setValue (pix0[0], pix0[1], pix0[2]);
        XYZ = AdobeRGBToXYZ (RGB);
        aLAB[i] = XYZToLAB (XYZ);
        aLum[i] = XYZ[1] * luminance;

        RGB.setValue (pix1[0], pix1[1], pix1[2]);
        XYZ = AdobeRGBToXYZ (RGB);
        bLAB[i] = XYZToLAB (XYZ);
        bLum[i] = XYZ[1] * luminance;
    }

    // Construct Laplacian pyramids
    LaplacianPyramid la (&aLum[0], spec.width, nscanlines);
    LaplacianPyramid lb (&bLum[0], spec.width, nscanlines);

    float num_one_degree_pixels = (float) (2 * tan(fov * 0.5 * M_PI / 180) * 180 / M_PI);
    float pixels_per_degree = spec.width / num_one_degree_pixels;

    unsigned int adaptation_level = 0;
    for (int i = 0, npixels = 1;
             i < LAPLACIAN_MAX_LEVELS && npixels <= num_one_degree_pixels;
             ++i, npixels *= 2)
        adaptation_level = i;

    float cpd[LAPLACIAN_MAX_LEVELS];
    cpd[0] = 0.5f * pixels_per_degree;
    for (int i = 1;  i < LAPLACIAN_MAX_LEVELS;  ++i)
        cpd[i] = 0.5f * cpd[i - 1];
    float csf_max = contrast_sensitivity (3.248f, 100.0f);

    float F_freq[LAPLACIAN_MAX_LEVELS - 2];
    for (int i = 0; i < LAPLACIAN_MAX_LEVELS - 2;  ++i)
        F_freq[i] = csf_max / contrast_sensitivity (cpd[i], 100.0f);

    unsigned int pixels_failed = 0;
    for (int y = 0, index = 0; y < nscanlines;  ++y) {
        for (int x = 0;  x < spec.width;  ++x, ++index) {
            float contrast[LAPLACIAN_MAX_LEVELS - 2];
            float sum_contrast = 0;
            for (int i = 0; i < LAPLACIAN_MAX_LEVELS - 2; i++) {
                float n1 = fabsf (la.value(x,y,i) - la.value(x,y,i+1));
                float n2 = fabsf (lb.value(x,y,i) - lb.value(x,y,i+1));
                float numerator = std::max (n1, n2);
                float d1 = fabsf (la.value(x,y,i+2));
                float d2 = fabsf (lb.value(x,y,i+2));
                float denominator = std::max (std::max (d1, d2), 1.0e-5f);
                contrast[i] = numerator / denominator;
                sum_contrast += contrast[i];
            }
            if (sum_contrast < 1e-5)
                sum_contrast = 1e-5f;
            float F_mask[LAPLACIAN_MAX_LEVELS - 2];
            float adapt = la.value(x,y,adaptation_level) + lb.value(x,y,adaptation_level);
            adapt *= 0.5f;
            if (adapt < 1e-5)
                adapt = 1e-5f;
            for (int i = 0; i < LAPLACIAN_MAX_LEVELS - 2; i++)
                F_mask[i] = mask(contrast[i] * contrast_sensitivity(cpd[i], adapt));
            float factor = 0;
            for (int i = 0; i < LAPLACIAN_MAX_LEVELS - 2; i++)
                factor += contrast[i] * F_freq[i] * F_mask[i] / sum_contrast;
            factor = Imath::clamp (factor, 1.0f, 10.0f);
            float delta = fabsf (la.value(x,y,0) - lb.value(x,y,0));
            bool pass = true;
            // pure luminance test
            if (delta > factor * tvi(adapt)) {
                pass = false;
            } else if (! luminanceOnly) {
                // CIE delta E test with modifications
                float color_scale = 1.0f;
                // ramp down the color test in scotopic regions
                if (adapt < 10.0f) {
                    color_scale = 1.0f - (10.0f - color_scale) / 10.0f;
                    color_scale = color_scale * color_scale;
                }
                float da = aLAB[index][1] - bLAB[index][1];  // diff in A
                float db = aLAB[index][2] - bLAB[index][2];  // diff in B
                da = da * da;
                db = db * db;
                float delta_e = (da + db) * color_scale;
                if (delta_e > factor)
                    pass = false;
            }
            if (!pass)
                ++pixels_failed;
        }
    }
//    std::cout << "Perceptual diff shows " << pixels_failed << " failures\n";

    return pixels_failed;
}

}
OIIO_NAMESPACE_EXIT


/*
 * Copyright (c) 2017, Adrian Michel
 * http://www.amichel.com
 *
 * This software is released under the 3-Clause BSD License
 *
 * The complete terms can be found in the attached LICENSE file
 * or at https://opensource.org/licenses/BSD-3-Clause
 */

#pragma once

#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/make_shared.hpp>
#include <boost/tokenizer.hpp>
#include <set>

#include "de_types.hpp"
#include "random_generator.hpp"

#if defined(max)
#undef max
#endif

#if defined(min)
#undef min
#endif

namespace amichel {
namespace de {

/**
 * Exception thrown in case of a constraint error
 */
class constraints_exception : public exception {
 public:
  constraints_exception(const std::string& message)
      : exception(message.c_str()) {}
};

/**
 * Abstract base class for concrete constraint classes
 *
 * A constraint class describes certain characteristics and
 * limits of the input variables fed to the objective function.
 */
class constraint {
 public:
  virtual ~constraint() {}

  /**
   * returns a random value limited to the type and range of the
   * constraint
   *
   * @return double
   */
  virtual double get_rand_value() = 0;

  /**
   * returns a random value limited to the type and range of the
   * constraint based on a previous value and an origin (see
   * specific implementation in derived classes)
   *
   * @param value
   * @param origin
   *
   * @return double
   */
  virtual double get_rand_value(double value, double origin) = 0;

  /**
   * returns the min limit of the range
   *
   * @return double
   */
  virtual double min() const = 0;

  /**
   * returns the max limit of the range
   *
   * @return double
   */
  virtual double max() const = 0;

  /**
   * Gets a random value within the limits set for the constraint,
   * but further limited to a range defined by its origin and the
   * width of the zone around this origin in pct of the total
   * width
   *
   * @param origin the origin (center) of the zone further
   *  			 limiting the constraint
   * @param zonePct the width of the zone in pct of the total
   *  			  width, around the origin
   *
   * @return double
   */
  virtual double get_rand_value_in_zone(double origin, double zonePct) const = 0;

  /**
   * Gets the point midway between min and max - will only work
   * for range constraints
   *
   * @return double
   */
  virtual double get_middle_point() = 0;
};

/**
 * A smart pointer to a Constraint
 */
using constraint_ptr = std::shared_ptr<constraint>;

/**
 * Base class for constraints that are range based. Each such
 * constraint has a min and a max value
 */
class range_constraint : public constraint {
 private:
  double m_min;
  double m_max;

 public:
  /**
   * constructor that takes the min and max limits of the range
   *
   * @param min
   * @param max
   */
  range_constraint(double min, double max) : m_min(min), m_max(max) {
    assert(min <= max);
  }

  /**
   * returns the min limit of the range
   *
   * @return double
   */
  double min() const { return m_min; }

  /**
   * returns the max limit of the range
   *
   * @return double
   */
  double max() const { return m_max; }
};

/**
 * A real constraint. Specifies that variables can have any
 * double value, within the specified limits.
 */
class real_constraint : public range_constraint {
 public:
  /**
   * constructor that takes the min and max limit of the real
   * constraint
   *
   * @param min
   * @param max
   */
  real_constraint(double min, double max) : range_constraint(min, max) {
    assert(min <= max);
  }

  /**
   * returns a random value limited to the type and range of the
   * constraint
   *
   * @return double
   */
  double get_rand_value() {
    return genrand(range_constraint::min(), range_constraint::max());
  }

  /**
   * returns a random value limited to the type and range of the
   * constraint based on a previous value and an origin
   *
   * @param value
   * @param origin
   *
   * @return double
   */
  double get_rand_value(double value, double origin) {
    double ret = value;

    while (ret < range_constraint::min()) {
      ret = range_constraint::min() + genrand() * (origin - range_constraint::min());
    }

    while (ret > range_constraint::max()) {
      ret = range_constraint::max() + genrand() * (origin - range_constraint::max());
    }

    return ret;
  }

  double get_rand_value_in_zone(double origin, double zonePct) const override {
    if (origin > max()) {
      throw constraints_exception("origin coordinate > max");
    }

    if (origin < min()) {
      throw constraints_exception("origin coordinate < min");
    }

    if (zonePct > 100.0) {
      throw constraints_exception("zonePct > 100%");
    }

    if (zonePct < 0) {
      throw constraints_exception("zonePct < 0%");
    }

    if (zonePct == 0) {
      throw constraints_exception("zonePct == 0%");
    }

    double zoneSize = (max() - min()) * zonePct / 100.0;

    double _min = std::max(min(), origin - zoneSize / 2.0);
    double _max = std::min(max(), origin + zoneSize / 2.0);

    return genrand(_min, _max);
  }

  double get_middle_point() override { return (max() + min()) / 2.0; }
};

/**
 * An integer constraint. Specifies that variables can have any
 * integer values within the specified limits.
 */
class int_constraint : public range_constraint {
 public:
  /**
   * constructor that takes the min and max limit of the integer
   * constraint
   *
   * @param min
   * @param max
   */
  int_constraint(double min, double max) : range_constraint(min, max) {
    assert(min <= max);
  }

  /**
   * returns a random value limited to the type and range of the
   * constraint
   *
   * @return double
   */
  double get_rand_value() {
    return genintrand(range_constraint::min(), range_constraint::max());
  }

  /**
   * returns a random value limited to the type and range of the
   * constraint based on a previous value and an origin
   *
   * @param value
   * @param origin
   *
   * @return double
   */
  double get_rand_value(double value, double origin) {
    double ret = boost::math::round(value);

    while (ret < range_constraint::min()) {
      ret = range_constraint::min() + genrand() * (origin - range_constraint::min());
      ret = boost::math::round(ret);
    }

    while (ret > range_constraint::max()) {
      ret = range_constraint::max() + genrand() * (origin - range_constraint::max());
      ret = boost::math::round(ret);
    }

    return ret;
  }

  virtual double get_rand_value_in_zone(double origin, double zonePct) const {
    if (origin > max()) {
      throw constraints_exception("origin coordinate > max");
    }

    if (origin < min()) {
      throw constraints_exception("origin coordinate < min");
    }

    if (zonePct > 100.0) {
      throw constraints_exception("zonePct > 100%");
    }

    if (zonePct < 0) {
      throw constraints_exception("zonePct < 0%");
    }

    if (zonePct == 0) {
      throw constraints_exception("zonePct == 0%");
    }

    double zoneSize = (max() - min()) * zonePct / 100.0;

    double _min = std::max(min(), origin - zoneSize / 2.0);
    double _max = std::min(max(), origin + zoneSize / 2.0);

    double val = boost::math::round(genrand(_min, _max));

    while (val < _min || val > _max) {
      val = boost::math::round(genrand(_min, _max));
    }

    return val;
  }

  virtual double get_middle_point() {
    return boost::math::round((max() - min()) / 2.0);
  }
};

/**
 * A set constraint. Specifies that variables can take any
 * values from a predefined set. Doesn't require min or max.
 *
 * Note that duplicate values will be removed
 */
class set_constraint : public constraint {
 private:
  class unique : public std::unary_function<double, bool> {
   public:
    bool operator()(double d) const { return m_unique.insert(d).second; }

   public:
    double min() const {
      if (m_unique.size() > 0) {
        return *m_unique.begin();
      }
      else {
        throw constraints_exception("could not get the min value of an empty set constraint");
      }
    }

    double max() const {
      if (m_unique.size() > 0) {
        return *m_unique.rbegin();
      }
      else {
        throw constraints_exception("could not get the max value of an empty set constraint");
      }
    }

   private:
    mutable std::set<double> m_unique;
  };

 private:
  unique m_unique;
  de::DVector m_values;

 public:
  /**
   * Constructs the set from a vector of Double values, and
   * removes duplicates to ensure a uniform distribution for
   * randomization
   *
   * @param values
   */
  set_constraint(const de::DVector& values) {
    // making sure the values in the "set" are unique
    std::remove_copy_if(values.begin(), values.end(), std::back_inserter(m_values), m_unique);
  }

  /**
   * adds an individual value to the "set", won't create duplicate
   * values.
   *
   * @param value
   */
  void add_value(double value) {
    if (m_unique(value)) {
      m_values.push_back(value);
    }
  }

  /**
   * returns a value randomly chosen from the set of available
   * values
   *
   * @return double
   */
  virtual double get_rand_value() {
    de::DVector::size_type index(genintrand(0, m_values.size() - 1));

    return m_values[index];
  }

  /**
   * returns a value randomly chosen from the set of available
   * values
   *
   * @return double
   */
  virtual double get_rand_value(double value, double origin) {
    return get_rand_value();
  }

  /**
   * returns the min limit of the range
   *
   * @return double
   */
  double min() const { return m_unique.min(); }

  /**
   * returns the max limit of the range
   *
   * @return double
   */
  double max() const { return m_unique.max(); }

  virtual double get_rand_value_in_zone(double origin, double zonePct) const {
    throw constraints_exception("get_rand_value_in_zone only supported for range constraints");
  }

  virtual double get_middle_point() {
    throw constraints_exception("get_middle_point not supported by set constraint");
  }
};

/**
 * A boolean constraint. Specifies that variables can take a
 * boolean value - true or false.
 */
class boolean_constraint : public constraint {
 public:
  /**
   * returns a random boolean value
   *
   * @return double
   */
  virtual double get_rand_value() { return genrand() < 0.5; }

  /**
   * returns a random boolean value
   *
   * @return double
   */
  virtual double get_rand_value(double value, double origin) {
    return get_rand_value();
  }

  /**
   * returns the min limit of the range
   *
   * @return double
   */
  double min() const { return 0; }

  /**
   * returns the max limit of the range
   *
   * @return double
   */
  double max() const { return 1; }

  virtual double get_rand_value_in_zone(double origin, double zonePct) const {
    throw constraints_exception("get_rand_value_in_zone only supported for range constraints");
  }

  virtual double get_middle_point() {
    throw constraints_exception("get_middle_point not supported by bool constraint");
  }
};

using constraints_base = std::vector<constraint_ptr>;

/**
 * A collection of constraints, implemented as a vector.
 *
 * Is used to define the constraints for all variables used
 * during an optimization session.
 */
class constraints : public constraints_base {
 private:
  using separator = boost::char_separator<char>;
  using tokenizer = boost::tokenizer<separator>;

 public:
  /**
   * Initializes a collection of constraints with default values.
   * All constraints are of type real and have the same default
   * min and max.
   *
   * @param varCount number of constraints
   * @param defMin default min limit
   * @param defMax default max limit
   */
  constraints(size_t varCount, double defMin, double defMax)
      : constraints_base(varCount, std::make_shared<real_constraint>(defMin, defMax)) {}

  /**
   * Initializes a collection of constraints from string
   * descriptions. Currently used only for range based
   * constraints.
   *
   * A constraint can be described as "type;min;max" where type
   * can be real or integer and min and max are the range limits.
   *
   * @param str a collection (vector) of constraint description
   *  		  strings, each string describing constraints for
   *  		  one variable
   * @param var_count the total number of variables (can be
   *  			   different than the number of strings). If
   *  			   there are more variables than strings, the
   *  			   extra constraints are set to be real and use
   *  			   the default min and max arguments for the
   *  			   range
   * @param def_min default min value in case the number of
   *  			 variables is higher than the number of
   *  			 constraints specified as strings.
   * @param def_max default max value in case the number of
   *  			 variables is higher than the number of
   *  			 constraints specified as strings.
   */
  constraints(const std::vector<std::string>& str, size_t var_count, double def_min, double def_max)
      : constraints_base( var_count, std::make_shared<real_constraint>(def_min, def_max)) {
    for (std::vector<std::string>::size_type i = 0; i < str.size(); ++i) {
      tokenizer tokens(str[i], separator(":;,"));

      std::string type;
      double _min;
      double _max;

      size_t count(0);

      for (tokenizer::const_iterator j = tokens.begin(); j != tokens.end(); ++j, ++count) {
        const std::string token(boost::trim_copy(*j));

        try {
          switch (count) {
            case 0:
              type = token;
              break;
            case 1:
              _min = boost::lexical_cast<double>(token.c_str());
              break;
            case 2:
              _max = boost::lexical_cast<double>(token.c_str());
              break;
            default:
              // too many fields
              throw constraints_exception((boost::format("wrong variable format in \"%1%\" - too many fields") % str[i]) .str());
          }
        }
        catch (const boost::bad_lexical_cast&) {
          throw constraints_exception((boost::format("wrong floating point number format: %1%") % token).str());
        }
      }

      // too few fields
      if (count < 3) {
        throw constraints_exception((boost::format("wrong variable format in \"%1%\" - too few fields") % str[i]).str());
      }

      if (i < var_count) {
        constraints_base::at(i) = str_to_constraint(type, _min, _max);
      }
      else {
        constraints_base::push_back(str_to_constraint(type, _min, _max));
      }
    }
  }

  /**
   * returns a random value limited to the type and range of the
   * constraint
   *
   * @return double
   */
  double get_rand_value(size_t index) const {
    if (index < constraints_base::size()) {
      return (*this)[index]->get_rand_value();
    }
    else {
      throw constraints_exception((boost::format("invalid constraint index: %1%, higher than max number of constraints: %2%") %
          index % constraints_base::size()).str());
    }
  }

  /**
   * returns a random value limited to the type and range of the
   * constraint based on a previous value and an origin
   *
   * @param index the constraint index
   * @param value previous value
   * @param origin origin
   *
   * @return double
   */
  double get_rand_value(size_t index, double value, double origin) const {
    if (index < constraints_base::size()) {
      return (*this)[index]->get_rand_value(value, origin);
    }
    else {
      throw constraints_exception((boost::format("invalid constraint index: %1%, higher than max number of constraints: %2%") %
        index % constraints_base::size()).str());
    }
  }

  /**
   * Generates a set of random values within a "hypercube" region
   * defined by an origin and an area expressed as a percentage of
   * the entire range
   *
   * @param origin the origin coordinates of the region
   * @param sidePct the side of the hypercube in pct of the entire
   *  			  side
   *
   * @return DVectorPtr
   */
  DVectorPtr get_square_zone_rand_values(const DVectorPtr origin, double sidePct) const {
    assert(origin);
    assert(sidePct > 0 && sidePct <= 100);

    if (origin->size() == constraints_base::size()) {
      DVectorPtr square(std::make_shared<DVector>(origin->size()));

      for (constraints_base::size_type n = 0; n < constraints_base::size(); ++n)
        (*square)[n] = (*this)[n]->get_rand_value_in_zone((*origin)[n], sidePct);

      return square;

    }
    else {
      throw constraints_exception("The origin vector must have the same number of elements as there are constraints");
    }
  }

  DVectorPtr get_middle_point() {
    DVectorPtr r(std::make_shared<DVector>(constraints_base::size()));

    for (constraints_base::size_type n = 0; n < constraints_base::size(); ++n)
      (*r)[n] = (*this)[n]->get_middle_point();

    return r;
  }

  /**
   * Get a set of random values within the limits set by the
   * constraints
   *
   * @return DVectorPtr
   */
  DVectorPtr get_rand_values() const {
    DVectorPtr r(std::make_shared<DVector>(constraints_base::size()));

    for (constraints_base::size_type n = 0; n < constraints_base::size(); ++n)
      (*r)[n] = (*this)[n]->get_rand_value();

    return r;
  }

 private:
  constraint_ptr str_to_constraint(const std::string& type, double min, double max) {
    if (boost::to_lower_copy(type) == "real") {
      return std::make_shared<real_constraint>(min, max);
    }
    else if (boost::to_lower_copy(type) == "int" || boost::to_lower_copy(type) == "integer") {
      return std::make_shared<int_constraint>(min, max);
    }
    else {
      throw constraints_exception((boost::format("invalid constraint type \"%1%\"") % type).str());
    }
  }
};

using constraints_ptr = std::shared_ptr<constraints>;
}  // namespace de
}  // namespace amichel


/*-------------HO.cpp---------------------------------------------------------//
*
*              HO.cpp -- Diffusion Monte Carlo for Schroedy
*
* Purpose: Implement the quantum Monte Carlo (diffusion) by Kosztin:
*          http://www.thphys.uni-heidelberg.de/~wetzel/qmc2006/KOSZ96.pdf
*
*    Note: This algorithm may be improved by later work
*          A "psip" is an imaginary configuration of electrons in space.
*          Requires c++11 for random and Eigen for matrix
*          Protons assume to be at origin
*          A lot of this algorithm is more easily understood here:
*              http://www.thphys.uni-heidelberg.de/~wetzel/qmc2006/KOSZ96.pdf
*
*-----------------------------------------------------------------------------*/

#include <iostream>
#include <random>
#include <Eigen/Core>
#include <fstream>

#define DOF 1
#define SIZE 2000
#define DIMS (DOF + 2)
#define binnum 40

typedef Eigen::Matrix<double, // typename Scalar
   SIZE, // int RowsAtCompileTime,
   DIMS, // int ColsAtCompileTime,
   0> // int Options = 0,
   MatrixPSIP;

struct H3plus {
    MatrixPSIP pos;
    double Vref, dt, Energy;
    int psipnum, id;
    std::vector<int> bins = std::vector<int>(binnum, 0);;
};

// Populate a distribution of particles for QMC
void populate(H3plus& state);

// Calculates energy of a configuration and stores it into the final element of
// the MatrixPSIP
void find_weights(H3plus& state);

// Branching scheme
void branch(H3plus& state);

// Random walking of matrix of position created in populate
void diffuse(H3plus& state, std::ostream& output);

// Adding function to bin data into wavefunction
void bin(H3plus& state, std::ostream& output);

// output certain elements in array
void arrayout(H3plus& state, int length);

/*----------------------------------------------------------------------------//
* MAIN
*-----------------------------------------------------------------------------*/

int main(){

    std::ofstream output("out.dat", std::ostream::out);
    H3plus state;

    state.Vref = 0;
    state.dt = 0.1;
    state.psipnum = 100;
    state.Energy = 0;
    state.id = 0;

    populate(state);

    std::cout << state.Vref << '\t' << state.psipnum << '\n';

    /*
    for (size_t i = 0; i < state.psipnum; i++){
        std::cout << " final element is: " << state.pos(i,DIMS-2) << '\n';
    }
    */

    diffuse(state, output);

    //output << state.pos << '\n';

}

/*----------------------------------------------------------------------------//
* SUBROUTINES
*-----------------------------------------------------------------------------*/

// Populate a distribution of particles for QMC
// Unlike Anderson, we are initilizing each psip randomly from a distribution.
// This might scre things up, because
void populate(H3plus& state){

    std::default_random_engine generator;
    std::uniform_real_distribution<double> distribution(-1.0,1.0);

    for (size_t i = 0; i < state.psipnum; i++){
        state.pos(i,0) = 0;
        state.pos(i,DIMS-1) = state.id;
        state.id++;
    }

    find_weights(state);

    /*
    for (size_t i = 0; i < state.psipnum; i++){
        std::cout << state.pos(i,DIMS-2) << '\n';
    }
    */

}

// Calculates energy of a configuration and stores it into the final element of
// the MatrixPSIP
// Note: When calculating the potential, I am not sure whether we need to use
//       absolute value of distance or the distance, itself.
// Note: Inefficient. Can calculate energy on the fly with every generation of
//       psip. Think about it.
// Note: Vref will be a dummy variable for now.
void find_weights(H3plus& state){

    double dist, pot, pot_tot = 0;

    std::default_random_engine gen;
    std::uniform_real_distribution<double> distribution(0,1);

    // Note that this is specific to the Anderson paper
    // Finding the distance between electrons, then adding the distances
    // from the protons to the electrons.
    for (size_t i = 0; i < state.psipnum; i++){
        pot = 0.5 * state.pos(i,0) * state.pos(i,0);
        state.pos(i,DIMS-2) = pot;

        pot_tot += pot;

    }

    // defining the new reference potential to psipnum down.
    state.Energy = pot_tot / state.psipnum;
    state.Vref = state.Energy
                 - ((state.psipnum - 1000) / (1000 * state.dt));

    for (size_t i = 0; i < state.psipnum; i++){
        state.pos(i,DIMS-2) = (int)(1-(state.pos(i,DIMS-2) - state.Vref)
                                    * state.dt
                              + distribution(gen));
        if (state.pos(i,DIMS-2) > 3){
            state.pos(i,DIMS-2) = 3;
        }

    }
}

// Branching scheme
void branch(H3plus& state){

    find_weights(state);

    /*
    for (size_t i = 0; i < state.psipnum; i++){
        std::cout << state.pos(i,DIMS-2) << '\n';
    }
    */

    int variable, offset = 0, psip_old = state.psipnum, births = 0, tmpi = 0;

    for (size_t i = 0; i < psip_old + births; i++){

        //std::cout << i << '\n';

        variable = state.pos(i, DIMS-2);

        switch (variable){
            // Destruction
            case 0: state.psipnum--;
                    break;

            // Creation of 1
            case 2: state.psipnum++;
                    births++;
                    for (size_t j = 0; j < state.pos.cols() - 2; j++){
                        state.pos(psip_old+births-1,j) = state.pos(i,j);
                    }
                    //std::cout << "writing: " << i << " to "
                    //          << psip_old+births-1 << '\n';
                    state.pos(psip_old+births-1,DIMS-2) = 1;
                    state.pos(psip_old+births-1,DIMS-1) = state.id;
                    state.id++;
                    break;

            // Creation of 2
            case 3: state.psipnum += 2;
                    births += 2;
                    for (size_t k = 0; k < 2; k++){
                        for (size_t j = 0; j < state.pos.cols() - 2; j++){
                            state.pos(psip_old-k+births-1, j) = state.pos(i, j);
                            //std::cout << state.pos(psip_old-k+births-1, j)
                            //          << '\n';
                        }
                        state.pos(psip_old-k+births-1,DIMS-2) = 1;
                        state.pos(psip_old-k+births-1,DIMS-1) = state.id;
                        state.id++;
                        //std::cout << "writing: " << i << " to "
                        //          << psip_old-k+births-1 << '\n';
                    }
                    break;

        }

    }

    //arrayout(state, state.psipnum);

    // Adjustment for offset
    // Note: Account for the situation where offset is greater than arraysize
    for (size_t i = 0; i < SIZE; i++){
        if (state.pos(i,DIMS-2) != 0){
            for (size_t j = 0; j < state.pos.cols(); j++){
                state.pos(tmpi,j) = state.pos(i,j);
            }
            tmpi++;
        }
        if (i > state.psipnum){
            for (size_t j = 0; j < state.pos.cols(); j++){
                state.pos(i,j) = 0;
            }

        }
    }

}

// Random walking of matrix of position created in populate
// Step 1: Move particles via 6D random walk
// Step 2: Destroy and create particles as need based on Anderson
// Step 3: check energy, end if needed.
void diffuse(H3plus& state, std::ostream& output){

    // Let's initialize the randomness
    std::default_random_engine gen;
    std::normal_distribution<double> gaussian(0,1);

    double diff = 1, Vsave = 0, tmpbin, x;
    int wavenum = 1000;

    for (size_t k = 0; k < wavenum; k++){
        // For now, I am going to set a definite number of timesteps
        // This will be replaced by a while loop in the future.
        for (size_t t = 0; t < 100; t++){
        //while (diff > 0.01){
            Vsave = state.Vref;
            #pragma omp parallel for
            for (size_t i = 0; i < state.psipnum; i++){
                for (size_t j = 0; j < state.pos.cols() - 1; j++){
                    state.pos(i, j) += sqrt(state.dt) * gaussian(gen);
                }
            }
            branch(state);
            diff = sqrt((Vsave - state.Vref)*(Vsave - state.Vref));
            std::cout << state.Vref << '\t' << state.psipnum << '\n';
        }

        bin(state, output);
    }

    for (size_t i = 0; i < binnum; i++){
        tmpbin = state.bins[i] * 0.0001;
        output << tmpbin << '\n';
    }

    output << '\n' << '\n';

    for (size_t i = 0; i < binnum; i++){
        x = ((double)i / (double)binnum)*8.0 - 4.0;
        std::cout << x << '\n';
        tmpbin = exp(- x*x / 2);
        output << tmpbin << '\n';
    }

}

// Adding function to bin data into wavefunction
void bin(H3plus& state, std::ostream& output){

    double bound = 4, max = bound, min = -bound;

    for (size_t i = 0; i < state.psipnum; i++){
        for (size_t j = 0; j < binnum; j++){
            if (state.pos(i,0) >= ((max - min) / binnum) * j  - bound&&
                state.pos(i,0) < ((max - min) / binnum) * (j+1) - bound){
                state.bins[j] += 1;
            }
        }
    }

}

// output certain elements in array
void arrayout(H3plus& state, int length){

    for (size_t i = 0; i < length; i++){
        for (size_t j  = 0; j < 2; j++){
            std::cout << state.pos(i,DIMS - 1 - j) << '\t';
        }
        std::cout << '\n';
    }
}


#include <iostream>
#include <string>

#include <boost/program_options.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/preprocessor/seq/for_each.hpp>

#if defined(SOLVER_BACKEND_VEXCL)
#  include <amgcl/backend/vexcl.hpp>
   typedef amgcl::backend::vexcl<double> Backend;
#elif defined(SOLVER_BACKEND_VIENNACL)
#  include <amgcl/backend/viennacl.hpp>
   typedef amgcl::backend::viennacl< viennacl::compressed_matrix<double> > Backend;
#elif defined(SOLVER_BACKEND_CUDA)
#  include <amgcl/backend/cuda.hpp>
#  include <amgcl/relaxation/cusparse_ilu0.hpp>
   typedef amgcl::backend::cuda<double> Backend;
#else
#  ifndef SOLVER_BACKEND_BUILTIN
#    define SOLVER_BACKEND_BUILTIN
#  endif
#  include <amgcl/backend/builtin.hpp>
#  include <amgcl/value_type/static_matrix.hpp>
#  include <amgcl/adapter/block_matrix.hpp>
#  include <amgcl/make_block_solver.hpp>
   typedef amgcl::backend::builtin<double> Backend;
#endif

#include <amgcl/make_solver.hpp>
#include <amgcl/amg.hpp>
#include <amgcl/solver/runtime.hpp>
#include <amgcl/coarsening/runtime.hpp>
#include <amgcl/relaxation/runtime.hpp>
#include <amgcl/preconditioner/schur_pressure_correction.hpp>
#include <amgcl/preconditioner/runtime.hpp>
#include <amgcl/adapter/crs_tuple.hpp>

#include <amgcl/io/mm.hpp>
#include <amgcl/io/binary.hpp>
#include <amgcl/profiler.hpp>

#ifndef AMGCL_BLOCK_SIZES
#  define AMGCL_BLOCK_SIZES (3)(4)
#endif

namespace amgcl { profiler<> prof; }
using amgcl::prof;
using amgcl::precondition;

template <class USolver, class PSolver, class Matrix>
typename std::enable_if<
    (
        amgcl::math::static_rows<
            typename amgcl::preconditioner::detail::common_backend<
                typename USolver::backend_type,
                typename PSolver::backend_type
            >::type::value_type
        >::value > 1
    ),
    void
    >::type
solve_schur(const Matrix&, const std::vector<double>&, boost::property_tree::ptree&)
{}

//---------------------------------------------------------------------------
template <class USolver, class PSolver, class Matrix>
typename std::enable_if<
    (
        amgcl::math::static_rows<
            typename amgcl::preconditioner::detail::common_backend<
                typename USolver::backend_type,
                typename PSolver::backend_type
            >::type::value_type
        >::value == 1
    ),
    void>::type
solve_schur(const Matrix &K, const std::vector<double> &rhs, boost::property_tree::ptree &prm)
{
    Backend::params bprm;

#if defined(SOLVER_BACKEND_VEXCL)
    vex::Context ctx(vex::Filter::Env);
    std::cout << ctx << std::endl;
    bprm.q = ctx;
#elif defined(SOLVER_BACKEND_VIENNACL)
    std::cout
        << viennacl::ocl::current_device().name()
        << " (" << viennacl::ocl::current_device().vendor() << ")\n\n";
#elif defined(SOLVER_BACKEND_CUDA)
    cusparseCreate(&bprm.cusparse_handle);
    {
        int dev;
        cudaGetDevice(&dev);

        cudaDeviceProp prop;
        cudaGetDeviceProperties(&prop, dev);
        std::cout << prop.name << std::endl << std::endl;
    }
#endif

    auto t1 = prof.scoped_tic("schur_complement");

    prof.tic("setup");
    amgcl::make_solver<
        amgcl::preconditioner::schur_pressure_correction<USolver, PSolver>,
        amgcl::runtime::solver::wrapper<Backend>
        > solve(K, prm, bprm);
    prof.toc("setup");

    std::cout << solve.precond() << std::endl;

    auto f = Backend::copy_vector(rhs, bprm);
    auto x = Backend::create_vector(rhs.size(), bprm);
    amgcl::backend::clear(*x);

    size_t iters;
    double error;

    prof.tic("solve");
    std::tie(iters, error) = solve(*f, *x);
    prof.toc("solve");

    std::cout << "Iterations: " << iters << std::endl
              << "Error:      " << error << std::endl;
}

#define AMGCL_BLOCK_PSOLVER(z, data, B)                                        \
  case B: {                                                                    \
    typedef amgcl::backend::builtin<amgcl::static_matrix<double, B, B> >       \
        BBackend;                                                              \
    typedef amgcl::make_block_solver<                                          \
        amgcl::runtime::preconditioner<BBackend>,                              \
        amgcl::runtime::solver::wrapper<BBackend> >                            \
        PSolver;                                                               \
    solve_schur<USolver, PSolver>(K, rhs, prm);                                \
  } break;

//---------------------------------------------------------------------------
template <class USolver, class Matrix>
void solve_schur(int pb, const Matrix &K, const std::vector<double> &rhs, boost::property_tree::ptree &prm)
{
    switch (pb) {
        case 1:
            {
                typedef
                    amgcl::make_solver<
                        amgcl::runtime::preconditioner<Backend>,
                        amgcl::runtime::solver::wrapper<Backend>
                        >
                    PSolver;
                solve_schur<USolver, PSolver>(K, rhs, prm);
            }
            break;
#if defined(SOLVER_BACKEND_BUILTIN)
        BOOST_PP_SEQ_FOR_EACH(AMGCL_BLOCK_PSOLVER, ~, AMGCL_BLOCK_SIZES)
#endif
        default:
            precondition(false, "Unsupported block size for pressure");
    }
}

#define AMGCL_BLOCK_USOLVER(z, data, B)                                        \
  case B: {                                                                    \
    typedef amgcl::backend::builtin<amgcl::static_matrix<double, B, B> >       \
        BBackend;                                                              \
    typedef amgcl::make_block_solver<                                          \
        amgcl::runtime::preconditioner<BBackend>,                              \
        amgcl::runtime::solver::wrapper<BBackend> >                            \
        USolver;                                                               \
    solve_schur<USolver>(pb, K, rhs, prm);                                     \
  } break;

//---------------------------------------------------------------------------
template <class Matrix>
void solve_schur(int ub, int pb, const Matrix &K, const std::vector<double> &rhs, boost::property_tree::ptree &prm)
{
    precondition(ub == 1 || pb == 1,
            "At least one of the flow/pressure subproblems has to be scalar");

    switch (ub) {
        case 1:
            {
                typedef
                    amgcl::make_solver<
                        amgcl::runtime::preconditioner<Backend>,
                        amgcl::runtime::solver::wrapper<Backend>
                        >
                    USolver;
                solve_schur<USolver>(pb, K, rhs, prm);
            }
            break;
#if defined(SOLVER_BACKEND_BUILTIN)
        BOOST_PP_SEQ_FOR_EACH(AMGCL_BLOCK_USOLVER, ~, AMGCL_BLOCK_SIZES)
#endif
        default:
            precondition(false, "Unsupported block size for flow");
    }
}

//---------------------------------------------------------------------------
int main(int argc, char *argv[]) {
    using std::string;
    using std::vector;

    namespace po = boost::program_options;
    namespace io = amgcl::io;

    po::options_description desc("Options");

    desc.add_options()
        ("help,h", "show help")
        (
         "binary,B",
         po::bool_switch()->default_value(false),
         "When specified, treat input files as binary instead of as MatrixMarket. "
         "It is assumed the files were converted to binary format with mm2bin utility. "
        )
        (
         "matrix,A",
         po::value<string>()->required(),
         "The system matrix in MatrixMarket format"
        )
        (
         "rhs,f",
         po::value<string>(),
         "The right-hand side in MatrixMarket format"
        )
        (
         "pmask,m",
         po::value<string>(),
         "The pressure mask in MatrixMarket format. Or, if the parameter has "
         "the form '%n:m', then each (n+i*m)-th variable is treated as pressure."
        )
        (
         "ub",
         po::value<int>()->default_value(1),
         "Block-size of the 'flow'/'non-pressure' part of the matrix"
        )
        (
         "pb",
         po::value<int>()->default_value(1),
         "Block-size of the 'pressure' part of the matrix"
        )
        (
         "params,P",
         po::value<string>(),
         "parameter file in json format"
        )
        (
         "prm,p",
         po::value< vector<string> >()->multitoken(),
         "Parameters specified as name=value pairs. "
         "May be provided multiple times. Examples:\n"
         "  -p solver.tol=1e-3\n"
         "  -p precond.coarse_enough=300"
        )
        ;

    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, desc), vm);

    if (vm.count("help")) {
        std::cout << desc << std::endl;
        return 0;
    }

    po::notify(vm);

    boost::property_tree::ptree prm;
    if (vm.count("params")) read_json(vm["params"].as<string>(), prm);

    if (vm.count("prm")) {
        for(const string &v : vm["prm"].as<vector<string> >()) {
            amgcl::put(prm, v);
        }
    }

    size_t rows;
    vector<ptrdiff_t> ptr, col;
    vector<double> val, rhs;
    std::vector<char> pm;

    {
        auto t = prof.scoped_tic("reading");

        string Afile  = vm["matrix"].as<string>();
        bool   binary = vm["binary"].as<bool>();

        if (binary) {
            io::read_crs(Afile, rows, ptr, col, val);
        } else {
            size_t cols;
            std::tie(rows, cols) = io::mm_reader(Afile)(ptr, col, val);
            precondition(rows == cols, "Non-square system matrix");
        }

        if (vm.count("rhs")) {
            string bfile = vm["rhs"].as<string>();

            size_t n, m;

            if (binary) {
                io::read_dense(bfile, n, m, rhs);
            } else {
                std::tie(n, m) = io::mm_reader(bfile)(rhs);
            }

            precondition(n == rows && m == 1, "The RHS vector has wrong size");
        } else {
            rhs.resize(rows, 1.0);
        }

        if(vm.count("pmask")) {
            std::string pmask = vm["pmask"].as<string>();
            prm.put("precond.pmask_size", rows);

            switch (pmask[0]) {
                case '%':
                case '<':
                case '>':
                    prm.put("precond.pmask_pattern", pmask);
                    break;
                default:
                    {
                        size_t n, m;

                        if (binary) {
                            io::read_dense(pmask, n, m, pm);
                        } else {
                            std::tie(n, m) = amgcl::io::mm_reader(pmask)(pm);
                        }

                        precondition(n == rows && m == 1, "Mask file has wrong size");

                        prm.put("precond.pmask", static_cast<void*>(&pm[0]));
                    }
            }
        }
    }

    solve_schur(vm["ub"].as<int>(), vm["pb"].as<int>(),
            std::tie(rows, ptr, col, val), rhs, prm);

    std::cout << prof << std::endl;
}


/*
    Copyright 2016 Emanuele Vespa, Imperial College London
    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:

    1. Redistributions of source code must retain the above copyright notice, this
    list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

    3. Neither the name of the copyright holder nor the names of its contributors
    may be used to endorse or promote products derived from this software without
    specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/
#ifndef OCTANT_OPS_HPP
#define OCTANT_OPS_HPP
#include "utils/morton_utils.hpp"
#include "utils/math_utils.h"
#include "octree_defines.h"
#include <iostream>
#include <bitset>
#include <Eigen/Dense>

namespace se {
  namespace keyops {

    inline se::key_t code(const se::key_t key) {
      return key & ~SCALE_MASK;
    }

    inline int level(const se::key_t key) {
      return key & SCALE_MASK;
}

    inline se::key_t encode(const int x, const int y, const int z,
        const int level, const int max_depth) {
      const int offset = MAX_BITS - max_depth + level - 1;
      return (compute_morton(x, y, z) & MASK[offset]) | level;
    }

    inline Eigen::Vector3i decode(const se::key_t key) {
      return unpack_morton(key & ~SCALE_MASK);
    }
  }
}

/*
 * Algorithm 5 of p4est paper: https://epubs.siam.org/doi/abs/10.1137/100791634
 */
inline Eigen::Vector3i face_neighbour(const se::key_t o,
    const unsigned int face, const unsigned int l,
    const unsigned int max_depth) {
  Eigen::Vector3i coords = se::keyops::decode(o);
  const unsigned int side = 1 << (max_depth - l);
  coords(0) = coords(0) + ((face == 0) ? -side : (face == 1) ? side : 0);
  coords(1) = coords(1) + ((face == 2) ? -side : (face == 3) ? side : 0);
  coords(2) = coords(2) + ((face == 4) ? -side : (face == 5) ? side : 0);
  return {coords(0), coords(1), coords(2)};
}

/*
 * \brief Return true if octant is a descendant of ancestor
 * \param octant
 * \param ancestor
 * \param max_depth max depth of the tree on which the octant lives
 */
inline bool descendant(se::key_t octant, se::key_t ancestor,
    const int max_depth) {
  const int level = se::keyops::level(ancestor);
  const int idx = MAX_BITS - max_depth + level - 1;
  ancestor = se::keyops::code(ancestor);
  octant = se::keyops::code(octant) & MASK[idx];
  return (ancestor ^ octant) == 0;
}

/*
 * \brief Computes the parent's morton code of a given octant
 * \param octant
 * \param max_depth max depth of the tree on which the octant lives
 */
inline se::key_t parent(const se::key_t& octant, const int max_depth) {
  const int level = se::keyops::level(octant) - 1;
  const int idx = MAX_BITS - max_depth + level - 1;
  return (octant & MASK[idx]) | level;
}

/*
 * \brief Computes the octants's id in its local brotherhood
 * \param octant
 * \param level of octant
 * \param max_depth max depth of the tree on which the octant lives
 */
inline int child_id(se::key_t octant, const int level,
    const int max_depth) {
  int shift = max_depth - level;
  octant = se::keyops::code(octant) >> shift*3;
  int idx = (octant & 0x01) | (octant & 0x02) | (octant & 0x04);
  return idx;
}

/*
 * \brief Computes the octants's corner which is not shared with its siblings
 * \param octant
 * \param level of octant
 * \param max_depth max depth of the tree on which the octant lives
 */
inline Eigen::Vector3i far_corner(const se::key_t octant, const int level,
    const int max_depth) {
  const unsigned int side = 1 << (max_depth - level);
  const int idx = child_id(octant, level, max_depth);
  const Eigen::Vector3i coordinates = se::keyops::decode(octant);
  return Eigen::Vector3i(coordinates(0) + (idx & 1) * side,
                   coordinates(1) + ((idx & 2) >> 1) * side,
                   coordinates(2) + ((idx & 4) >> 2) * side);
}

/*
 * \brief Computes the non-sibling neighbourhood around an octants. In the
 * special case in which the octant lies on an edge, neighbour are duplicated
 * as movement outside the enclosing cube is forbidden.
 * \param result 7-vector containing the neighbours
 * \param octant
 * \param level of octant
 * \param max_depth max depth of the tree on which the octant lives
 */
inline void exterior_neighbours(se::key_t result[7],
    const se::key_t octant, const int level, const int max_depth) {

  const int idx = child_id(octant, level, max_depth);
  Eigen::Vector3i dir = Eigen::Vector3i((idx & 1) ? 1 : -1,
                       (idx & 2) ? 1 : -1,
                       (idx & 4) ? 1 : -1);
  Eigen::Vector3i base = far_corner(octant, level, max_depth);
  dir(0) = se::math::in(base(0) + dir(0) , 0, (1 << max_depth) - 1) ? dir(0) : 0;
  dir(1) = se::math::in(base(1) + dir(1) , 0, (1 << max_depth) - 1) ? dir(1) : 0;
  dir(2) = se::math::in(base(2) + dir(2) , 0, (1 << max_depth) - 1) ? dir(2) : 0;

 result[0] = se::keyops::encode(base(0) + dir(0), base(1) + 0, base(2) + 0,
     level, max_depth);
 result[1] = se::keyops::encode(base(0) + 0, base(1) + dir(1), base(2) + 0,
     level, max_depth);
 result[2] = se::keyops::encode(base(0) + dir(0), base(1) + dir(1), base(2) + 0,
     level, max_depth);
 result[3] = se::keyops::encode(base(0) + 0, base(1) + 0, base(2) + dir(2),
     level, max_depth);
 result[4] = se::keyops::encode(base(0) + dir(0), base(1) + 0, base(2) + dir(2),
     level, max_depth);
 result[5] = se::keyops::encode(base(0) + 0, base(1) + dir(1), base(2) + dir(2),
     level, max_depth);
 result[6] = se::keyops::encode(base(0) + dir(0), base(1) + dir(1),
     base(2) + dir(2), level, max_depth);
}

/*
 * \brief Computes the morton number of all siblings around an octant,
 * including itself.
 * \param result 8-vector containing the neighbours
 * \param octant
 * \param max_depth max depth of the tree on which the octant lives
 */
inline void siblings(se::key_t result[8],
    const se::key_t octant, const int max_depth) {
  const int level = (octant & SCALE_MASK);
  const int shift = 3*(max_depth - level);
  const se::key_t p = parent(octant, max_depth) + 1; // set-up next level
  for(int i = 0; i < 8; ++i) {
    result[i] = p | (i << shift);
  }
}
#endif


/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2010 Roland Lichters

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 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 license for more details.
*/

#include <qlo/qladdindefines.hpp>
#include <qlo/credit.hpp>
#include <qlo/enumerations/factories/termstructuresfactory.hpp>

#include <ql/instruments/stock.hpp>
#include <ql/quote.hpp>
#include <ql/time/daycounter.hpp>
#include <ql/termstructures/credit/interpolatedhazardratecurve.hpp>
#include <ql/termstructures/yield/piecewiseyieldcurve.hpp>
#include <ql/termstructures/credit/piecewisedefaultcurve.hpp>
#include <ql/math/interpolations/backwardflatinterpolation.hpp>
#include <ql/math/interpolations/loginterpolation.hpp>
#include <ql/pricingengines/credit/midpointcdsengine.hpp>

#include <boost/algorithm/string/case_conv.hpp>

#include <ql/settings.hpp>

using boost::algorithm::to_upper_copy;

namespace QuantLibAddin {

    CreditDefaultSwap::CreditDefaultSwap(
              const boost::shared_ptr<ObjectHandler::ValueObject>& properties,
              QuantLib::Protection::Side side,
              QuantLib::Real notional,
              QuantLib::Rate upfront,
              QuantLib::Rate spread,
              const boost::shared_ptr<QuantLib::Schedule>& schedule,
              QuantLib::BusinessDayConvention paymentConvention,
              const QuantLib::DayCounter& dayCounter,
              bool settlesAccrual,
              bool paysAtDefaultTime,
              const QuantLib::Date& protectionStart,
              const QuantLib::Date& upfrontDate,
              bool permanent)
        : Instrument(properties, permanent) {
        libraryObject_ = boost::shared_ptr<QuantLib::CreditDefaultSwap>(
                    new QuantLib::CreditDefaultSwap(side,
                                                    notional,
                                                    upfront,
                                                    spread,
                                                    *schedule,
                                                    paymentConvention,
                                                    dayCounter,
                                                    settlesAccrual,
                                                    paysAtDefaultTime,
                                                    protectionStart,
                                                    upfrontDate));
    }

    MidPointCdsEngine::MidPointCdsEngine(
            const boost::shared_ptr<ObjectHandler::ValueObject>& properties,
            const QuantLib::Handle<QuantLib::DefaultProbabilityTermStructure>& defaultTS,
            QuantLib::Real recoveryRate,
            const QuantLib::Handle<QuantLib::YieldTermStructure>& yieldTS,
            bool permanent)
        : PricingEngine(properties, permanent) {
        libraryObject_ = boost::shared_ptr<QuantLib::PricingEngine>(new
              QuantLib::MidPointCdsEngine(defaultTS, recoveryRate, yieldTS));
    }

    SpreadCdsHelper::SpreadCdsHelper(
            const boost::shared_ptr<ObjectHandler::ValueObject>& properties,
            const QuantLib::Handle<QuantLib::Quote>& quote,
            const QuantLib::Period& period,
            QuantLib::Natural settlementDays,
            const QuantLib::Calendar& calendar,
            QuantLib::Frequency frequency,
            QuantLib::BusinessDayConvention paymentConvention,
            QuantLib::DateGeneration::Rule rule,
            const QuantLib::DayCounter& dayCounter,
            QuantLib::Real recoveryRate,
            const QuantLib::Handle<QuantLib::YieldTermStructure>& yieldTS,
            bool settlesAccrual,
            bool paysAtDefaultTime,
            bool permanent) : DefaultProbabilityHelper(properties, permanent) {

        libraryObject_ = boost::shared_ptr<QuantLib::DefaultProbabilityHelper>(new
		       QuantLib::SpreadCdsHelper(quote,
						 period,
						 settlementDays,
						 calendar,
						 frequency,
						 paymentConvention,
						 rule,
						 dayCounter,
						 recoveryRate,
						 yieldTS,
						 settlesAccrual,
						 paysAtDefaultTime));
    }

    UpfrontCdsHelper::UpfrontCdsHelper(
            const boost::shared_ptr<ObjectHandler::ValueObject>& properties,
            const QuantLib::Handle<QuantLib::Quote>& quote,
            QuantLib::Rate runningSpread,
            const QuantLib::Period& period,
            QuantLib::Natural settlementDays,
            const QuantLib::Calendar& calendar,
            QuantLib::Frequency frequency,
            QuantLib::BusinessDayConvention paymentConvention,
            QuantLib::DateGeneration::Rule rule,
            const QuantLib::DayCounter& dayCounter,
            QuantLib::Real recoveryRate,
            const QuantLib::Handle<QuantLib::YieldTermStructure>& yieldTS,
            QuantLib::Natural upfrontSettlementDays,
            bool settlesAccrual,
            bool paysAtDefaultTime,
            bool permanent) : DefaultProbabilityHelper(properties, permanent) {
        libraryObject_ = boost::shared_ptr<QuantLib::DefaultProbabilityHelper>(new
		       QuantLib::UpfrontCdsHelper(quote,
                                          runningSpread,
                                          period,
                                          settlementDays,
                                          calendar,
                                          frequency,
                                          paymentConvention,
                                          rule,
                                          dayCounter,
                                          recoveryRate,
                                          yieldTS,
                                          upfrontSettlementDays,
                                          settlesAccrual,
                                          paysAtDefaultTime));
    }

    HazardRateCurve::HazardRateCurve(
            const boost::shared_ptr<ObjectHandler::ValueObject>& properties,
            const std::vector<QuantLib::Date>& dates,
            const std::vector<QuantLib::Rate>& hazardRates,
            const QuantLib::DayCounter& dayCounter,
            bool permanent)
        : DefaultProbabilityTermStructure(properties, permanent) {
        QL_REQUIRE(!dates.empty(), "no input dates given");
        QL_REQUIRE(dates.size() == hazardRates.size(),
                   "vector sizes differ");
        libraryObject_ = boost::shared_ptr<QuantLib::Extrapolator>(
        new QuantLib::InterpolatedHazardRateCurve<QuantLib::BackwardFlat>(
 				 dates, hazardRates, dayCounter));
    }

    PiecewiseFlatHazardRateCurve::PiecewiseFlatHazardRateCurve(
            const boost::shared_ptr<ObjectHandler::ValueObject>& properties,
            const QuantLib::Date& referenceDate,
            const std::vector<boost::shared_ptr<QuantLib::DefaultProbabilityHelper> >& helpers,
            const QuantLib::DayCounter& dayCounter,
            QuantLib::Real accuracy,
            bool permanent)
        : DefaultProbabilityTermStructure(properties, permanent) {
        libraryObject_ = boost::shared_ptr<QuantLib::Extrapolator>(new
               QuantLib::PiecewiseDefaultCurve<QuantLib::HazardRate,QuantLib::BackwardFlat>(referenceDate, helpers, dayCounter));
    }

    PiecewiseFlatForwardCurve::PiecewiseFlatForwardCurve(
            const boost::shared_ptr<ObjectHandler::ValueObject>& properties,
            const QuantLib::Date& referenceDate,
            const std::vector<boost::shared_ptr<QuantLib::RateHelper> >& helpers,
            const QuantLib::DayCounter& dayCounter,
            QuantLib::Real accuracy,
            bool permanent)
        : YieldTermStructure(properties, permanent) {
        libraryObject_ = boost::shared_ptr<QuantLib::Extrapolator>(new
               QuantLib::PiecewiseYieldCurve<QuantLib::Discount,QuantLib::LogLinear>(referenceDate, helpers, dayCounter));
    }

}


#include <stdio.h>

#include <complex>
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <cmath>
#include <utility>

#include <boost/timer/timer.hpp>

#include "dune/grid/config.h"

#include "dune/common/stdstreams.hh"
#include "dune/grid/sgrid.hh"
#include "dune/grid/uggrid.hh"
#include "dune/grid/io/file/amirameshreader.hh"
#include "dune/grid/io/file/amirameshwriter.hh"
#include "dune/grid/common/gridinfo.hh"
#include "dune/istl/solvers.hh"

#include "utilities/enums.hh"
#include "fem/assemble.hh"
#include "fem/gridmanager.hh"
#include "fem/lagrangespace.hh"
#include "fem/functional_aux.hh"
#include "fem/coarsening.hh"
#include "fem/norms.hh"

#include "linalg/umfpack_solve.hh"

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

#include "io/vtk.hh"
#include "io/amira.hh"

#include "utilities/kaskopt.hh"
#include "amiramesh.hh"

using namespace Kaskade;

#include "integrate.hh"
#include "aliev.hh"

boost::timer::cpu_timer partTimer;
double assTime, solveTime;

struct InitialValue
{
  typedef double Scalar;
  static int const components = 1;
  typedef Dune::FieldVector<Scalar,components> ValueType;

  InitialValue(int c): component(c) {}

  template <class Cell> int order(Cell const&) const { return std::numeric_limits<int>::max(); }
  template <class Cell>
  ValueType value(Cell const& cell,
                  Dune::FieldVector<typename Cell::ctype,Cell::dimension> const& localCoordinate) const
  {
	Dune::FieldVector<typename Cell::ctype,Cell::dimensionworld> x = cell.geometry().global(localCoordinate);
	double R=1.0;		// slab: R=0.09		heart: R=1.0
	double beta=1000.0;
	double f=0.0;
	double r;

	//r = sqrt( (x[0]-(0.3))*(x[0]-(0.3)) + (x[1]-(-2.4))*(x[1]-(-2.4)) + (x[2]-182.5)*(x[2]-182.5) );	// heart smooth
	r = sqrt( (x[0]-(-0.9))*(x[0]-(-0.9)) + (x[1]-1.7)*(x[1]-1.7) + (x[2]-1.1)*(x[2]-1.1) );			// heart glenn
	//r = sqrt( (x[0]-1.5)*(x[0]-1.5) + (x[1]-1.5)*(x[1]-1.5) + (x[2]-1.1)*(x[2]-1.1) );				// slab
	beta = -1e4*log(0.001);
	if (r < R) f = 1;
	else f = exp(-beta*(R-r)*(R-r));

    if (component==0)
      return 0.0;		// 0.95*f;
    else if (component==1)
      return 0.0;
    else
      assert("wrong index!\n"==0);
    return 0;
  }

private:
  int component;
};

template <class Eq>
typename Eq::AnsatzVars::VariableSet transformBack(Eq const& eq, typename Eq::AnsatzVars::VariableSet const& vars)
{
  typename Eq::AnsatzVars::VariableSet  x(vars);

  for (int j=0; j<boost::fusion::at_c<0>(x.data).space().degreesOfFreedom(); ++j) {
    (boost::fusion::at_c<0>(x.data)).coefficients()[j] = eq.theta((boost::fusion::at_c<0>(x.data)).coefficients()[j]);
  }

  return x;
}

template <class Eq>
typename Eq::AnsatzVars::VariableSet transformBackDeriv(Eq const& eq, typename Eq::AnsatzVars::VariableSet const& vars)
{
  typename Eq::AnsatzVars::VariableSet  x(vars);

  for (int j=0; j<boost::fusion::at_c<0>(x.data).space().degreesOfFreedom(); ++j) {
    (*boost::fusion::at_c<0>(x.data))[j] = eq.dtheta((*boost::fusion::at_c<0>(x.data))[j]);
  }

  return x;
}

template <class Eq>
typename Eq::AnsatzVars::VariableSet transformBackDerivMul(Eq const& eq, typename Eq::AnsatzVars::VariableSet const& vars1, typename Eq::AnsatzVars::VariableSet const& vars2)
{
  typename Eq::AnsatzVars::VariableSet  x(vars1);

  for (int j=0; j<boost::fusion::at_c<0>(x.data).space().degreesOfFreedom(); ++j) {
    (*boost::fusion::at_c<0>(x.data))[j] = eq.dtheta((*boost::fusion::at_c<0>(x.data))[j]) * (*boost::fusion::at_c<0>(vars2.data))[j];
  }

  return x;
}

template <class Eq>
typename Eq::AnsatzVars::VariableSet transformForward(Eq const& eq, typename Eq::AnsatzVars::VariableSet const& vars)
{
  typename Eq::AnsatzVars::VariableSet  x(vars);

  for (int j=0; j<boost::fusion::at_c<0>(x.data).space().degreesOfFreedom(); ++j) {
    (*boost::fusion::at_c<0>(x.data))[j] = eq.zeta((*boost::fusion::at_c<0>(x.data))[j]);
  }

  return x;
}

template <class Eq>
typename Eq::AnsatzVars::VariableSet transformInitial(Eq const& eq, typename Eq::AnsatzVars::VariableSet const& vars)
{
  typename Eq::AnsatzVars::VariableSet  x(vars);

  for (int j=0; j<boost::fusion::at_c<0>(x.data).space().degreesOfFreedom(); ++j) {
    (*boost::fusion::at_c<0>(x.data))[j] = eq.zetaInitial((*boost::fusion::at_c<0>(x.data))[j]);
  }

  return x;
}

int main(int argc, char *argv[])
{
  using namespace boost::fusion;

  int verbosityOpt = 1;
  bool dump = true;
  std::unique_ptr<boost::property_tree::ptree> pt = getKaskadeOptions(argc, argv, verbosityOpt, dump);

  std::cout << "Start cardiac simulation (Aliev-Panfilov)" << std::endl;

  fexcept_t flag;
  fegetexceptflag(&flag,FE_DIVBYZERO|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
  fesetexceptflag(&flag,FE_DIVBYZERO|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);

  boost::timer::cpu_timer totalTimer;

  int const dim = 3;
  int refinements;
  int  order, extrapolOrder, maxSteps;
  double dt, maxDT, T, rTolT, aTolT, rTolX, aTolX, writeInterval;
  std::string logname = "cardio.log";
  bool refineTimeStep;
  std::string zetaFile, thetaFile;
  double xi, a, gs, ga, mu1, mu2, eps1, kappa, v_rest, v_peak;

  std::string empty, geoFile("slab242.am");
  geoFile = getParameter(pt, "file", geoFile);

  refinements = 0;   // slab: 3,  Herz: 0
  order = 2;
  extrapolOrder = 2;
  T = 1.0;
  dt = 0.01;
  maxDT = 4.0;
  writeInterval = 1.0;
  refineTimeStep = true;
  rTolT = 1e-3;
  aTolT = 1e-3;
  rTolX = 1e-3;
  aTolX = 1e-3;
  maxSteps = 10000;
// 	thetaFile = "zetaLinearInv.gnu";
// 	zetaFile  = "zetaLinear.gnu";
// 	thetaFile = "zetaInv.gnu";     			// original nonlinear scaling
// 	zetaFile  = "zeta.gnu";        			// original nonlinear scaling
// 	thetaFile = "theta-tanh5-100000.gnu";   // tanh nonlinear scaling
// 	zetaFile  = "zeta-tanh5-100000.gnu";    // tanh nonlinear scaling
	thetaFile = "theta-tanh2-100000.gnu";   // tanh nonlinear scaling
	zetaFile  = "zeta-tanh2-100000.gnu";    // tanh nonlinear scaling
// 	thetaFile = "zetaInv-MW.gnu";     		// MW nonlinear scaling
// 	zetaFile  = "zeta-MW.gnu";        		// MW nonlinear scaling
// 	thetaFile = "theta-2d-1e-5.gnu";		// MW nonlinear scaling
// 	zetaFile  = "zeta-2d-1e-5.gnu";			// MW nonlinear scaling
//	thetaFile = "theta-2d-1e-7.gnu";		// MW nonlinear scaling
//	zetaFile  = "zeta-2d-1e-7.gnu";			// MW nonlinear scaling
// 	thetaFile = "zetaInvQuadratic.gnu";     // Quadratic nonlinear scaling
// 	zetaFile  = "zetaQuadratic.gnu";        // Quadratic nonlinear scaling
// 	thetaFile = "zetaIdentityInv.gnu";
// 	zetaFile  = "zetaIdentity.gnu";
// 	thetaFile = "";
// 	zetaFile  = "";

  xi   = 1.0;
  kappa = 0.001;
  a = 0.1;
  gs = 8.0;
  ga = 8.0;
  mu1 = 0.07;
  mu2 = 0.3;
  eps1 = 0.01;

  v_rest = 0.0;   //-85.0;
  v_peak = 1.0;   //35.0;

  int heapSize=16384;
  typedef Dune::UGGrid<dim> Grid;
  Grid::setDefaultHeapSize(heapSize);
  Dune::GridFactory<Grid> factory;

  DuneAmiraMesh<dim,dim,Grid> mesh(geoFile.c_str());
  mesh.InsertUGGrid(factory);

  std::unique_ptr<Grid> grid( factory.createGrid() );
  for (int k=0; k<refinements; k++)
  {
    grid->globalRefine(1);
  }

  // some information on the refined mesh
  std::cout << std::endl << "Grid: " << grid->size(0) << " tetrahedra, " << std::endl;
  std::cout << "      " << grid->size(1) << " triangles, " << std::endl;
  std::cout << "      " << grid->size(dim-1) << " edges, " << std::endl;
  std::cout << "      " << grid->size(dim) << " points" << std::endl;
  // a gridmanager is constructed
  // as connector between geometric and algebraic information
  GridManager<Grid> gridManager(std::move(grid));
  gridManager.enforceConcurrentReads(true);

  typedef Grid::LeafGridView LeafView;

  	// construct involved spaces.
    //typedef ContinuousHierarchicMapper<double,Grid>  LMapper;
  typedef ContinuousLagrangeMapper<double,LeafView> LMapper;
  typedef FEFunctionSpace<LMapper> H1Space;

  H1Space h1Space(gridManager,gridManager.grid().leafView(),order);

  	typedef boost::fusion::vector<H1Space const*> Spaces;
  	Spaces spaces(&h1Space);

  	// construct variable list.
  	typedef boost::fusion::vector<VariableDescription<0,1,0>,
    							  VariableDescription<0,1,1> > VariableDescriptions;
  	std::string varNames[2] = { "u", "v" };

  	typedef VariableSetDescription<Spaces,VariableDescriptions> VariableSet;
  	VariableSet variableSet(spaces,varNames);

  	typedef AlievPanfilovEquation<double,VariableSet> Equation;
  	Equation Eq(xi,kappa,a,gs,ga,mu1,mu2,eps1,v_rest,v_peak,zetaFile,thetaFile);
  	// Eq.check();

  std::vector<VariableSet::VariableSet> solutions;
  std::cout << "solutions.size = " << solutions.size() << std::endl;
  // std::vector<VariableSet::VariableSet> devnull;

  Eq.time(0);
  VariableSet::VariableSet x(variableSet);
  Eq.scaleInitialValue<0>(InitialValue(0),x);
  Eq.scaleInitialValue<1>(InitialValue(1),x);

  T     = 1.0;
  dt    = 1e-3;			//0.001;
  maxDT = 1.0;
  rTolT = 5e-3;		// slab: 1e-2; herz(Glenn): 5e-3;		//1e-5/8.0;
  aTolT = 5e-3;		// slab: 1e-2; herz(Glenn): 5e-3;		//1e-5/8.0;
  rTolX = 1e-2;		// slab: 1e-2; herz(Glenn): 5e-3;		//1e-5*2.0;
  aTolX = 1e-2;		// slab: 1e-2; herz(Glenn): 5e-3;		//1e-5*2.0;
  maxSteps = 10000;
  x = integrate(gridManager,Eq,variableSet,spaces,gridManager.grid(),
                dt,maxDT,T,maxSteps,rTolT,aTolT,rTolX,aTolX,extrapolOrder,
                std::back_inserter(solutions),writeInterval,x,DirectType::MUMPS);

  T     = 225.0;
  dt    = 5e-3;		//0.001;
  rTolT = 2e-3;		//1e-5/8.0;
  aTolT = 2e-3;		//1e-5/8.0;
  rTolX = 1.0e-2;   //1.5e-2;		//1e-5*2.0;
  aTolX = 1.0e-2;   //1.5e-2;		//1e-5*2.0;
  maxSteps = 10000;
  x = integrate(gridManager,Eq,variableSet,spaces,gridManager.grid(),
                dt,maxDT,T,maxSteps,rTolT,aTolT,rTolX,aTolX,extrapolOrder,
                std::back_inserter(solutions),writeInterval,x,DirectType::MUMPS);

  T     = 226.0;
  dt    = 1e-3;			//0.001;
  rTolT = 2e-3;		//1e-5/8.0;
  aTolT = 2e-3;		//1e-5/8.0;
  rTolX = 1.5e-2;		//1e-5*2.0;
  aTolX = 1.5e-2;		//1e-5*2.0;
  maxSteps = 10000;
  x = integrate(gridManager,Eq,variableSet,spaces,gridManager.grid(),
                dt,maxDT,T,maxSteps,rTolT,aTolT,rTolX,aTolX,extrapolOrder,
                std::back_inserter(solutions),writeInterval,x,DirectType::MUMPS);

  T     = 1000.0;
  dt    = 1e-3;		//0.001;
  rTolT = 2e-3;		//1e-5/8.0;
  aTolT = 2e-3;		//1e-5/8.0;
  rTolX = 1.5e-2;		//1e-5*2.0;
  aTolX = 1.5e-2;		//1e-5*2.0;
  maxSteps = 10000;
  x = integrate(gridManager,Eq,variableSet,spaces,gridManager.grid(),
                dt,maxDT,T,maxSteps,rTolT,aTolT,rTolX,aTolX,extrapolOrder,
                std::back_inserter(solutions),writeInterval,x,DirectType::MUMPS);

  std::cout << " End of integrate" << std::endl;

//   if (order>1)
//     gridManager.globalRefine(order);

//   for (int i=0; i<solutions.size(); ++i) {
//     //VariableSet::VariableSet x(transformBack(Eq,solutions[i]));
//     VariableSet::VariableSet xT(transformBack(Eq,x));
//
//     std::ostringstream fn;
//     fn << "graph/out-rescaled";
//     fn.width(3);
//     fn.fill('0');
//     fn.setf(std::ios_base::right,std::ios_base::adjustfield);
//     fn << i;
//     fn.flush();
//     writeVTKFile(variableSet,xT,fn.str());
//   }

//
//   for (int i=0; i<solutions.size(); ++i) {
//     std::ostringstream fn;
//     fn << "graph/outScaled";
//     fn.width(3);
//     fn.fill('0');
//     fn.setf(std::ios_base::right,std::ios_base::adjustfield);
//     fn << i;
//     fn.flush();
//     writeVTKFile(variableSet,solutions[i],fn.str());
//   }

  std::cout << "End Aliev-Panfilov integration" << std::endl;

  return 0;
}