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

/*
 Copyright (C) 2015 Peter Caspers
 Copyright (C) 2015 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 <ql/experimental/models/betaetacore.hpp>

#include <ql/errors.hpp>
#include <ql/math/modifiedbessel.hpp>
#include <ql/math/integrals/gausslobattointegral.hpp>
#include <ql/math/integrals/segmentintegral.hpp>
#include <ql/math/interpolations/bilinearinterpolation.hpp>
#include <ql/math/interpolations/bicubicsplineinterpolation.hpp>
#include <ql/methods/finitedifferences/meshers/concentrating1dmesher.hpp>

#include <boost/math/special_functions/gamma.hpp>
#include <boost/make_shared.hpp>

#include <iostream>

namespace QuantLib {

BetaEtaCore::BetaEtaCore(const Array &times, const Array &alpha,
                         const Array &kappa, const Real &beta, const Real &eta)
    : times_(times), alpha_(alpha), kappa_(kappa), beta_(beta), eta_(eta),
      ghPoints_(8), prob_y_0_cutoff(1E-6), kappa_cutoff(1E-6) {

    QL_REQUIRE(beta > 0.0, "beta (" << beta << ") must be positive");
    QL_REQUIRE(eta >= 0.0 && eta <= 1.0, " eta (" << eta
                                                  << ") must be in [0,1]");
    QL_REQUIRE(alpha.size() == times.size() + 1,
               "alpha size (" << alpha.size()
                              << ") must be equal to times size ("
                              << times.size() << ") plus one");
    QL_REQUIRE(kappa.size() == 1 || kappa.size() == times.size() + 1,
               "kappa size (" << kappa.size()
                              << ") must be equal to times size ("
                              << times.size() << ") plus one or equal to one");
    for (Size i = 0; i < times.size(); ++i) {
        QL_REQUIRE(times[i] > 0.0, "time #" << i << " (" << times[i]
                                            << ") must be positive");
        if (i < times.size() - 1) {
            QL_REQUIRE(times[i] < times[i + 1],
                       "times must be strictly increasing, #"
                           << i << " and #" << (i + 1) << " are " << times[i]
                           << " and " << times[i + 1] << " respectively");
        }
    }

    // integrator and fallback to compute M directly in terms of x
    integrator_ = boost::make_shared<GaussLobattoIntegral>(10000, 1E-8, 1E-8);
    integrator2_ = boost::make_shared<SegmentIntegral>(250);

    // integrator to compute M for case eta = 1
    ghIntegrator_ = boost::make_shared<GaussHermiteIntegration>(ghPoints_);

    // integrator and fallback to tabulate M
    preIntegrator_ =
        boost::make_shared<GaussLobattoIntegral>(10000, 1E-8, 1E-8);
    preIntegrator2_ = boost::make_shared<SegmentIntegral>(250);

    // tabulation data
    etaSize_ = detail::eta_pre_size;
    uSize_ = detail::u_pre_size;
    SuSize_ = detail::Su_pre_size;
    vSize_ = detail::v_pre_size;
    y0Size_ = detail::y0_pre_size;

    eta_pre_ = std::vector<Real>(detail::eta_pre, detail::eta_pre + etaSize_);
    u_pre_ = std::vector<Real>(detail::u_pre, detail::u_pre + uSize_);
    Su_pre_ = std::vector<Real>(detail::Su_pre, detail::Su_pre + SuSize_);
    v_pre_ = std::vector<Real>(detail::v_pre, detail::v_pre + vSize_);
    y0_pre_ = std::vector<Real>(detail::y0_pre, detail::y0_pre + y0Size_);

    // interpolation of tabulated data
    for (Size i = 0; i < etaSize_; ++i) {
        boost::shared_ptr<Matrix> zTmp =
            boost::make_shared<Matrix>(Su_pre_.size(), u_pre_.size());
        boost::shared_ptr<Matrix> z2Tmp =
            boost::make_shared<Matrix>(v_pre_.size(), y0_pre_.size());
        for (Size uu = 0; uu < uSize_; ++uu)
            for (Size vv = 0; vv < SuSize_; ++vv)
                (*zTmp)[vv][uu] = detail::M_pre[i][uu][vv];
        for (Size vv = 0; vv < vSize_; ++vv)
            for (Size yy = 0; yy < y0Size_; ++yy)
                (*z2Tmp)[yy][vv] = detail::p_pre[i][yy][vv];
        M_datasets_.push_back(zTmp);
        p_datasets_.push_back(z2Tmp);
        boost::shared_ptr<BilinearInterpolation> tmp =
            boost::make_shared<BilinearInterpolation>(
                u_pre_.begin(), u_pre_.end(), Su_pre_.begin(), Su_pre_.end(),
                *(M_datasets_[i]));
        boost::shared_ptr<BilinearInterpolation> tmp2 =
            boost::make_shared<BilinearInterpolation>(
                v_pre_.begin(), v_pre_.end(), y0_pre_.begin(), y0_pre_.end(),
                *(p_datasets_[i]));
        tmp->enableExtrapolation();
        tmp2->enableExtrapolation();
        M_surfaces_.push_back(tmp);
        p_surfaces_.push_back(tmp2);
    }
};

// integrand to compute M directly in terms of x
class BetaEtaCore::mIntegrand1 {
    const BetaEtaCore *model_;
    const Real t0_, x0_, t_;

  public:
    mIntegrand1(const BetaEtaCore *model, const Real t0, const Real x0,
                const Real t)
        : model_(model), t0_(t0), x0_(x0), t_(t) {}
    Real operator()(Real x) const {
        return model_->p(t0_, x0_, t_, x) *
               exp(-model_->lambda(t_) * (x - x0_));
    }
};

// integrand to compute prob_y_0 (directly in terms of x, eta < 0.5)
class BetaEtaCore::pIntegrand1 {
    const BetaEtaCore *model_;
    const Real t0_, x0_, t_;

  public:
    pIntegrand1(const BetaEtaCore *model, const Real t0, const Real x0,
                const Real t)
        : model_(model), t0_(t0), x0_(x0), t_(t) {}
    Real operator()(Real x) const { return model_->p(t0_, x0_, t_, x); }
};

// integrand to precompute M, 0 < eta < 1, eta != 0.5
class BetaEtaCore::mIntegrand2 {
    const BetaEtaCore *model_;
    const Real S_, u0_;

  public:
    mIntegrand2(const BetaEtaCore *model, const Real S, const Real u0)
        : model_(model), S_(S), u0_(u0) {}
    Real operator()(Real u) const {
        if (close(u, 0))
            return 0.0;
        Real eta = model_->eta();
        return model_->p_y_core1(
                   S_ * std::pow(1.0 - eta, 2.0 * eta) *
                       std::pow(u0_, 2.0 - 2.0 * eta),
                   std::pow(u0_, 1.0 - eta) * std::pow(1.0 - eta, eta - 1.0),
                   std::pow(u, 1.0 - eta) * std::pow(1.0 - eta, eta - 1.0),
                   eta) *
               exp(-(u - u0_));
    }
};

// integrand to precompute prob_y_0 (eta < 0.5)
class BetaEtaCore::pIntegrand2 {
    const BetaEtaCore *model_;
    const Real v_, y0_;

  public:
    pIntegrand2(const BetaEtaCore *model, const Real v, const Real y0)
        : model_(model), v_(v), y0_(y0) {}
    Real operator()(Real y) const {
        return model_->p_y_core0(v_, y0_, y, model_->eta());
    }
};

// integrand to compute M, eta = 1
class BetaEtaCore::mIntegrand3 {
    const BetaEtaCore *model_;
    const Real v_, y0_, lambda_;

  public:
    mIntegrand3(const BetaEtaCore *model, const Real v, const Real y0,
                const Real lambda)
        : model_(model), v_(v), y0_(y0), lambda_(lambda) {}
    Real operator()(Real z) const {
        Real beta = model_->beta();
        Real y = M_SQRT2 * std::sqrt(v_) * z + y0_ - beta * v_ / 2.0;
        return exp(-lambda_ * (exp(beta * y) - exp(beta * y0_)) / beta) *
               exp(-z * z);
    }
};

const Real BetaEtaCore::M(const Time t0, const Real x0, const Real t,
                          const bool useTabulation) const {

    // since we are assuming a reflecting barrier we can
    // return M = 0 for all eta when y is negative or zero
    if (x0 <= -1.0 / beta_)
        return 0.0;

    Real lambda = this->lambda(t);
    Real v = this->tau(t0, t);

    // for zero volatility we obviously have M = 0
    if (close(v, 0.0))
        return 0.0;

    // without the reflecting barrier at y=0 we could write
    // M = 0.5 *lambda * lambda * v for eta = 0
    // with the reflecting barrier assumed here, we do not
    // have a closed form solution

    if (close(eta_, 0.5)) {
        return M_eta_05(t0, x0, t);
    }
    if (close(eta_, 1.0)) {
        return M_eta_1(t0, x0, t);
    }

    Real result;
    if (useTabulation) {
        result = M_tabulated(t0, x0, t);
    } else {
        Real s = std::sqrt(tau(t0, t));
        if (close(s, 0.0))
            return 0.0;
        mIntegrand1 in(this, t0, x0, t);
        std::pair<Real, Real> d = detail::domain(
            in, x0, 1E-10, 1E-12, 1E-6, 1.1, -1.0 / beta_, QL_MAX_REAL);
        Real a = d.first;
        Real b = d.second;
        try {
            result = std::log(integrator_->operator()(in, a, b));
        } catch (...) {
            try {
                result = std::log(integrator2_->operator()(in, a, b));
            } catch (...) {
                QL_FAIL("could not compute M(" << t0 << "," << x0 << "," << t
                                               << "), tried integration over "
                                               << a << "..." << b);
            }
        }
    }

    Real singularProb = prob_y_0(t0, x0, t, useTabulation);
    Real singularTerm = 0.0;
    singularTerm = singularProb * exp(-lambda * (-1.0 / beta_ - x0));

    // only take the singular term into account if numerically significant
    if (singularTerm > std::exp(result) * QL_EPSILON) {
        result = std::log(std::exp(result) + singularTerm);
    }

    return result;
};

const Real BetaEtaCore::M_eta_1(const Real t0, const Real x0,
                                const Real t) const {
    if (x0 < -1.0 / beta_)
        return 0.0;
    Real lambda = this->lambda(t);

    Real y0 = y(x0, 1.0);
    Real v = this->tau(t0, t);
    Real result = M_1_SQRTPI *
                  ghIntegrator_->operator()(mIntegrand3(this, v, y0, lambda));
    return std::log(result);
}

const Real BetaEtaCore::M_eta_05(const Real t0, const Real x0,
                                 const Real t) const {
    if (x0 < -1.0 / beta_)
        return 0.0;
    Real lambda = this->lambda(t);
    Real v = this->tau(t0, t);
    return (1.0 + beta_ * x0) * lambda * lambda * v /
           (2.0 + beta_ * lambda * v);
}

const Real BetaEtaCore::M_tabulated(const Real t0, const Real x0,
                                    const Real t) const {

    Real vraw = this->tau(t0, t);
    Real lambda = this->lambda(t);

    if (close(eta_, 0.5) || close(eta_, 1.0)) {
        return M(t0, x0, t);
    }

    Real u0 = lambda / beta_ * std::fabs(1.0 + beta_ * x0);
    Real Su = vraw * beta_ * beta_ /
              std::pow(1.0 + beta_ * x0, 2.0 - 2.0 * eta_) *
              std::pow(u0, 2.0 - 0.5 * eta_);

    int etaIdx = std::upper_bound(eta_pre_.begin(), eta_pre_.end(), eta_) -
                 eta_pre_.begin();

    Real eta_weight_1 =
        (etaIdx < static_cast<int>(eta_pre_.size()) ? eta_pre_[etaIdx] - eta_
                                                    : 1.0 - eta_) /
        (etaIdx < static_cast<int>(eta_pre_.size())
             ? (eta_pre_[etaIdx] - eta_pre_[etaIdx - 1])
             : (1.0 - eta_pre_[etaIdx - 1]));

    Real eta_weight_2 = (eta_ - eta_pre_[etaIdx - 1]) /
                        (etaIdx < static_cast<int>(eta_pre_.size())
                             ? (eta_pre_[etaIdx] - eta_pre_[etaIdx - 1])
                             : (1.0 - eta_pre_[etaIdx - 1]));

    Real result_eta_lower = M_surfaces_[etaIdx - 1]->operator()(u0, Su);

    Real result_eta_higher;
    if (etaIdx < static_cast<int>(eta_pre_.size())) {
        result_eta_higher = M_surfaces_[etaIdx]->operator()(u0, Su);
    } else {
        result_eta_higher = M_eta_1(t0, x0, t);
    }

    Real result =
        (result_eta_lower * eta_weight_1 + result_eta_higher * eta_weight_2);

    if (u0 > u_pre_.back() || Su > Su_pre_.back())
        QL_FAIL("tabulated value lookup ("
                << u0 << "," << Su
                << ") would require extrapolation, bounds are u0_max="
                << u_pre_.back() << " and Su_max=" << Su_pre_.back());

    return result;
}

const Real BetaEtaCore::M(const Real u0, const Real Su) const {
    Real res;
    if (close(Su, 0.0)) {
        res = 1.0;
    } else {
        QL_REQUIRE(!close(eta_, 1.0), "M(u0,Su) is only defined for eta < 1");
        mIntegrand2 ig(this, Su / std::pow(u0, 2.0 - 0.5 * eta_), u0);
        std::pair<Real, Real> d =
            detail::domain(ig, u0, 1E-10, 1E-12, 1E-6, 1.1, 1E-10, QL_MAX_REAL);
        try {
            res = preIntegrator_->operator()(ig, d.first, d.second);
        } catch (...) {
            try {
                res = preIntegrator2_->operator()(ig, d.first, d.second);
            } catch (...) {
                QL_FAIL("could not compute M(" << u0 << "," << Su
                                               << "), tried integration over "
                                               << d.first << "..." << d.second);
            }
        }
    }
    Real a = close(res, 0.0) ? -50.0 : std::log(res);
    return a;
}

const Real BetaEtaCore::p_y_core0(const Real v, const Real y0, const Real y,
                                  const Real eta) const {
    QL_REQUIRE(!close(eta, 1.0), "eta must not be one in p_y_core0");
    if (close(y, 0.0) || close(y0, 0.0)) // i.e. x, x0 = -1/beta
        return 0.0;
    Real nu = 1.0 / (2.0 - 2.0 * eta);
    // 0.0 <= eta < 0.5
    if (eta < 0.5) {
        return std::pow(y0 / y, nu) * y / v *
               modifiedBesselFunction_i_exponentiallyWeighted(-nu, y0 * y / v) *
               exp(-(y - y0) * (y - y0) / (2.0 * v));
    }
    // 0.5 <= eta < 1.0
    return std::pow(y0 / y, nu) * y / v *
           modifiedBesselFunction_i_exponentiallyWeighted(nu, y0 * y / v) *
           exp(-(y - y0) * (y - y0) / (2.0 * v));
}

const Real BetaEtaCore::p_y_core1(const Real v, const Real y0, const Real y,
                                  const Real eta) const {
    QL_REQUIRE(!close(eta, 1.0), "eta must not be one in p_y_core1");
    return p_y_core0(v, y0, y, eta) * std::pow(y, eta / (eta - 1.0));
}

const Real BetaEtaCore::p_y(const Real v, const Real y0, const Real y,
                            const Real eta) const {
    // eta = 1.0
    if (close(eta, 1.0)) {
        return exp(-beta_ * y) / std::sqrt(2.0 * M_PI * v) *
               exp(-0.5 * (y - y0 + 0.5 * beta_ * v) *
                   (y - y0 + 0.5 * beta_ * v) / v);
    }
    // eta < 1.0
    return p_y_core1(v, y0, y, eta) * std::pow(1.0 - eta, eta / (eta - 1.0)) *
           std::pow(beta_, eta / (eta - 1.0));
}

const Real BetaEtaCore::p(const Time t0, const Real x0, const Real t,
                          const Real x) const {
    if (x <= -1.0 / beta_)
        return 0.0;
    // to avoid numerical instabilities when eta is close to
    // but not equal to one we interpolate the density between the largest
    // stable value for eta and one. Since the tabulation should be
    // stable w.r.t. the spanned eta grid, we use the largest grid value
    // from there as the cutoff value (which would typically be something
    // close to 0.99).
    Real v = this->tau(t0, t);
    if (eta_ <= eta_pre_.back()) {
        Real y0 = this->y(x0, eta_);
        Real y = this->y(x, eta_);
        return p_y(v, y0, y, eta_);
    } else {
        Real y0a = this->y(x0, eta_pre_.back());
        Real ya = this->y(x, eta_pre_.back());
        Real y0b = this->y(x0, 1.0);
        Real yb = this->y(x, 1.0);
        return 0.5 * (p_y(v, y0a, ya, eta_pre_.back()) + p_y(v, y0b, yb, 1.0));
    }
};

const Real BetaEtaCore::prob_y_0(const Real v, const Real y0) const {
    if (close(v, 0.0) || eta_ > eta_pre_.back())
        return 0.0;
    if (eta_ >= 0.5) {
        Real nu = 1.0 / (2.0 - 2.0 * eta_);
        Real res = boost::math::gamma_q(nu, y0 * y0 / (2.0 * v));
        return res;
    }
    // eta < 0.5
    pIntegrand2 inC(this, v, y0);
    std::pair<Real, Real> d =
        detail::domain(inC, y0, 1E-10, 1E-12, 1E-6, 1.1, 0.0, QL_MAX_REAL);
    Real a = d.first;
    Real b = d.second;
    Real result;
    try {
        result = 1.0 - integrator_->operator()(inC, a, b);
    } catch (...) {
        try {
            result = 1.0 - integrator2_->operator()(inC, a, b);
        } catch (...) {
            QL_FAIL("could not compute prob_y_0("
                    << v << "," << y0 << "), tried integration over " << a
                    << "..." << b);
        }
    }
    return result;
}

const Real BetaEtaCore::prob_y_0_tabulated(const Real v, const Real y0) const {
    if (close(v, 0.0) || eta_ > eta_pre_.back())
        return 0.0;
    int etaIdx = std::upper_bound(eta_pre_.begin(), eta_pre_.end(), eta_) -
                 eta_pre_.begin();

    // weight formulas are more general than needed because
    // etaIdx < eta_pre_.size() by the condition above
    Real eta_weight_1 =
        (etaIdx < static_cast<int>(eta_pre_.size()) ? eta_pre_[etaIdx] - eta_
                                                    : 1.0 - eta_) /
        (etaIdx < static_cast<int>(eta_pre_.size())
             ? (eta_pre_[etaIdx] - eta_pre_[etaIdx - 1])
             : (1.0 - eta_pre_[etaIdx - 1]));

    Real eta_weight_2 = (eta_ - eta_pre_[etaIdx - 1]) /
                        (etaIdx < static_cast<int>(eta_pre_.size())
                             ? (eta_pre_[etaIdx] - eta_pre_[etaIdx - 1])
                             : (1.0 - eta_pre_[etaIdx - 1]));

    Real result_eta_lower = p_surfaces_[etaIdx - 1]->operator()(v, y0);
    Real result_eta_higher = p_surfaces_[etaIdx]->operator()(v, y0);
    Real result =
        (result_eta_lower * eta_weight_1 + result_eta_higher * eta_weight_2);

    // if (v > v_pre_.back() || y0 > y0_pre_.back())
    //     QL_FAIL("tabulated value lookup ("
    //             << v << "," << y0
    //             << ") would require extrapolation, bounds are v_max="
    //             << v_pre_.back() << " and y0_max=" << y0_pre_.back());

    return result;
}

const Real BetaEtaCore::prob_y_0(const Time t0, const Real x0, const Time t,
                                 bool useTabulation) const {
    Real v = tau(t0, t);
    Real y0 = y(x0, eta_);
    Real result;
    if (useTabulation) {
        result = prob_y_0_tabulated(v, y0);
    } else {
        result = prob_y_0(v, y0);
    }
    // TODO ...
    if(result < prob_y_0_cutoff)
        result = 0.0;
    return result;
};

namespace detail {

const void
betaeta_tabulate(betaeta_tabulation_type type, std::ostream &out,
                 const Real eta_min, const Real eta_max, const Real u0_min,
                 const Real u0_max, const Real Su_min, const Real Su_max,
                 const Size u_size, const Size Su_size, const Size eta_size,
                 const Real c_u, const Real density_u, const Real c_Su,
                 const Real density_Su, const Real c_e, const Real density_e) {

    Concentrating1dMesher um(u0_min, u0_max, u_size,
                             std::make_pair(c_u, density_u), true);
    Concentrating1dMesher sum(Su_min, Su_max, Su_size,
                              std::make_pair(c_Su, density_Su), true);
    Concentrating1dMesher em(eta_min, eta_max, eta_size,
                             std::make_pair(c_e, density_e), true);

    out.precision(8);
    if (type == Cpp_M || type == Cpp_p) {

        out << "/* -*- mode: c++; tab-width: 4; indent-tabs-mode:"
            << "nil; c-basic-offset: 4 -*- */\n"
            << "\n "
            << "/*\n"
            << " Copyright (C) 2015 Peter Caspers\n"
            << " Copyright (C) 2015 Roland Lichters\n"
            << "\n"
            << " This file is part of QuantLib, a "
               "free-software/open-source "
               "library\n"
            << " for financial quantitative analysts and developers - "
               "http://quantlib.org/\n"
            << "\n"
            << " QuantLib is free software: you can redistribute it and/or "
               "modify it\n"
            << " under the terms of the QuantLib license.  You should have "
               "received a\n"
            << " copy of the license along with this program; if not, "
               "please "
               "email\n"
            << " <quantlib-dev@lists.sf.net>. The license is also "
               "available "
               "online at\n"
            << " <http://quantlib.org/license.shtml>.\n"
            << "\n"
            << " This program is distributed in the hope that it will be "
               "useful, but WITHOUT\n"
            << " ANY WARRANTY; without even the implied warranty of "
               "MERCHANTABILITY or FITNESS\n"
            << " FOR A PARTICULAR PURPOSE.  See the license for more "
               "details.\n"
            << "*/\n\n";

        out << "// this file was generated by "
               "QuantLib::detail::betaeta_tabulate\n"
               "// using the following parameters:\n";
        if (type == Cpp_M) {
            out << "// u0_min = " << u0_min << " u0_max = " << u0_max << "\n";
            out << "// Su_min = " << Su_min << " Su_max = " << Su_max << "\n";
            out << "// u_size = " << u_size << " Su_size = " << Su_size
                << " eta_size = " << eta_size << "\n";
            out << "// c_u = " << c_u << " density_u = " << density_u << "\n";
            out << "// c_Su = " << c_Su << " density_Su = " << density_Su
                << "\n";
            out << "// c_e = " << c_e << " density_e = " << density_e << "\n\n";
            out << "namespace QuantLib {\n"
                << "namespace detail {\n\n";
            out << "extern \"C\" const double eta_pre[] = {";
            for (Size i = 0; i < em.size() - 1; ++i)
                out << em.location(i) << (i < em.size() - 2 ? "," : "};\n\n");
            out << "extern \"C\" const double u_pre[] = {";
            for (Size i = 0; i < um.size(); ++i)
                out << um.location(i) << (i < um.size() - 1 ? "," : "};\n\n");
            out << "extern \"C\" const double Su_pre[] = {";
            for (int i = 0; i < static_cast<int>(sum.size()); ++i)
                out << (i == -1 ? 0.0 : sum.location(i))
                    << (i < static_cast<int>(sum.size()) - 1 ? "," : "};\n\n");
            out << "extern \"C\" const double M_pre[][" << um.size() << "]["
                << (sum.size()) << "] = {\n";
        } else {
            out << "// y0_min = " << u0_min << " y0_max = " << u0_max << "\n";
            out << "// v_min = " << Su_min << " v_max = " << Su_max << "\n";
            out << "// y0_size = " << u_size << " v_size = " << Su_size
                << " eta_size = " << eta_size << "\n";
            out << "// c_y0 = " << c_u << " density_y0 = " << density_u << "\n";
            out << "// c_v = " << c_Su << " density_v = " << density_Su
                << "\n";
            out << "// c_e = " << c_e << " density_e = " << density_e << "\n\n";
            out << "// note that the eta grid is taken from "
                   "betaetatabulation.cpp\n\n";
            out << "namespace QuantLib {\n"
                << "namespace detail {\n\n";
            out << "extern \"C\" const double y0_pre[] = {";
            for (Size i = 0; i < um.size(); ++i)
                out << um.location(i) << (i < um.size() - 1 ? "," : "};\n\n");
            out << "extern \"C\" const double v_pre[] = {";
            for (int i = 0; i < static_cast<int>(sum.size()); ++i)
                out << (i == -1 ? 0.0 : sum.location(i))
                    << (i < static_cast<int>(sum.size()) - 1 ? "," : "};\n\n");
            out << "extern \"C\" const double p_pre[][" << um.size() << "]["
                << (sum.size()) << "] = {\n";
        }
    }

    Array times;
    Array alpha(1, 0.01);
    Array kappa(1, 0.01);

    if (type == Cpp_M || type == Cpp_p) {
        for (Size e = 0; e < eta_size - 1; ++e) {
            Real eta = em.location(e);
            BetaEtaCore core(times, alpha, kappa, 1.0, eta);
            out << "// ========================  eta=" << eta << "\n";
            out << "{ ";
            for (Size i = 0; i < um.size(); ++i) {
                Real u0 = um.location(i);
                if (type == Cpp_M) {
                    out << "// eta=" << eta << " u0=" << u0 << "\n";
                } else {
                    out << "// eta=" << eta << " y0=" << u0 << "\n";
                }
                out << "{";
                for (int j = 0; j < static_cast<int>(sum.size()); ++j) {
                    Real v = j == -1 ? 0.0 : sum.location(j);
                    Real lres;
                    if (type == Cpp_M) {
                        lres = core.M(u0, v);
                    } else {
                        lres = core.prob_y_0(v, u0);
                    }
                    out << lres
                        << (j < static_cast<int>(sum.size()) - 1 ? "," : "}");
                }
                out << (i < um.size() - 1 ? ",\n" : "}");
            }
            out << (e < eta_size - 2 ? ",\n" : "};\n");
        }
        out << "} // namespace detail\n"
            << "} // namespace QuantLib\n";
    }

    if (type == GnuplotEUV) {
        for (Size e = 0; e < eta_size - 1; ++e) {
            Real eta = em.location(e);
            BetaEtaCore core(times, alpha, kappa, 1.0, eta);
            for (Size i = 0; i < um.size(); ++i) {
                Real u0 = um.location(i);
                for (int j = 0; j < static_cast<int>(sum.size()); ++j) {
                    Real v = j == -1 ? 0.0 : sum.location(j);
                    Real lres = core.M(u0, v);
                    out << eta << " " << u0 << " " << v << " " << lres << "\n";
                }
                out << "\n";
            }
        }
    }

    if (type == GnuplotUEV) {
        for (Size i = 0; i < um.size(); ++i) {
            Real u0 = um.location(i);
            for (Size e = 0; e < eta_size - 1; ++e) {
                Real eta = em.location(e);
                BetaEtaCore core(times, alpha, kappa, 1.0, eta);
                for (int j = 0; j < static_cast<int>(sum.size()); ++j) {
                    Real v = j == -1 ? 0.0 : sum.location(j);
                    Real lres = core.M(u0, v);
                    out << u0 << " " << eta << " " << v << " " << lres << "\n";
                }
                out << "\n";
            }
        }
    }

    if (type == GnuplotVEU) {
        for (int j = 0; j < static_cast<int>(sum.size()); ++j) {
            Real v = j == -1 ? 0.0 : sum.location(j);
            for (Size e = 0; e < eta_size - 1; ++e) {
                Real eta = em.location(e);
                BetaEtaCore core(times, alpha, kappa, 1.0, eta);
                for (Size i = 0; i < um.size(); ++i) {
                    Real u0 = um.location(i);
                    Real lres = core.M(u0, v);
                    out << v << " " << eta << " " << u0 << " " << lres << "\n";
                }
                out << "\n";
            }
        }
    }

    if (type == GnuplotP) {
        for (Size e = 0; e < eta_size - 1; ++e) {
            Real eta = em.location(e);
            for (Size i = 0; i < um.size(); ++i) {
                Real u0 = um.location(i);
                for (int j = 0; j < static_cast<int>(sum.size()); ++j) {
                    Real v = j == -1 ? 0.0 : sum.location(j);
                    BetaEtaCore core(times, alpha, kappa, 1.0, eta);
                    Real lres = core.prob_y_0(v, u0);
                    out << eta << " " << v << " " << u0 << " " << lres << "\n";
                }
                out << "\n";
            }
        }
    }
}

} // namespace detail

} // namespace QuantLib


//==================================================================================================
/*!
  @file
  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_GAMMA_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_GAMMA_HPP_INCLUDED

#include <boost/simd/function/std.hpp>
#include <boost/config.hpp>
#include <boost/simd/arch/common/detail/generic/gamma_kernel.hpp>
#include <boost/simd/constant/half.hpp>
#include <boost/simd/constant/inf.hpp>
#include <boost/simd/constant/nan.hpp>
#include <boost/simd/constant/one.hpp>
#include <boost/simd/constant/pi.hpp>
#include <boost/simd/constant/three.hpp>
#include <boost/simd/constant/two.hpp>
#include <boost/simd/function/abs.hpp>
#include <boost/simd/function/copysign.hpp>
#include <boost/simd/function/floor.hpp>
#include <boost/simd/function/is_eqz.hpp>
#include <boost/simd/function/is_even.hpp>
#include <boost/simd/function/is_ltz.hpp>
#include <boost/simd/function/sinpi.hpp>
#include <boost/simd/function/stirling.hpp>

#include <boost/simd/detail/dispatch/function/overload.hpp>
#include <cmath>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  namespace bs = boost::simd;
  using bs::std_tag;
  BOOST_DISPATCH_OVERLOAD ( gamma_
                          , (typename A0)
                          , bd::cpu_
                          , bd::scalar_< bd::floating_<A0> >
                          )
  {
    BOOST_FORCEINLINE A0 operator() (A0 a0) const BOOST_NOEXCEPT
    {
      if (is_eqz(a0)) return copysign(Inf<A0>(), a0);
      #ifndef BOOST_SIMD_NO_INVALIDS
      if( is_nan(a0) || (a0 == Minf<A0>()) ) return Nan<A0>();
      if (a0 == Inf<A0>()) return a0;
      #endif

      A0 x = a0;
      if (inftest(a0)) return Inf<A0>();
      A0 q = bs::abs(x);
      if(x < A0(-33.0))
      {
//        return std::tgamma(a0);
        A0 st = stirling(q);
        A0 p =  floor(q);
        auto iseven =  is_even((int32_t)p);
        if (p == q) return Nan<A0>();
        A0 z = q - p;
        if( z > Half<A0>() )
        {
          p += One<A0>();
          z = q - p;
        }
        z = q*sinpi(z);
        if( is_eqz(z) ) return Nan<A0>();
        st = Pi<A0>()/(bs::abs(z)*st);
        return iseven  ? -st : st;
      }
      A0 z = One<A0>();
      while( x >= Three<A0>() )
      {
        x -= One<A0>();
        z *= x;
      }
      while( is_ltz(x) )
      {
        z /= x;
        x += One<A0>();
      }
      while( x < Two<A0>() )
      {
        if( is_eqz(x)) return Nan<A0>();
        z /= x;
        x +=  One<A0>();
      }
      if( x == Two<A0>() ) return(z);
      x -= Two<A0>();
      return z*detail::gamma_kernel<A0>::gamma1(x);
    }
  private:
    static BOOST_FORCEINLINE bool inftest(const float a0)
    {
      return a0 > 35.4f;
    }
    static BOOST_FORCEINLINE bool inftest(const double a0)
    {
      return a0 > 171.624;
    }

  };
  BOOST_DISPATCH_OVERLOAD ( gamma_
                          , (typename A0)
                          , bd::cpu_
                          , bs::std_tag
                          , bd::scalar_< bd::floating_<A0> >
                          )
  {
    BOOST_FORCEINLINE A0 operator() (const std_tag &, A0 a0) const BOOST_NOEXCEPT
    {
      return std::tgamma(a0);
    }
  };
} } }

#endif


/*
 * Copyright 2018, LAAS-CNRS
 * Author: Steve Tonneau
 */

#ifndef BEZIER_COM_TRAJ_DEFINITIONS_H
#define BEZIER_COM_TRAJ_DEFINITIONS_H

#include <hpp/centroidal-dynamics/centroidal_dynamics.hh>
#include <ndcurves/bezier_curve.h>
#include <Eigen/Dense>

namespace bezier_com_traj {

typedef double value_type;
typedef Eigen::Matrix<value_type, 3, 3> Matrix3;
typedef Eigen::Matrix<value_type, 6, 3> Matrix63;
typedef Eigen::Matrix<value_type, 3, 9> Matrix39;
typedef Eigen::Matrix<value_type, Eigen::Dynamic, 3> MatrixX3;
typedef Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic> MatrixXX;
typedef centroidal_dynamics::Vector3 Vector3;
typedef centroidal_dynamics::Vector6 Vector6;
typedef centroidal_dynamics::VectorX VectorX;

typedef Eigen::Ref<Vector3> Ref_vector3;
typedef Eigen::Ref<VectorX> Ref_vectorX;
typedef Eigen::Ref<MatrixX3> Ref_matrixX3;
typedef Eigen::Ref<MatrixXX> Ref_matrixXX;

typedef const Eigen::Ref<const Vector3>& Cref_vector3;
typedef const Eigen::Ref<const Vector6>& Cref_vector6;
typedef const Eigen::Ref<const VectorX>& Cref_vectorX;
typedef const Eigen::Ref<const MatrixXX>& Cref_matrixXX;
typedef const Eigen::Ref<const MatrixX3>& Cref_matrixX3;

typedef Matrix63 matrix6_t;
typedef Vector6 point6_t;
typedef Matrix3 matrix3_t;
typedef Vector3 point3_t;

typedef Eigen::Vector3d point_t;
typedef const Eigen::Ref<const point_t>& point_t_tC;

/**
 * @brief waypoint_t a waypoint is composed of a  6*3 matrix that depend
 * on the variable x, and of a 6d vector independent of x, such that
 * each control point of the target bezier curve is given by pi = wix * x + wis
 */
typedef std::pair<matrix6_t, point6_t> waypoint6_t;
typedef std::pair<matrix3_t, point3_t> waypoint3_t;
typedef std::pair<Matrix39, point3_t> waypoint9_t;
struct waypoint_t;  // forward declaration

typedef ndcurves::bezier_curve<double, double, true, point_t> bezier_t;
typedef ndcurves::bezier_curve<double, double, true, waypoint_t> bezier_wp_t;
typedef ndcurves::bezier_curve<double, double, true, point6_t> bezier6_t;

typedef std::vector<std::pair<double, int> > T_time;
typedef T_time::const_iterator CIT_time;

typedef std::pair<double, point3_t> coefs_t;

}  // end namespace bezier_com_traj

#endif


#define PY_ARRAY_UNIQUE_SYMBOL phist_PyArray_API
#define NO_IMPORT_ARRAY

#include <string>
#include <cmath>

#include <boost/python/suite/indexing/vector_indexing_suite.hpp>

#include <boost/array.hpp>

#include <vigra/numpy_array.hxx>
#include <vigra/numpy_array_converters.hxx>

#include "seglib/histogram/histogram.hxx"
#include "seglib/histogram/histogram_python.hxx"

namespace python = boost::python;

namespace histogram {

    vigra::NumpyAnyArray labelHistogram(
        vigra::NumpyArray<3, vigra::Multiband<LabelType>  >         img,
        const LabelType                                             nLabels,
        const size_t                                                r,
        //output
        vigra::NumpyArray<4, float >    res = vigra::NumpyArray<4, float >()
    ){
        const size_t nChannels=img.shape(2);
        // allocate output
        typedef typename vigra::NumpyArray<4, float >::difference_type Shape4;
        Shape4 shape(img.shape(0),img.shape(1),nChannels,nLabels);
        res.reshapeIfEmpty(shape);
        std::fill(res.begin(),res.end(),0.0);

        // coordinate in the res array (pixel wise histogram)
        // (x,y,c,bin)
        Shape4 histCoord;
        const vigra::TinyVector<float, 2>  radius1(r+1,r+1);

        vigra::TinyVector<int,2>  start,end,c;

        for(histCoord[0]=0;histCoord[0]<img.shape(0);++histCoord[0])
        for(histCoord[1]=0;histCoord[1]<img.shape(1);++histCoord[1]){

            for(int d=0;d<2;++d){
                start[d]   = std::max(int(0),            int(histCoord[d]) - int(r));
                end[d]     = std::min(int(img.shape(d)), int(histCoord[d] + (r+1) ));
            }

            for(c[0]=start[0];c[0]<end[0];++c[0])
            for(c[1]=start[1];c[1]<end[1];++c[1]){

                // iterate over all channels
                for(histCoord[2]=0;histCoord[2]<nChannels;++histCoord[2] ){

                    const LabelType label = img(c[0],c[1],histCoord[2]);

                    histCoord[3] = label;

                    /*
                    std::cout<<"\n\n” channel "<<histCoord[2]<<"\n";
                    std::cout<<"value "<< value<<"\n";
                    std::cout<<"mi " << min(histCoord[2])<<"\n";
                    std::cout<<"ma " << max(histCoord[2])<<"\n";
                    std::cout<<"fa " << fac[histCoord[2]]<<"\n";
                    */

                    PHIST_ASSERT_OP(histCoord[3],<,nLabels);
                    // increment hist
                    res(histCoord[0],histCoord[1],histCoord[2],histCoord[3])+=1.0;
                }
            }
        }

        // normalize

        for(histCoord[0]=0;histCoord[0]<img.shape(0);++histCoord[0])
        for(histCoord[1]=0;histCoord[1]<img.shape(1);++histCoord[1])
        for(histCoord[2]=0;histCoord[2]<img.shape(2);++histCoord[2]){

            float sum=0.0;
            for(histCoord[3]=0;histCoord[3]<nLabels;++histCoord[3]){
                sum+=res(histCoord[0],histCoord[1],histCoord[2],histCoord[3]);
            }
            for(histCoord[3]=0;histCoord[3]<nLabels;++histCoord[3]){
                res(histCoord[0],histCoord[1],histCoord[2],histCoord[3])/=sum;
            }
        }
        return res;
    }

    vigra::NumpyAnyArray labelSimHistogram(
        vigra::NumpyArray<3, vigra::Multiband<LabelType>  >         img,
        vigra::NumpyArray<3, float  >                               labelSim,
        const LabelType                                             nLabels,
        const size_t                                                r,
        //output
        vigra::NumpyArray<4, float >    res = vigra::NumpyArray<4, float >()
    ){
        const size_t nChannels=img.shape(2);
        // allocate output
        typedef typename vigra::NumpyArray<4, float >::difference_type Shape4;
        Shape4 shape(img.shape(0),img.shape(1),nChannels,nLabels);
        res.reshapeIfEmpty(shape);
        std::fill(res.begin(),res.end(),0.0);

        PHIST_ASSERT_OP(labelSim.shape(0),==,labelSim.shape(1));
        PHIST_ASSERT_OP(labelSim.shape(0),==,nLabels);
        PHIST_ASSERT_OP(labelSim.shape(2),==,img.shape(2));
        // coordinate in the res array (pixel wise histogram)
        // (x,y,c,bin)
        Shape4 histCoord;
        const vigra::TinyVector<float, 2>  radius1(r+1,r+1);

        vigra::TinyVector<int,2>  start,end,c;

        for(histCoord[0]=0;histCoord[0]<img.shape(0);++histCoord[0])
        for(histCoord[1]=0;histCoord[1]<img.shape(1);++histCoord[1]){

            for(int d=0;d<2;++d){
                start[d]   = std::max(int(0),            int(histCoord[d]) - int(r));
                end[d]     = std::min(int(img.shape(d)), int(histCoord[d] + (r+1) ));
            }

            for(c[0]=start[0];c[0]<end[0];++c[0])
            for(c[1]=start[1];c[1]<end[1];++c[1]){

                // iterate over all channels
                for(histCoord[2]=0;histCoord[2]<nChannels;++histCoord[2] ){

                    const LabelType label = img(c[0],c[1],histCoord[2]);

                    histCoord[3] = label;

                    /*
                    std::cout<<"\n\n” channel "<<histCoord[2]<<"\n";
                    std::cout<<"value "<< value<<"\n";
                    std::cout<<"mi " << min(histCoord[2])<<"\n";
                    std::cout<<"ma " << max(histCoord[2])<<"\n";
                    std::cout<<"fa " << fac[histCoord[2]]<<"\n";
                    */

                    PHIST_ASSERT_OP(histCoord[3],<,nLabels);
                    // increment hist

                    for(size_t ll = 0 ;ll<nLabels;++ll){
                        const float sim = labelSim(label,ll,histCoord[2]);
                        res(histCoord[0],histCoord[1],histCoord[2],ll)+=sim;
                    }

                }
            }
        }

        // normalize

        for(histCoord[0]=0;histCoord[0]<img.shape(0);++histCoord[0])
        for(histCoord[1]=0;histCoord[1]<img.shape(1);++histCoord[1])
        for(histCoord[2]=0;histCoord[2]<img.shape(2);++histCoord[2]){

            float sum=0.0;
            for(histCoord[3]=0;histCoord[3]<nLabels;++histCoord[3]){
                sum+=res(histCoord[0],histCoord[1],histCoord[2],histCoord[3]);
            }
            for(histCoord[3]=0;histCoord[3]<nLabels;++histCoord[3]){
                res(histCoord[0],histCoord[1],histCoord[2],histCoord[3])/=sum;
            }
        }
        return res;
    }

    void moveMe(
        vigra::NumpyArray<2, float  >           globalFeatures,
        vigra::NumpyArray<1, vigra::Int64  >    batchIndex,
        vigra::NumpyArray<1, vigra::Int64  >    minCenterIndex,
        vigra::NumpyArray<1, float  >           centerCount,
        vigra::NumpyArray<2, float  >           centers
    ){

        const size_t batchSize = batchIndex.shape(0);
        const size_t nFeatures = globalFeatures.shape(1);
        for(size_t bi=0;bi<batchSize;++bi){
            const size_t ci = minCenterIndex(bi);
            centerCount(ci)+=1.0;
            const float rate = 1.0/centerCount(ci);
            for(size_t f=0;f<nFeatures;++f){
                centers(ci,f)*=(1.0-rate);
                centers(ci,f)+=rate*globalFeatures(batchIndex(bi),f);
            }
        }
    }

    void histDist(
        vigra::NumpyArray<2, float  > samples,
        vigra::NumpyArray<2, float  > centers,
        vigra::NumpyArray<2, float  > distances,
        const std::string distType
    ){

        // renormalize centers

        const size_t nSamples  = samples.shape(0);
        const size_t nFeatures = samples.shape(1);
        const size_t nCenters  = centers.shape(0);

        if(distType==std::string("bhattacharyya")){

            // bhattacharyya dist may shift centers
            for(size_t c=0;c<nCenters;++c){

                float minV=std::numeric_limits<float>::infinity();
                for(size_t f=0;f<nFeatures;++f){
                    minV=std::min(minV,centers(c,f));
                }
                if (minV<0.000001f){
                    for(size_t f=0;f<nFeatures;++f){
                        centers(c,f)-=minV;
                    }
                }
                float sum=0;
                for(size_t f=0;f<nFeatures;++f){
                    sum+=centers(c,f);
                }
                if(sum>0.999999){
                    for(size_t f=0;f<nFeatures;++f){
                        centers(c,f)/=sum;
                    }
                }

            }

            for(size_t s=0;s<nSamples;++s)
            for(size_t c=0;c<nCenters;++c){
                // compute distace

                float sum=0.0;
                for(size_t f=0;f<nFeatures;++f){

                    const float sv=samples(s,f);
                    const float cv=samples(c,f);
                    PHIST_ASSERT_OP(sv , > , -0.0000001);
                    PHIST_ASSERT_OP(cv , > , -0.0000001);
                    PHIST_ASSERT_OP(sv , < , 1.0000001);
                    PHIST_ASSERT_OP(cv , < , 1.0000001);
                    sum+=std::sqrt(samples(s,f)*centers(c,f));
                }
                PHIST_ASSERT_OP(sum , <= , 1.0001);
                sum=std::min(sum,0.9999999f);
                distances(s,c)=(1.0f - sum);
                //std::cout<<"distances "<<distances(s,c)<<"\n";
            }
        }
        else if(distType==std::string("chi2")){
            for(size_t s=0;s<nSamples;++s)
            for(size_t c=0;c<nCenters;++c){
                // compute distace

                float sum=0.0;
                for(size_t f=0;f<nFeatures;++f){
                    const float sv=samples(s,f);
                    const float cv=samples(c,f);

                    sum+=std::pow(sv-cv,2)/(sv+cv);
                }
                distances(s,c)=0.5*sum;
                //std::cout<<"distances "<<distances(s,c)<<"\n";
            }
        }
        else{
            PHIST_ASSERT_OP(0,==,1);
        }
    }

    void export_label_histogram(){

        python::def("_label_histogram_",vigra::registerConverters(&labelHistogram),
            (
                python::arg("img"),
                python::arg("nLabels"),
                python::arg("r"),
                python::arg("out")=python::object()
            )
        );

        python::def("_label_sim_histogram_",vigra::registerConverters(&labelSimHistogram),
            (
                python::arg("img"),
                python::arg("labelSim"),
                python::arg("nLabels"),
                python::arg("r"),
                python::arg("out")=python::object()
            )
        );

        python::def("moveMe",vigra::registerConverters(&moveMe),
            (
                python::arg("globalFeatures"),
                python::arg("batchIndex"),
                python::arg("minCenterIndex"),
                python::arg("centerCount"),
                python::arg("centers")
            )
        );

        python::def("histDist",vigra::registerConverters(&histDist),
            (
                python::arg("samples"),
                python::arg("centers"),
                python::arg("distances"),
                python::arg("distType")=std::string("bhattacharyya")
            )
        );

    }

}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                           */
/*  This file is part of the library KASKADE 7                               */
/*    see http://www.zib.de/projects/kaskade7-finite-element-toolbox         */
/*                                                                           */
/*  Copyright (C) 2002-2009 Zuse Institute Berlin                            */
/*                                                                           */
/*  KASKADE 7 is distributed under the terms of the ZIB Academic License.    */
/*    see $KASKADE/academic.txt                                              */
/*                                                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

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

#include <boost/timer.hpp>

#include "dune/common/stdstreams.hh"
#include "dune/grid/sgrid.hh"
#include "dune/grid/uggrid.hh"
#include "dune/grid/common/gridinfo.hh"

#include "fem/assemble.hh"
#include "fem/embedded_errorest.hh"
#include "fem/istlinterface.hh"
#include "fem/functional_aux.hh"
#include "fem/hierarchicspace.hh"
#include "fem/lagrangespace.hh"
#include "fem/iterate_grid.hh"
#include "fem/hierarchicErrorEstimator.hh"
#include "linalg/trivialpreconditioner.hh"
#include "linalg/direct.hh"
#include "linalg/triplet.hh"
#include "linalg/mumps_solve.hh"

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

#include "mg/pcg.hh"
#include "mg/apcg.hh"

#include "utilities/kaskopt.hh"

#include "poisson.hh"
#include "amiramesh.hh"

int problemNo = 1;

bool compareAbs(const double x1, const double  x2)
  {
    return fabs(x1)>fabs(x2);
  }

int main(int argc, char *argv[])
  {
    int verbosity = 1;
    bool dump = false;
    boost::property_tree::ptree *pt = GetKaskadeOptions(argc, argv, verbosity, dump);

    std::cout << "Start cascadic multigrid test program" << std::endl;

    int  direct, refs, order, onlyLowerTriangle = false, maxAdaptSteps;
    SolverType directType;
    IterateType iterateType = IterateType::PCG;
    MatrixProperties property;
    std::string empty, problem, functional, geoFile;

	problem = GetParameter(pt, "problem", empty);
	refs = GetParameter(pt, problem+".refs", 0),
    order =  GetParameter(pt, problem+".order", 1),
    maxAdaptSteps = GetParameter(pt, problem+".maxAdaptSteps", 10);
    geoFile = GetParameter(pt, problem+".geoFile", empty);
    functional = GetParameter(pt, problem+".functional", empty);
    problemNo = GetParameter(pt, "names.functional."+functional, 1);
    std::cout << "selected problem " << problem << ", functional=" <<
                 functional << "(" << problemNo <<  "), geoFile=" <<
                 geoFile << std::endl;

    std::string s("names.type.");
    s += GetParameter(pt, "solver.type", empty);
    direct = GetParameter(pt, s, 0);

    s = "names.direct." + GetParameter(pt, "solver.direct", empty);
    directType = static_cast<SolverType>(GetParameter(pt, s, 2));

	s = "names.iterate." + GetParameter(pt, "solver.iterate", empty);
	iterateType = static_cast<IterateType>(GetParameter(pt, s, 0));

    property = MatrixProperties::POSITIVEDEFINITE;

    if (((property == MatrixProperties::SYMMETRIC)||(property == MatrixProperties::POSITIVEDEFINITE))&&
        ((directType == DirectType::MUMPS)||(directType == DirectType::PARDISO)))
      {
        onlyLowerTriangle = true;
      }

    int const dim = 3;
    int k;
    typedef Dune::UGGrid<dim> Grid;
    Grid *grid = new Grid(20000);
    Dune::GridFactory<Grid> factory(grid);

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

//    std::auto_ptr<Grid> grid( factory.createGrid() );
    grid = factory.createGrid();
    // the coarse grid will be refined refs times
    for (k=0; k<refs; k++)
      {
        grid->globalRefine(1);
	  }
    // some information on the refined mesh
    std::cout << "Grid: " << grid->size(0) << " tetrahedra, " << std::endl;
    std::cout << "      " << grid->size(1) << " triangles, " << std::endl;
    std::cout << "      " << grid->size(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(grid);

    typedef Grid::LeafGridView LeafView;
    // construction of finite element space for the scalar solution T
    typedef FEFunctionSpace<ContinuousLagrangeMapper<double,LeafView> > H1Space;

    H1Space temperatureSpace(gridManager,gridManager.grid().leafView(),
                             order);
    typedef boost::fusion::vector<H1Space const*> Spaces;
    Spaces spaces(&temperatureSpace);
    // VariableDescription<int spaceId, int components, int Id>
    // spaceId: number of associated FEFunctionSpace
    // components: number of components in this variable
    // Id: number of this variable
    typedef boost::fusion::vector<VariableDescription<0,1,0> >
      VariableDescriptions;
    std::string varNames[1] = { "T" };
    typedef VariableSetDescription<Spaces,VariableDescriptions> VariableSet;
    VariableSet variableSet(spaces,varNames);

    typedef PoissonFunctional<double,VariableSet> Functional;
    Functional F;
    typedef VariationalFunctionalAssembler<LinearizationAt<Functional> > GOP;
    typedef GOP::TestVariableRepresentation<>::type Rhs;
    GOP gop(gridManager.signals,spaces);

    typedef FEFunctionSpace<ContinuousHierarchicExtensionMapper<double,LeafView> > H1ExSpace;
    H1ExSpace spaceEx(gridManager,gridManager.grid().leafView(), order+1);

    typedef boost::fusion::vector<H1Space const*,H1ExSpace const*> H1ExSpaces;
    H1ExSpaces exSpaces(&temperatureSpace,&spaceEx);

    typedef boost::fusion::vector<VariableDescription<1,1,0> > ExVariableDescriptions;
    typedef VariableSetDescription<H1ExSpaces,ExVariableDescriptions> ExVariableSet;
    std::string exVarNames[2] = { "l", "e"};
    ExVariableSet exVariableSet(exSpaces, exVarNames);

    typedef HierarchicErrorEstimator<LinearizationAt<Functional>,ExVariableSet> ErrorEstimator;
    typedef VariationalFunctionalAssembler<ErrorEstimator> EstGOP;

    EstGOP estGop(gridManager.signals,exSpaces);
	typedef EstGOP::AnsatzVariableRepresentation<> Ansatz;
	typedef EstGOP::TestVariableRepresentation<> Test;

	typedef VariableSet::Grid::Traits::LeafIndexSet IS ;
	IS const& is = gridManager.grid().leafIndexSet();

    VariableSet::VariableSet x(variableSet), dx(variableSet), tmp(variableSet);

    double rTolX = GetParameter(pt, problem+".rTolX", 1.0e-4),
           aTolX = GetParameter(pt, problem+".aTolX", 1.0-2),
           minRefine = GetParameter(pt, problem+".minRefine", 0.0);
    std::vector<std::pair<double,double> > tolX(variableSet.noOfVariables);
    std::vector<std::pair<double,double> > tolXC(variableSet.noOfVariables);
    for (int i=0; i<tolX.size(); ++i)
      {
        tolX[i] = std::make_pair(aTolX,rTolX);
        tolXC[i] = std::make_pair(aTolX/100,rTolX/100);
     }
	std::cout << "rTolX = " << rTolX << ", aTolX = " << aTolX <<
	             ", minRefine = " << minRefine << std::endl ;

	IoOptions options;
	options.outputType = IoOptions::ascii;

    int refSteps = 0;
    bool accurate = false;
	int iteSteps = GetParameter(pt, "solver.iteMax", 1000);
	int verbose = GetParameter(pt, "solver.verbose", 1);
	double iteEps = GetParameter(pt, "solver.iteEps", 1.0e-6);
	typedef GOP::TestVariableRepresentation<>::type LinearSpace;
	Dune::InverseOperatorResult res;
	double errNorm;

	size_t size = variableSet.degreesOfFreedom(0,1);
	double gamma = 1.0, d = dim;
	double beta = 1.0/sqrt(d*gamma);
	double alpha = (d*gamma-1.0)/(d*(1.0+gamma));
	double qk = 1.0, dNk = size, zk = 0.0;
	double requested = sqrt(1-beta*beta)*tolX[0].first;
	double safety = 1.0;
	double yk = pow(dNk,alpha);
printf("gamma=%e, d=%e, beta=%e, alpha=%e, requested=%e, safety=%e\n",
       gamma,d,beta,alpha,requested,safety);
//printf("    %10ld %e   %e   %e   %e\n", size, qk, dNk, yk, zk);
    do
      {
		typedef GOP::AnsatzVariableRepresentation<>::type Sol;
		Sol solution(GOP::AnsatzVariableRepresentation<>::init(gop));
		Sol hilfe(GOP::AnsatzVariableRepresentation<>::init(gop));
		solution = 0;
		hilfe = 0;
		gop.assemble(linearization(F,x));
		Rhs rhs(GOP::TestVariableRepresentation<>::rhs(gop));
		AssembledGalerkinOperator<GOP,0,1,0,1> A(gop, onlyLowerTriangle);
		AssembledGalerkinOperator<GOP,0,1,0,1>::matrix_type tri(A.getmat());

        if (direct)
          {
            directInverseOperator(A,directType,property).applyscaleadd(-1.0,rhs,solution);
		  }
		else
		  {
			switch (iterateType)
			  {
			   case IterateType::CG:
			     {
				   JacobiPreconditioner<GOP,0,1,0> jacobi(gop,1.0);
				   Dune::CGSolver<LinearSpace> cg(A,jacobi,iteEps,iteSteps,verbose);
				   cg.apply(hilfe,rhs,res);
			     }
			     break;
			   case IterateType::PCG:
			     {
			       JacobiPreconditioner<GOP,0,1,0> jacobi(gop,1.0);
			       Kaskade7::NMIIIPCGSolver<LinearSpace> pcg(A,jacobi,iteEps,iteSteps,verbose);
			       pcg.apply(hilfe,rhs,res);
			     }
			     break;
			   case IterateType::APCG:
			     {
    			   int addedIterations = GetParameter(pt, "solver.APCG.addedIterations", 10);
			       JacobiPreconditioner<GOP,0,1,0> jacobiPCG(gop,1.0);
			       Kaskade7::NMIIIAPCGSolver<LinearSpace> apcg(A,jacobiPCG,iteEps,iteSteps,verbose,addedIterations);
			       apcg.apply(hilfe,rhs,res);
			     }
			     break;
			  default:
			    std::cout << "Solver not available" << std::endl;
			    throw -111;
			  }
			solution.axpy(-1,hilfe);
		  }
        dx.data = solution.data;
printf("Solved!\n");

		std::ostringstream fn;
		fn << "graph3d/cmg-grid";
		fn.width(3);
		fn.fill('0');
		fn.setf(std::ios_base::right,std::ios_base::adjustfield);
		fn << refSteps;
		fn.flush();
	    LeafView leafView = gridManager.grid().leafView();
	    writeVTKFile(leafView,variableSet,x,fn.str(),options,order);

        // Do hierarchical error estimation. Remember to provide the very same underlying problem to the
        // error estimator functional as has been used to compute dx (do not modify x!).
        if (!tolX.empty())
          {
	        tmp *= 0 ;
	        estGop.assemble(ErrorEstimator(LinearizationAt<Functional>(F,x),dx));
		    int const estNvars = ErrorEstimator::AnsatzVars::noOfVariables;
		    int const estNeq = ErrorEstimator::TestVars::noOfVariables;
		    size_t  estNnz = estGop.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);
		    estGop.toSequence(0,estNeq,estRhs.begin());

		  // iterative solution of error estimator

		    AssembledGalerkinOperator<EstGOP> E(estGop);
		    Dune::InverseOperatorResult estRes;
		    Test::type estRhside(Test::rhs(estGop) ) ;
		    Ansatz::type estSol(Ansatz::init(estGop) ) ;
		    estSol = 1.0 ;
		    JacobiPreconditioner<EstGOP> jprec(estGop, 1.0);
		    jprec.apply(estSol,estRhside); //single Jacobi iteration
		    estSol.write(estSolVec.begin());

	  // Transfer error indicators to cells.
		    std::vector<double> errorDistribution(is.size(0),0.0);
		    typedef VariableSet::GridView::Codim<0>::Iterator CellIterator ;
		    double maxErr = 0.0;
		    for (CellIterator ci=variableSet.gridView.begin<0>(); ci!=variableSet.gridView.end<0>(); ++ci)
		      {
				typedef H1ExSpace::Mapper::GlobalIndexRange GIR;
				double err = 0;
				GIR gix = spaceEx.mapper().globalIndices(*ci);
				for (GIR::iterator j=gix.begin(); j!=gix.end(); ++j)
				  err += fabs(boost::fusion::at_c<0>(estSol.data)[*j]);
				errorDistribution[is.index(*ci)] = err;
				if (fabs(err)>maxErr) maxErr = fabs(err);
		      }

		    double errLevel = 0.5*maxErr;

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

		    errNorm = 0 ;
		    for (k=0; k < estRhs.size() ; k++ )
		      errNorm +=  fabs(estRhs[k]*estSolVec[k]) ;

	        if (errNorm<requested)
	          accurate = true ;
	        else
	          {

	  // Refine mesh.

				int noToRefine = 0;
				double alphaSave = 1.0 ;
				std::vector<bool> toRefine( is.size(0), false ) ; //for adaptivity in compression
				std::vector< std::vector<bool> > refinements;
				for (CellIterator ci=variableSet.gridView.begin<0>(); ci!=variableSet.gridView.end<0>(); ++ci)
				  if (fabs(errorDistribution[is.index(*ci)]) >= alphaSave*errLevel)
					{
					  noToRefine++;
					  toRefine[is.index(*ci)] = true ;
					  gridManager.mark(1,*ci);
					}

				refinements.push_back(toRefine);
				accurate = !gridManager.adaptAtOnce();
	          }
        }
        // apply the Newton correction here
       x += dx;

//           { // Compute spatial error estimate
//     		   std::vector<double> norm2, error2;
//     		   norm2.resize(1);
//     		   error2.resize(1);
//             tmp = x;
//             projectHierarchically(variableSet,tmp);
//             tmp -= x;
//
//           // perform mesh adaptation
//             accurate = embeddedErrorEstimator(variableSet, tmp, x, scal, tolX, gridManager, norm2, error2);
//             printf(" %e %e\n", norm2[0], error2[0]);
//             if (!accurate)
//               {
//                 nnz = gop.nnz(0,1,0,1,onlyLowerTriangle);
//                 size = variableSet.degreesOfFreedom(0,1);
// 				ridx.resize(nnz);
// 				cidx.resize(nnz);
// 				data.resize(nnz);
// 				rhs.resize(size);
// 				sol.resize(size);
//               }
//           }
        refSteps++;

        printf("%3d %10ld %5d   %e   %e\n", refSteps, size, res.iterations, errNorm,
               iteEps);

        size = variableSet.degreesOfFreedom(0,1);
	    qk = size/dNk;
		dNk = size;
	    yk += pow(dNk,alpha);
	    zk = pow(dNk,alpha)*(pow(errNorm/requested,d*alpha)-pow(qk,alpha))/(pow(qk,alpha)-1.0);
        iteEps = safety*beta*errNorm*yk/(yk+zk);

//        printf("  %10ld %e   %e   %e\n", size, yk, zk, yk+zk);

        if (refSteps>maxAdaptSteps) break;
//
// 		std::ostringstream fn;
// 		fn << "graph3d/cmg-grid";
// 		fn.width(3);
// 		fn.fill('0');
// 		fn.setf(std::ios_base::right,std::ios_base::adjustfield);
// 		fn << refSteps;
// 		fn.flush();
// 	    LeafView leafView = gridManager.grid().leafView();
// 	    writeVTKFile(leafView,variableSet,x,fn.str(),options,order);

      } while (!accurate);

    std::cout << "End cmgtest" << std::endl;
  }


/*
 * This file is a part of
 *
 * ============================================
 * ###   Pteros molecular modeling library  ###
 * ============================================
 *
 * (C) 2009-2018, Semen Yesylevskyy
 *
 * All works, which use Pteros, should cite the following papers:
 *
 *  1.  Semen O. Yesylevskyy, "Pteros 2.0: Evolution of the fast parallel
 *      molecular analysis library for C++ and python",
 *      Journal of Computational Chemistry, 2015, 36(19), 1480–1488.
 *      doi: 10.1002/jcc.23943.
 *
 *  2.  Semen O. Yesylevskyy, "Pteros: Fast and easy to use open-source C++
 *      library for molecular analysis",
 *      Journal of Computational Chemistry, 2012, 33(19), 1632–1636.
 *      doi: 10.1002/jcc.22989.
 *
 * This is free software distributed under Artistic License:
 * http://www.opensource.org/licenses/artistic-license-2.0.php
 *
*/

#include "pteros/core/system.h"
#include "pteros/core/pteros_error.h"
#include "pteros/core/distance_search.h"
#include "pteros/core/force_field.h"
#include <cmath>
#include <functional>
#include "pteros/core/logging.h"
#include <boost/algorithm/string.hpp>

using namespace std;
using namespace pteros;
using namespace Eigen;

Vector3f get_shift_coefs(int alpha, float r1, float rc){
    Vector3f res;
    res(0) = -(( (alpha+4)*rc - (alpha+1)*r1 )/( pow(rc,alpha+2)*pow(rc-r1,2) ));
    res(1) = ( (alpha+3)*rc - (alpha+1)*r1 )/( pow(rc,alpha+2)*pow(rc-r1,3) );
    res(2) = 1.0/pow(rc,alpha) - (res(0)/3.0)*pow(rc-r1,3) - (res(1)/4.0)*pow(rc-r1,4);
    return res;
}

// Plain LJ kernel
float LJ_en_kernel(float C6, float C12, float r, const Force_field& ff){
    float r_inv = 1.0/r;
    float tmp = r_inv*r_inv; // (1/r)^2
    tmp = tmp*tmp*tmp; // (1/r)^6
    return C12*tmp*tmp-C6*tmp;
}

// Cutoff LJ kernel
float LJ_en_kernel_cutoff(float C6, float C12, float r, const Force_field& ff){
    if(r>ff.rvdw) return 0.0;
    float r_inv = 1.0/r;
    float tmp = r_inv*r_inv; // (1/r)^2
    tmp = tmp*tmp*tmp; // (1/r)^6
    return C12*tmp*tmp-C6*tmp;
}

// Shifted LJ kernel
float LJ_en_kernel_shifted(float C6, float C12, float r, const Force_field& ff){
    if(r>ff.rvdw) return 0.0;

    float val12 = pow(r,-12)
            -(ff.shift_12(0)/3.0)*pow(r-ff.rvdw_switch,3)
            -(ff.shift_12(1)/4.0)*pow(r-ff.rvdw_switch,4)
            -ff.shift_12(2);
    float val6 = pow(r,-6)
            -(ff.shift_6(0)/3.0)*pow(r-ff.rvdw_switch,3)
            -(ff.shift_6(1)/4.0)*pow(r-ff.rvdw_switch,4)
            -ff.shift_6(2);

    return C12*val12 - C6*val6;
}

#define ONE_4PI_EPS0      138.935456

// Plane Coulomb kernel
float Coulomb_en_kernel(float q1, float q2, float r, const Force_field& ff){
    return ff.coulomb_prefactor*q1*q2/r;
}

// Cutoff Coulomb kernel
float Coulomb_en_kernel_cutoff(float q1, float q2, float r, const Force_field& ff){
    if(r>ff.rcoulomb) return 0.0;
    return ff.coulomb_prefactor*q1*q2/r;
}

// Reaction field Coulomb kernel
float Coulomb_en_kernel_rf(float q1, float q2, float r, const Force_field& ff){
    return ff.coulomb_prefactor*q1*q2*(1.0/r + ff.k_rf*r*r - ff.c_rf);
}

// Shifted Coulomb kernel
float Coulomb_en_kernel_shifted(float q1, float q2, float r, const Force_field& ff){
    return ff.coulomb_prefactor*q1*q2*( 1.0/r
                                     -(ff.shift_1(0)/3.0)*pow(r-ff.rcoulomb_switch,3)
                                     -(ff.shift_1(1)/4.0)*pow(r-ff.rcoulomb_switch,4)
                                     -ff.shift_1(2)
                                     );
}

#define LOWER(s) boost::algorithm::to_lower_copy(string(s))

void Force_field::setup_kernels(){
    using namespace placeholders;

    LOG()->debug("Coulomb type: {}",coulomb_type);
    LOG()->debug("Coulomb modifier: {}",coulomb_modifier);
    LOG()->debug("VdW type: {}",vdw_type);
    LOG()->debug("VdW modifier: {}",vdw_modifier);

    // Set Coulomb prefactor
    coulomb_prefactor = ONE_4PI_EPS0 / epsilon_r;

    // Set Coulomb kernel
    if(LOWER(coulomb_type)=="reaction-field"){
        // In case of reaction field precompute constanst
        if(epsilon_rf){
            k_rf = (1.0/(rcoulomb*rcoulomb*rcoulomb))
                    * (epsilon_rf-epsilon_r) / (2.0*epsilon_rf+epsilon_r);
        } else {
            // for epsilon_rf = 0 (which means inf)
            k_rf = 0.5/(rcoulomb*rcoulomb*rcoulomb);
        }
        c_rf = (1.0/rcoulomb) + k_rf*rcoulomb*rcoulomb;

        // Set coulomb kernel pointer
        coulomb_kernel_ptr = &Coulomb_en_kernel_rf;
        LOG()->debug("\tCoulomb kernel: reaction_field");

    } else if( ( LOWER(coulomb_type)=="cut-off"
                 && LOWER(coulomb_modifier)== "potential-shift"
               )
              || LOWER(coulomb_type)=="shift"
              || LOWER(coulomb_type)=="pme") {
        // Compute shift constants for power 1
        shift_1 = get_shift_coefs(1,rcoulomb_switch,rcoulomb);

        coulomb_kernel_ptr = &Coulomb_en_kernel_shifted;
        LOG()->debug("\tCoulomb kernel: shifted");

    } else if(LOWER(coulomb_type)==LOWER("cut-off")) {
        // In other cases set plain Coulomb interaction
        coulomb_kernel_ptr = &Coulomb_en_kernel_cutoff;
        LOG()->debug("\tCoulomb kernel: cutoff");
    } else {
        coulomb_kernel_ptr = &Coulomb_en_kernel;
        LOG()->debug("\tCoulomb kernel: plain");
    }

    // Set LJ kernel
    if(LOWER(vdw_type)== "shift"){
        // Compute shift constants for powers 6 and 12
        shift_6 = get_shift_coefs(6,rvdw_switch,rvdw);
        shift_12 = get_shift_coefs(12,rvdw_switch,rvdw);

        LJ_kernel_ptr = &LJ_en_kernel_shifted;
        LOG()->debug("\tLJ kernel: shifted");

    } else if(LOWER(vdw_type)== "cut-off") {
        LJ_kernel_ptr = &LJ_en_kernel_cutoff;
        LOG()->debug("\tLJ kernel: cutoff");

    } else {
        LJ_kernel_ptr = &LJ_en_kernel;
        LOG()->debug("\tLJ kernel: plain");
    }
}

Vector2f Force_field::pair_energy(int at1, int at2, float r, float q1, float q2, int type1, int type2)
{
    float c6,c12;
    // indexes have to be in increasing order
    if(at1>at2){
        std::swap(at1,at2);
        std::swap(q1,q2);
        std::swap(type1,type2);
    }
    // Check is the pair is excluded
    if(exclusions[at1].count(at2)) return {0,0};
    // Check if this pair is 1-4 pair
    auto it = LJ14_pairs.find(at1*natoms+at2);
    if(it==std::end(LJ14_pairs)){
        // normal pair
        c6 = LJ_C6(type1,type2);
        c12 = LJ_C12(type1,type2);
        return {coulomb_kernel_ptr(q1,q2,r,*this), LJ_kernel_ptr(c6,c12,r,*this)};
    } else {
        // 1-4 pair
        c6 = LJ14_interactions[it->second](0);
        c12 = LJ14_interactions[it->second](1);
        return {coulomb_kernel_ptr(q1,q2,r,*this)*fudgeQQ, LJ_kernel_ptr(c6,c12,r,*this)};
    }
}

float Force_field::get_cutoff(){
    return std::min(rcoulomb,rvdw);
}

Force_field::Force_field():  ready(false) {}

Force_field::Force_field(const Force_field &other){
    exclusions = other.exclusions;
    LJ_C6 = other.LJ_C6;
    LJ_C12 = other.LJ_C12;
    LJ14_interactions = other.LJ14_interactions;
    LJ14_pairs = other.LJ14_pairs;
    fudgeQQ = other.fudgeQQ;
    rcoulomb = other.rcoulomb;
    epsilon_r = other.epsilon_r;
    epsilon_rf = other.epsilon_rf;
    rcoulomb_switch = other.rcoulomb_switch;
    rvdw_switch = other.rvdw_switch;
    rvdw = other.rvdw;
    coulomb_type = other.coulomb_type;
    coulomb_modifier = other.coulomb_modifier;
    vdw_type = other.vdw_type;
    vdw_modifier = other.vdw_modifier;

    ready = other.ready;

    if(ready) setup_kernels();
}

Force_field &Force_field::operator=(Force_field other){
    exclusions = other.exclusions;
    LJ_C6 = other.LJ_C6;
    LJ_C12 = other.LJ_C12;
    LJ14_interactions = other.LJ14_interactions;
    LJ14_pairs = other.LJ14_pairs;
    fudgeQQ = other.fudgeQQ;
    rcoulomb = other.rcoulomb;
    epsilon_r = other.epsilon_r;
    epsilon_rf = other.epsilon_rf;
    rcoulomb_switch = other.rcoulomb_switch;
    rvdw_switch = other.rvdw_switch;
    rvdw = other.rvdw;
    coulomb_type = other.coulomb_type;
    coulomb_modifier = other.coulomb_modifier;
    vdw_type = other.vdw_type;
    vdw_modifier = other.vdw_modifier;

    ready = other.ready;

    if(ready) setup_kernels();

    return *this;
}

void Force_field::clear(){
    exclusions.clear();
    LJ_C6.fill(0.0);
    LJ_C12.fill(0.0);
    LJ14_interactions.clear();
    LJ14_pairs.clear();
    fudgeQQ = 0.0;

    ready = false;
}


/*! @file pitts_tensortrain_from_dense.hpp
* @brief conversion of a dense tensor to the tensor-train format (based on a hopefully faster TSQR algorithm)
* @author Melven Roehrig-Zoellner <Melven.Roehrig-Zoellner@DLR.de>
* @date 2020-06-19
* @copyright Deutsches Zentrum fuer Luft- und Raumfahrt e. V. (DLR), German Aerospace Center
*
**/

// include guard
#ifndef PITTS_TENSORTRAIN_FROM_DENSE_HPP
#define PITTS_TENSORTRAIN_FROM_DENSE_HPP

// includes
#include "pitts_parallel.hpp"
#include "pitts_tensortrain.hpp"
#include "pitts_multivector.hpp"
#include "pitts_multivector_tsqr.hpp"
#include "pitts_multivector_transform.hpp"
#include "pitts_tensor2.hpp"
#include "pitts_tensor2_eigen_adaptor.hpp"
#include "pitts_timer.hpp"
#include <limits>
#include <numeric>
#pragma GCC push_options
#pragma GCC optimize("no-unsafe-math-optimizations")
#include <Eigen/Dense>
#pragma GCC pop_options

//! namespace for the library PITTS (parallel iterative tensor train solvers)
namespace PITTS
{
  //! calculate tensor-train decomposition of a tensor stored in fully dense format
  //!
  //! Passing a large enough buffer in work helps to avoid costly reallocations + later page-faults for large data.
  //!
  //! @warning To reduce memory overhead, this function will overwrite the input arguments with temporary data.
  //!          Please pass a copy of the data if you still need it!
  //!
  //! @tparam T         underlying data type (double, complex, ...)
  //!
  //! @param X              input tensor, overwritten and modified output, dimension must be (size/lastDim, lastDim) where lastDim = dimensions.back()
  //! @param dimensions     tensor dimensions, input is interpreted in Fortran storage order (first index changes the fastest)
  //! @param work           buffer for temporary data, will be resized and modified
  //! @param rankTolerance  approximation accuracy, used to reduce the TTranks of the resulting tensor train
  //! @param maxRank        maximal TTrank (bond dimension), unbounded by default
  //! @param mpiGlobal      (experimental) perform a MPI parallel decomposition, this assumes that the data is distributed on the MPI processes and dimensions specify the local dimensions
  //! @return               resulting tensor train
  //!
  template<typename T>
  TensorTrain<T> fromDense(MultiVector<T>& X, MultiVector<T>& work, const std::vector<int>& dimensions, T rankTolerance = std::sqrt(std::numeric_limits<T>::epsilon()), int maxRank = -1, bool mpiGlobal = false)
  {
    // timer
    const auto timer = PITTS::timing::createScopedTimer<TensorTrain<T>>();

    // abort early for zero dimensions
    if( dimensions.size() == 0 )
    {
      if( X.rows()*X.cols() !=  0 )
        throw std::out_of_range("Mismatching dimensions in TensorTrain<T>::fromDense");
      return TensorTrain<T>{dimensions};
    }

    const auto totalSize = std::accumulate(begin(dimensions), end(dimensions), (std::ptrdiff_t)1, std::multiplies<std::ptrdiff_t>());
    const auto nDims = dimensions.size();
    if( X.rows() != totalSize/dimensions[nDims-1] || X.cols() != dimensions[nDims-1] )
      throw std::out_of_range("Mismatching dimensions in TensorTrain<T>::fromDense");

    bool root = true;
    if( mpiGlobal )
    {
      const auto& [iProc,nProcs] = internal::parallel::mpiProcInfo();
      root = iProc == 0;
    }

    TensorTrain<T> result(dimensions);

    // actually convert to tensor train format
    Tensor2<T> tmpR;
    using EigenMatrix = Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>;
    Eigen::BDCSVD<EigenMatrix> svd;
    for(int iDim = nDims-1; iDim > 0; iDim--)
    {
      if( root )
        std::cout << "iDim: " << iDim << ", matrix dimensions: " << X.rows() << " x " << X.cols() << "\n";
      // calculate QR decomposition
      block_TSQR(X, tmpR, 0, mpiGlobal);
//std::cout << "tmpR:\n" << ConstEigenMap(tmpR) << "\n";

      // calculate SVD of R
      svd.compute(ConstEigenMap(tmpR), Eigen::ComputeThinU | Eigen::ComputeThinV);
      //Eigen::JacobiSVD<EigenMatrix> svd(ConstEigenMap(tmpR), Eigen::ComputeThinU | Eigen::ComputeThinV);
      svd.setThreshold(rankTolerance);
      if( root )
        std::cout << "singular values: " << svd.singularValues().transpose() << "\n";

      // copy V to the TT sub-tensor
      svd.setThreshold(rankTolerance);
      int rank = svd.rank();
      if( maxRank > 0 )
        rank = std::min(maxRank, rank);
      auto& subT = result.editableSubTensors()[iDim];
      subT.resize(rank, dimensions[iDim], X.cols()/dimensions[iDim]);
      for(int i = 0; i < subT.r1(); i++)
        for(int j = 0; j < subT.n(); j++)
          for(int k = 0; k < subT.r2(); k++)
            subT(i,j,k) = svd.matrixV()(j+subT.n()*k, i);

      tmpR.resize(X.cols(), rank);
      EigenMap(tmpR) = svd.matrixV().leftCols(rank);

      const auto nextDim = dimensions[iDim-1];
      transform(X, tmpR, work, {X.rows()/nextDim, rank*nextDim});
      std::swap(X, work);
    }
    // last sub-tensor is now in X
    auto& lastSubT = result.editableSubTensors()[0];
    lastSubT.resize(1, dimensions[0], X.cols()/dimensions[0]);
    for(int i = 0; i < lastSubT.n(); i++)
      for(int j = 0; j < lastSubT.r2(); j++)
        lastSubT(0, i, j) = X(0, i+j*dimensions[0]);

    // make sure we swap X and work back: prevents problems where the reserved space in X is used again later AND the data does only fit into memory once ;)
    if( nDims % 2 == 0 )
      std::swap(X, work);

    return result;
  }

}

#endif // PITTS_TENSORTRAIN_FROM_DENSE_HPP