#ifndef STAN_MATH_PRIM_PROB_FRECHET_CDF_HPP
#define STAN_MATH_PRIM_PROB_FRECHET_CDF_HPP

#include <stan/math/prim/meta.hpp>
#include <stan/math/prim/err.hpp>
#include <stan/math/prim/fun/constants.hpp>
#include <stan/math/prim/fun/exp.hpp>
#include <stan/math/prim/fun/log.hpp>
#include <stan/math/prim/fun/log1m.hpp>
#include <stan/math/prim/fun/max_size.hpp>
#include <stan/math/prim/fun/multiply_log.hpp>
#include <stan/math/prim/fun/scalar_seq_view.hpp>
#include <stan/math/prim/fun/size.hpp>
#include <stan/math/prim/fun/size_zero.hpp>
#include <stan/math/prim/fun/to_ref.hpp>
#include <stan/math/prim/fun/value_of.hpp>
#include <boost/random/weibull_distribution.hpp>
#include <boost/random/variate_generator.hpp>
#include <stan/math/prim/functor/operands_and_partials.hpp>
#include <cmath>

namespace stan {
namespace math {

template <typename T_y, typename T_shape, typename T_scale,
          require_all_not_nonscalar_prim_or_rev_kernel_expression_t<
              T_y, T_shape, T_scale>* = nullptr>
return_type_t<T_y, T_shape, T_scale> frechet_cdf(const T_y& y,
                                                 const T_shape& alpha,
                                                 const T_scale& sigma) {
  using T_partials_return = partials_return_t<T_y, T_shape, T_scale>;
  using T_y_ref = ref_type_t<T_y>;
  using T_alpha_ref = ref_type_t<T_shape>;
  using T_sigma_ref = ref_type_t<T_scale>;
  using std::pow;
  static const char* function = "frechet_cdf";
  T_y_ref y_ref = y;
  T_alpha_ref alpha_ref = alpha;
  T_sigma_ref sigma_ref = sigma;

  check_positive(function, "Random variable", y_ref);
  check_positive_finite(function, "Shape parameter", alpha_ref);
  check_positive_finite(function, "Scale parameter", sigma_ref);

  if (size_zero(y_ref, alpha_ref, sigma_ref)) {
    return 1.0;
  }

  T_partials_return cdf(1.0);
  operands_and_partials<T_y_ref, T_alpha_ref, T_sigma_ref> ops_partials(
      y_ref, alpha_ref, sigma_ref);

  scalar_seq_view<T_y> y_vec(y_ref);
  scalar_seq_view<T_scale> sigma_vec(sigma_ref);
  scalar_seq_view<T_shape> alpha_vec(alpha_ref);
  size_t N = max_size(y_ref, sigma_ref, alpha_ref);

  for (size_t n = 0; n < N; n++) {
    const T_partials_return y_dbl = y_vec.val(n);
    const T_partials_return sigma_dbl = sigma_vec.val(n);
    const T_partials_return alpha_dbl = alpha_vec.val(n);
    const T_partials_return pow_n = pow(sigma_dbl / y_dbl, alpha_dbl);
    const T_partials_return cdf_n = exp(-pow_n);

    cdf *= cdf_n;

    if (!is_constant_all<T_y>::value) {
      ops_partials.edge1_.partials_[n] += pow_n * alpha_dbl / y_dbl;
    }
    if (!is_constant_all<T_shape>::value) {
      ops_partials.edge2_.partials_[n] += pow_n * log(y_dbl / sigma_dbl);
    }
    if (!is_constant_all<T_scale>::value) {
      ops_partials.edge3_.partials_[n] -= pow_n * alpha_dbl / sigma_dbl;
    }
  }

  if (!is_constant_all<T_y>::value) {
    for (size_t n = 0; n < stan::math::size(y); ++n) {
      ops_partials.edge1_.partials_[n] *= cdf;
    }
  }
  if (!is_constant_all<T_shape>::value) {
    for (size_t n = 0; n < stan::math::size(alpha); ++n) {
      ops_partials.edge2_.partials_[n] *= cdf;
    }
  }
  if (!is_constant_all<T_scale>::value) {
    for (size_t n = 0; n < stan::math::size(sigma); ++n) {
      ops_partials.edge3_.partials_[n] *= cdf;
    }
  }
  return ops_partials.build(cdf);
}

}  // namespace math
}  // namespace stan
#endif


//  Copyright John Maddock 2006.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_STATS_WEIBULL_HPP
#define BOOST_STATS_WEIBULL_HPP

// http://www.itl.nist.gov/div898/handbook/eda/section3/eda3668.htm
// http://mathworld.wolfram.com/WeibullDistribution.html

#include <boost/math/distributions/fwd.hpp>
#include <boost/math/special_functions/gamma.hpp>
#include <boost/math/special_functions/log1p.hpp>
#include <boost/math/special_functions/expm1.hpp>
#include <boost/math/distributions/detail/common_error_handling.hpp>
#include <boost/math/distributions/complement.hpp>

#include <utility>

namespace boost{ namespace math
{
namespace detail{

template <class RealType, class Policy>
inline bool check_weibull_shape(
      const char* function,
      RealType shape,
      RealType* result, const Policy& pol)
{
   if((shape < 0) || !(boost::math::isfinite)(shape))
   {
      *result = policies::raise_domain_error<RealType>(
         function,
         "Shape parameter is %1%, but must be > 0 !", shape, pol);
      return false;
   }
   return true;
}

template <class RealType, class Policy>
inline bool check_weibull_x(
      const char* function,
      RealType const& x,
      RealType* result, const Policy& pol)
{
   if((x < 0) || !(boost::math::isfinite)(x))
   {
      *result = policies::raise_domain_error<RealType>(
         function,
         "Random variate is %1% but must be >= 0 !", x, pol);
      return false;
   }
   return true;
}

template <class RealType, class Policy>
inline bool check_weibull(
      const char* function,
      RealType scale,
      RealType shape,
      RealType* result, const Policy& pol)
{
   return check_scale(function, scale, result, pol) && check_weibull_shape(function, shape, result, pol);
}

} // namespace detail

template <class RealType = double, class Policy = policies::policy<> >
class weibull_distribution
{
public:
   typedef RealType value_type;
   typedef Policy policy_type;

   weibull_distribution(RealType shape, RealType scale = 1)
      : m_shape(shape), m_scale(scale)
   {
      RealType result;
      detail::check_weibull("boost::math::weibull_distribution<%1%>::weibull_distribution", scale, shape, &result, Policy());
   }

   RealType shape()const
   {
      return m_shape;
   }

   RealType scale()const
   {
      return m_scale;
   }
private:
   //
   // Data members:
   //
   RealType m_shape;     // distribution shape
   RealType m_scale;     // distribution scale
};

typedef weibull_distribution<double> weibull;

template <class RealType, class Policy>
inline const std::pair<RealType, RealType> range(const weibull_distribution<RealType, Policy>& /*dist*/)
{ // Range of permissible values for random variable x.
   using boost::math::tools::max_value;
   return std::pair<RealType, RealType>(0, max_value<RealType>());
}

template <class RealType, class Policy>
inline const std::pair<RealType, RealType> support(const weibull_distribution<RealType, Policy>& /*dist*/)
{ // Range of supported values for random variable x.
   // This is range where cdf rises from 0 to 1, and outside it, the pdf is zero.
   using boost::math::tools::max_value;
   using boost::math::tools::min_value;
   return std::pair<RealType, RealType>(min_value<RealType>(),  max_value<RealType>());
   // A discontinuity at x == 0, so only support down to min_value.
}

template <class RealType, class Policy>
inline RealType pdf(const weibull_distribution<RealType, Policy>& dist, const RealType& x)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   static const char* function = "boost::math::pdf(const weibull_distribution<%1%>, %1%)";

   RealType shape = dist.shape();
   RealType scale = dist.scale();

   RealType result;
   if(false == detail::check_weibull(function, scale, shape, &result, Policy()))
      return result;
   if(false == detail::check_weibull_x(function, x, &result, Policy()))
      return result;

   if(x == 0)
   { // Special case, but x == min, pdf = 1 for shape = 1,
      return 0;
   }
   result = exp(-pow(x / scale, shape));
   result *= pow(x / scale, shape) * shape / x;

   return result;
}

template <class RealType, class Policy>
inline RealType cdf(const weibull_distribution<RealType, Policy>& dist, const RealType& x)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   static const char* function = "boost::math::cdf(const weibull_distribution<%1%>, %1%)";

   RealType shape = dist.shape();
   RealType scale = dist.scale();

   RealType result;
   if(false == detail::check_weibull(function, scale, shape, &result, Policy()))
      return result;
   if(false == detail::check_weibull_x(function, x, &result, Policy()))
      return result;

   result = -boost::math::expm1(-pow(x / scale, shape), Policy());

   return result;
}

template <class RealType, class Policy>
inline RealType quantile(const weibull_distribution<RealType, Policy>& dist, const RealType& p)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   static const char* function = "boost::math::quantile(const weibull_distribution<%1%>, %1%)";

   RealType shape = dist.shape();
   RealType scale = dist.scale();

   RealType result;
   if(false == detail::check_weibull(function, scale, shape, &result, Policy()))
      return result;
   if(false == detail::check_probability(function, p, &result, Policy()))
      return result;

   if(p == 1)
      return policies::raise_overflow_error<RealType>(function, 0, Policy());

   result = scale * pow(-boost::math::log1p(-p, Policy()), 1 / shape);

   return result;
}

template <class RealType, class Policy>
inline RealType cdf(const complemented2_type<weibull_distribution<RealType, Policy>, RealType>& c)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   static const char* function = "boost::math::cdf(const weibull_distribution<%1%>, %1%)";

   RealType shape = c.dist.shape();
   RealType scale = c.dist.scale();

   RealType result;
   if(false == detail::check_weibull(function, scale, shape, &result, Policy()))
      return result;
   if(false == detail::check_weibull_x(function, c.param, &result, Policy()))
      return result;

   result = exp(-pow(c.param / scale, shape));

   return result;
}

template <class RealType, class Policy>
inline RealType quantile(const complemented2_type<weibull_distribution<RealType, Policy>, RealType>& c)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   static const char* function = "boost::math::quantile(const weibull_distribution<%1%>, %1%)";

   RealType shape = c.dist.shape();
   RealType scale = c.dist.scale();
   RealType q = c.param;

   RealType result;
   if(false == detail::check_weibull(function, scale, shape, &result, Policy()))
      return result;
   if(false == detail::check_probability(function, q, &result, Policy()))
      return result;

   if(q == 0)
      return policies::raise_overflow_error<RealType>(function, 0, Policy());

   result = scale * pow(-log(q), 1 / shape);

   return result;
}

template <class RealType, class Policy>
inline RealType mean(const weibull_distribution<RealType, Policy>& dist)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   static const char* function = "boost::math::mean(const weibull_distribution<%1%>)";

   RealType shape = dist.shape();
   RealType scale = dist.scale();

   RealType result;
   if(false == detail::check_weibull(function, scale, shape, &result, Policy()))
      return result;

   result = scale * boost::math::tgamma(1 + 1 / shape, Policy());
   return result;
}

template <class RealType, class Policy>
inline RealType variance(const weibull_distribution<RealType, Policy>& dist)
{
   RealType shape = dist.shape();
   RealType scale = dist.scale();

   static const char* function = "boost::math::variance(const weibull_distribution<%1%>)";

   RealType result;
   if(false == detail::check_weibull(function, scale, shape, &result, Policy()))
   {
      return result;
   }
   result = boost::math::tgamma(1 + 1 / shape, Policy());
   result *= -result;
   result += boost::math::tgamma(1 + 2 / shape, Policy());
   result *= scale * scale;
   return result;
}

template <class RealType, class Policy>
inline RealType mode(const weibull_distribution<RealType, Policy>& dist)
{
   BOOST_MATH_STD_USING  // for ADL of std function pow.

   static const char* function = "boost::math::mode(const weibull_distribution<%1%>)";

   RealType shape = dist.shape();
   RealType scale = dist.scale();

   RealType result;
   if(false == detail::check_weibull(function, scale, shape, &result, Policy()))
   {
      return result;
   }
   if(shape <= 1)
      return 0;
   result = scale * pow((shape - 1) / shape, 1 / shape);
   return result;
}

template <class RealType, class Policy>
inline RealType median(const weibull_distribution<RealType, Policy>& dist)
{
   BOOST_MATH_STD_USING  // for ADL of std function pow.

   static const char* function = "boost::math::median(const weibull_distribution<%1%>)";

   RealType shape = dist.shape(); // Wikipedia k
   RealType scale = dist.scale(); // Wikipedia lambda

   RealType result;
   if(false == detail::check_weibull(function, scale, shape, &result, Policy()))
   {
      return result;
   }
   using boost::math::constants::ln_two;
   result = scale * pow(ln_two<RealType>(), 1 / shape);
   return result;
}

template <class RealType, class Policy>
inline RealType skewness(const weibull_distribution<RealType, Policy>& dist)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   static const char* function = "boost::math::skewness(const weibull_distribution<%1%>)";

   RealType shape = dist.shape();
   RealType scale = dist.scale();

   RealType result;
   if(false == detail::check_weibull(function, scale, shape, &result, Policy()))
   {
      return result;
   }
   RealType g1, g2, g3, d;

   g1 = boost::math::tgamma(1 + 1 / shape, Policy());
   g2 = boost::math::tgamma(1 + 2 / shape, Policy());
   g3 = boost::math::tgamma(1 + 3 / shape, Policy());
   d = pow(g2 - g1 * g1, RealType(1.5));

   result = (2 * g1 * g1 * g1 - 3 * g1 * g2 + g3) / d;
   return result;
}

template <class RealType, class Policy>
inline RealType kurtosis_excess(const weibull_distribution<RealType, Policy>& dist)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   static const char* function = "boost::math::kurtosis_excess(const weibull_distribution<%1%>)";

   RealType shape = dist.shape();
   RealType scale = dist.scale();

   RealType result;
   if(false == detail::check_weibull(function, scale, shape, &result, Policy()))
      return result;

   RealType g1, g2, g3, g4, d, g1_2, g1_4;

   g1 = boost::math::tgamma(1 + 1 / shape, Policy());
   g2 = boost::math::tgamma(1 + 2 / shape, Policy());
   g3 = boost::math::tgamma(1 + 3 / shape, Policy());
   g4 = boost::math::tgamma(1 + 4 / shape, Policy());
   g1_2 = g1 * g1;
   g1_4 = g1_2 * g1_2;
   d = g2 - g1_2;
   d *= d;

   result = -6 * g1_4 + 12 * g1_2 * g2 - 3 * g2 * g2 - 4 * g1 * g3 + g4;
   result /= d;
   return result;
}

template <class RealType, class Policy>
inline RealType kurtosis(const weibull_distribution<RealType, Policy>& dist)
{
   return kurtosis_excess(dist) + 3;
}

} // namespace math
} // namespace boost

// This include must be at the end, *after* the accessors
// for this distribution have been defined, in order to
// keep compilers that support two-phase lookup happy.
#include <boost/math/distributions/detail/derived_accessors.hpp>

#endif // BOOST_STATS_WEIBULL_HPP


//////////////////////////////////////////////////////////////////////////////
///   Copyright 2003 and onward LASMEA UMR 6602 CNRS/U.B.P Clermont-Ferrand
///   Copyright 2009 and onward LRI    UMR 8623 CNRS/Univ Paris Sud XI
///
///          Distributed under the Boost Software License, Version 1.0
///                 See accompanying file LICENSE.txt or copy at
///                     http://www.boost.org/LICENSE_1_0.txt
//////////////////////////////////////////////////////////////////////////////
#ifndef NT2_TOOLBOX_ARITHMETIC_FUNCTION_SIMD_COMMON_RDIVIDE_HPP_INCLUDED
#define NT2_TOOLBOX_ARITHMETIC_FUNCTION_SIMD_COMMON_RDIVIDE_HPP_INCLUDED
#include <nt2/sdk/meta/upgrade.hpp>
#include <nt2/sdk/constant/digits.hpp>
#include <boost/fusion/tuple.hpp>
#include <nt2/sdk/meta/strip.hpp>
#include <nt2/include/functions/is_eqz.hpp>
#include <nt2/include/functions/group.hpp>
#include <nt2/include/functions/split.hpp>
#include <nt2/include/functions/toint.hpp>
#include <nt2/include/functions/tofloat.hpp>

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A0 is arithmetic_
/////////////////////////////////////////////////////////////////////////////
NT2_REGISTER_DISPATCH(tag::rdivide_, tag::cpu_,
                          (A0)(X),
                          ((simd_<arithmetic_<A0>,X>))
                          ((simd_<arithmetic_<A0>,X>))
                         );

namespace nt2 { namespace ext
{
  template<class X, class Dummy>
  struct call<tag::rdivide_(tag::simd_<tag::arithmetic_, X> ,
                            tag::simd_<tag::arithmetic_, X> ),
              tag::cpu_, Dummy> : callable
  {
    template<class Sig> struct result;
    template<class This,class A0>
    struct result<This(A0,A0)>
      : meta::strip<A0>{};//

    NT2_FUNCTOR_CALL(2)
    {
      const A0 iseqza1 = is_eqz(a1);
      return (a0-(iseqza1&a0))/(a1+(iseqza1&One<A0>()));
    }
  };
} }

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A0 is int8_
/////////////////////////////////////////////////////////////////////////////
NT2_REGISTER_DISPATCH(tag::rdivide_, tag::cpu_,
                          (A0)(X),
                          ((simd_<int8_<A0>,X>))
                          ((simd_<int8_<A0>,X>))
                         );

namespace nt2 { namespace ext
{
  template<class X, class Dummy>
  struct call<tag::rdivide_(tag::simd_<tag::int8_, X> ,
                            tag::simd_<tag::int8_, X> ),
              tag::cpu_, Dummy> : callable
  {
    template<class Sig> struct result;
    template<class This,class A0>
    struct result<This(A0,A0)>
      : meta::strip<A0>{};//

    NT2_FUNCTOR_CALL(2)
    {
      typedef typename meta::scalar_of<A0>::type              stype;
      typedef typename meta::upgrade<stype>::type             itype;
      typedef simd::native<itype, X>                 ivtype;
      ivtype a0l, a0h, a1l, a1h;
      boost::fusion::tie(a0l, a0h) = split(a0);
      boost::fusion::tie(a1l, a1h) = split(a1);
      return simd::native_cast<A0>(group(nt2::rdivide(a0l, a1l),
                               nt2::rdivide(a0h, a1h) ));
    }
  };
} }

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A0 is int16_
/////////////////////////////////////////////////////////////////////////////
NT2_REGISTER_DISPATCH(tag::rdivide_, tag::cpu_,
                          (A0)(X),
                          ((simd_<int16_<A0>,X>))
                          ((simd_<int16_<A0>,X>))
                         );

namespace nt2 { namespace ext
{
  template<class X, class Dummy>
  struct call<tag::rdivide_(tag::simd_<tag::int16_, X> ,
                            tag::simd_<tag::int16_, X> ),
              tag::cpu_, Dummy> : callable
  {
    template<class Sig> struct result;
    template<class This,class A0>
    struct result<This(A0,A0)>
      : meta::strip<A0>{};//

    NT2_FUNCTOR_CALL(2)
    {
      typedef typename meta::scalar_of<A0>::type             stype;
      typedef typename meta::upgrade<stype>::type            itype;
      typedef simd::native<itype,X>                 ivtype;
      ivtype a0l, a0h, a1l, a1h;
      boost::fusion::tie(a0l, a0h) = split(a0);
      boost::fusion::tie(a1l, a1h) = split(a1);
//       std::cout << type_id < ivtype > () << std::endl;
//       std::cout << a1l << std::endl;
//       std::cout << a1h << std::endl;
//       std::cout << a0l << std::endl;
//       std::cout << a0h << std::endl;
      return sel(is_eqz(a1),
		 Zero<A0>(),
		 simd::native_cast<A0>(group(toint(tofloat(a0l)/tofloat(a1l)),
					     toint(tofloat(a0h)/tofloat(a1h)))));
    }
  };
} }

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A0 is real_
/////////////////////////////////////////////////////////////////////////////
NT2_REGISTER_DISPATCH(tag::rdivide_, tag::cpu_,
                          (A0)(X),
                          ((simd_<real_<A0>,X>))
                          ((simd_<real_<A0>,X>))
                         );

namespace nt2 { namespace ext
{
  template<class X, class Dummy>
  struct call<tag::rdivide_(tag::simd_<tag::real_, X> ,
                            tag::simd_<tag::real_, X> ),
              tag::cpu_, Dummy> : callable
  {
    template<class Sig> struct result;
    template<class This,class A0>
    struct result<This(A0,A0)>
      : meta::strip<A0>{};//

    NT2_FUNCTOR_CALL(2)
    {
      return b_or(b_and(is_eqz(a0), is_eqz(a1)), a0/a1);
    }
  };
} }

#endif
// modified by jt the 04/01/2011


#include <cstring>
#include <fstream>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <cstring>

#include <NTL/ZZX.h>
#include <NTL/vector.h>

#include "FHE.h"
#include "timing.h"
#include "EncryptedArray.h"

// https://www.linuxquestions.org/questions/programming-9/c-list-files-in-directory-379323/
// get Dir file names
int getdir(string dir, vector <string> &files) {
    DIR *dp;
    struct dirent *dirp;
    if ((dp = opendir(dir.c_str())) == NULL) {
        cout << "Error(" << errno << ") opening " << dir << endl;
        return errno;
    }

    int checkIndex = 0;
    while ((dirp = readdir(dp)) != NULL) {
        if(checkIndex++ < 2) continue;
        string fileName = string(dirp->d_name);
        files.push_back(string(dirp->d_name));
    }
    closedir(dp);
    return 0;
}

int main(int argc, char *argv[]) {
    ArgMapping amap;

    long numberOfCandidates = 4;
    string owner = "owner";
    string directoryPath = "data";

    amap.arg("n", numberOfCandidates, "number of candidates");
    amap.arg("o", owner, "owner's address");
    amap.arg("dir", directoryPath, "data directory Path");
    amap.parse(argc, argv);

    // setting directory path with Ctxt files
    string directoryPathWithCtxt = directoryPath + "/result/" + owner;

    // files name vector list
    vector<string> fileNames = vector<string>();

    // set resultFile
    const string resultFilePath = directoryPath + "/result/" + owner + ".txt";
    ofstream resultFile(resultFilePath.c_str(), ios::binary);
    assert(resultFile.is_open());

    // get Ctxt files
    getdir(directoryPathWithCtxt, fileNames);
    const long sizeOfCtxtFile = fileNames.size();
    if(sizeOfCtxtFile == 0) {
        string res = "[";
        for (int i = 0; i < numberOfCandidates; i++) {
            res.append(to_string(i));
            if(i != numberOfCandidates-1) res.append(",");
        }
        res.append("]");

        resultFile << res;
        return 0;
    }

    // get public key
    const string secretKeyBinaryFilePath = directoryPath + "/secretKey/" + owner + ".bin";
    ifstream secretBinFile(secretKeyBinaryFilePath.c_str(), ios::binary);
    assert(secretBinFile.is_open());

    // make stream of Ctxt Files
    fstream CtxtFiles[sizeOfCtxtFile];
    for (long i = 0;i < sizeOfCtxtFile;i++) {
        string fileName = directoryPathWithCtxt + "/" + fileNames[i];
        CtxtFiles[i] = fstream(fileName.c_str(), fstream::in);
        assert(CtxtFiles[i].is_open());
    }

    // Read context in secret bin file
    std::unique_ptr <FHEcontext> context = buildContextFromBinary(secretBinFile);
    readContextBinary(secretBinFile, *context);

    // Read in SecKey and PubKey.
    // Got to insert pubKey into seckey obj first.
    std::unique_ptr <FHESecKey> secKey(new FHESecKey(*context));
    FHEPubKey *pubKey = (FHEPubKey *) secKey.get();

    // read publicKey
    readPubKeyBinary(secretBinFile, *pubKey);
    readSecKeyBinary(secretBinFile, *secKey);

    // close secreyKey file
    secretBinFile.close();

    cout << "read PublicKey & SecretKey successful.\n" << flush;

    // ready to add two Ctxts
    // get Ctxt to file
    Ctxt resultCtxt(*pubKey), secondCtxt(*pubKey), tempCtxt(*pubKey);

    // fill 1 to tempCtxt
    Vec <ZZ> tempPoly;
    tempPoly.SetLength(numberOfCandidates);

    // set poly
    for (long i = 0; i < numberOfCandidates; i++) tempPoly[i] = 1;

    // encrypt poly
    pubKey->Encrypt(tempCtxt, to_ZZX(tempPoly));

    // add files
    ZZX tempCtxtZZX;
    ostringstream cTxtStream;

    CtxtFiles[0] >> resultCtxt;
    cTxtStream << resultCtxt;
    secKey->Decrypt(tempCtxtZZX, resultCtxt);

    cout << "\n" << fileNames[0] << endl;
    cout << "CypherText: " << cTxtStream.str().substr(0, 20)
        << " ... " << cTxtStream.str().substr(cTxtStream.str().size() - 63, 50) << endl;
    cout << "PlainText: " << tempCtxtZZX << endl;
    cout << "\n+\n" << endl;

    for (long i = 1; i < sizeOfCtxtFile; i++) {
        CtxtFiles[i] >> secondCtxt;
        resultCtxt += secondCtxt;
        cTxtStream << secondCtxt;
        secKey->Decrypt(tempCtxtZZX, secondCtxt);

        cout << fileNames[i] << endl;
        cout << "CypherText: " << cTxtStream.str().substr(0, 20)
             << " ..." << cTxtStream.str().substr(cTxtStream.str().size() - 63, 50) << endl;
        cout << "PlainText:" << tempCtxtZZX << endl;
        if(i != sizeOfCtxtFile-1) cout << "\n+\n" << endl;
    }
    cout << "\n=\n" << endl;

    // save result
    ZZX ptSum;
    cTxtStream << resultCtxt;
    secKey->Decrypt(ptSum, resultCtxt);

    cout << " [ tally result ]" << endl;
    cout << "CypherText: " << cTxtStream.str().substr(0, 20)
         << " ..." << cTxtStream.str().substr(cTxtStream.str().size() - 63, 50) << endl;
    cout << "PlainText: " << ptSum << endl;

    // last elements +1
    resultCtxt += tempCtxt;
    secKey->Decrypt(ptSum, resultCtxt);

    // save result to file
    resultFile << ptSum;
}


#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
#include <Eigen/Core>

#include "george/hodlr.h"
#include "george/kernels.h"
#include "george/parser.h"
#include "george/exceptions.h"

namespace py = pybind11;

using RowMatrixXd = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;

class SolverMatrix {
  public:
    SolverMatrix (george::kernels::Kernel* kernel)
      : kernel_(kernel) {};
    void set_input_coordinates (RowMatrixXd x) {
      if (size_t(x.cols()) != kernel_->get_ndim()) {
        throw george::dimension_mismatch();
      }
      t_ = x;
    };
    double get_value (const int i, const int j) {
      if (i < 0 || i >= t_.rows() || j < 0 || j >= t_.rows()) {
        throw std::out_of_range("attempting to index outside of the dimension of the input coordinates");
      }
      return kernel_->value(t_.row(i).data(), t_.row(j).data());
    };

  private:
    george::kernels::Kernel* kernel_;
    RowMatrixXd t_;
};

class Solver {
public:

  Solver () {
    solver_ = NULL;
    kernel_ = NULL;
    matrix_ = NULL;
    computed_ = 0;
  };
  ~Solver () {
    if (solver_ != NULL) delete solver_;
    if (matrix_ != NULL) delete matrix_;
    if (kernel_ != NULL) delete kernel_;
  };

  int get_status () const { return 0; };
  int get_computed () const { return computed_; };
  double log_determinant () const { return log_det_; };

  int compute (
      const py::object& kernel_spec,
      const py::array_t<double>& x,
      const py::array_t<double>& yerr,
      int min_size = 100, double tol = 0.1, int seed = 0
  ) {
    computed_ = 0;
    kernel_ = george::parse_kernel_spec(kernel_spec);
    matrix_ = new SolverMatrix(kernel_);

    // Random number generator for reproducibility
    std::random_device r;
    std::mt19937 random(r());
    random.seed(seed);

    // Extract the data from the numpy arrays
    py::detail::unchecked_reference<double, 2L> x_p = x.unchecked<2>();
    py::detail::unchecked_reference<double, 1L> yerr_p = yerr.unchecked<1>();
    size_t n = x_p.shape(0), ndim = x_p.shape(1);
    RowMatrixXd X(n, ndim);
    Eigen::VectorXd diag(n);
    for (size_t i = 0; i < n; ++i) {
      diag(i) = yerr_p(i) * yerr_p(i);
      for (size_t j = 0; j < ndim; ++j) X(i, j) = x_p(i, j);
    }

    matrix_->set_input_coordinates(X);

    // Set up the solver object.
    if (solver_ != NULL) delete solver_;
    solver_ = new george::hodlr::Node<SolverMatrix> (
        diag, matrix_, 0, n, min_size, tol, random);
    solver_->compute();
    log_det_ = solver_->log_determinant();

    // Update the bookkeeping flags.
    computed_ = 1;
    size_ = n;
    return 0;
  };

  template <typename Derived>
  void apply_inverse (Eigen::MatrixBase<Derived>& x) {
    if (!computed_) throw george::not_computed();
    solver_->solve(x);
  };

  int size () const { return size_; };

private:
  double log_det_;
  int size_;
  int computed_;

  george::kernels::Kernel* kernel_;
  SolverMatrix* matrix_;
  george::hodlr::Node<SolverMatrix>* solver_;
};

PYBIND11_MODULE(_hodlr, m) {
  py::class_<Solver> solver(m, "HODLRSolver", R"delim(
A solver using `Sivaram Amambikasaran's HODLR algorithm
<http://arxiv.org/abs/1403.6015>`_ to approximately solve the GP linear
algebra in :math:`\mathcal{O}(N\,\log^2 N)`.

)delim");
  solver.def(py::init());
  solver.def_property_readonly("computed", &Solver::get_computed);
  solver.def_property_readonly("log_determinant", &Solver::log_determinant);
  solver.def("compute", &Solver::compute, R"delim(
Compute and factorize the covariance matrix.

Args:
    kernel (george.kernels.Kernel): A subclass of :class:`Kernel` specifying
        the kernel function.
    x (ndarray[nsamples, ndim]): The independent coordinates of the data
        points.
    yerr (ndarray[nsamples]): The Gaussian uncertainties on the data points at
        coordinates ``x``. These values will be added in quadrature to the
        diagonal of the covariance matrix.
    min_size (Optional[int]): The block size where the solver switches to a
        general direct factorization algorithm. This can be tuned for platform
        and problem specific performance and accuracy. As a general rule,
        larger values will be more accurate and slower, but there is some
        overhead for very small values, so we recommend choosing values in the
        hundreds. (default: ``100``)
    tol (Optional[float]): The precision tolerance for the low-rank
        approximation. This value is used as an approximate limit on the
        Frobenius norm between the low-rank approximation and the true matrix
        when reconstructing the off-diagonal blocks. Smaller values of ``tol``
        will generally give more accurate results with higher computational
        cost. (default: ``0.1``)
    seed (Optional[int]): The low-rank approximation method within the HODLR
        algorithm is not deterministic and, without a fixed seed, the method
        can give different results for the same matrix. Therefore, we require
        that the user provide a seed for the random number generator.
        (default: ``42``, obviously)
)delim",
    py::arg("kernel_spec"), py::arg("x"), py::arg("yerr"), py::arg("min_size") = 100, py::arg("tol") = 0.1, py::arg("seed") = 42
  );
  solver.def("apply_inverse", [](Solver& self, Eigen::MatrixXd& x, bool in_place = false){
    if (in_place) {
      self.apply_inverse(x);
      return x;
    }
    Eigen::MatrixXd alpha = x;
    self.apply_inverse(alpha);
    return alpha;
  }, py::arg("x"), py::arg("in_place") = false, R"delim(
Apply the inverse of the covariance matrix to the input by solving

.. math::

    K\,x = y

Args:
    y (ndarray[nsamples] or ndadrray[nsamples, nrhs]): The vector or matrix
        :math:`y`.
    in_place (Optional[bool]): Should the data in ``y`` be overwritten with
        the result :math:`x`? (default: ``False``)
)delim");

  solver.def("dot_solve", [](Solver& self, const Eigen::VectorXd& x){
    Eigen::VectorXd alpha = x;
    self.apply_inverse(alpha);
    return double(x.transpose() * alpha);
  }, R"delim(
Compute the inner product of a vector with the inverse of the covariance
matrix applied to itself:

.. math::

    y\,K^{-1}\,y

Args:
    y (ndarray[nsamples]): The vector :math:`y`.
)delim");

  solver.def("get_inverse", [](Solver& self){
    int n = self.size();
    Eigen::MatrixXd eye(n, n);
    eye.setIdentity();
    self.apply_inverse(eye);
    return eye;
  }, R"delim(
Get the dense inverse covariance matrix. This is used for computing gradients,
but it is not recommended in general.
)delim");
}


#ifndef VIENNACL_LINALG_DETAIL_AMG_AMG_INTERPOL_HPP
#define VIENNACL_LINALG_DETAIL_AMG_AMG_INTERPOL_HPP

/* =========================================================================
   Copyright (c) 2010-2014, Institute for Microelectronics,
                            Institute for Analysis and Scientific Computing,
                            TU Wien.
   Portions of this software are copyright by UChicago Argonne, LLC.

                            -----------------
                  ViennaCL - The Vienna Computing Library
                            -----------------

   Project Head:    Karl Rupp                   rupp@iue.tuwien.ac.at

   (A list of authors and contributors can be found in the PDF manual)

   License:         MIT (X11), see file LICENSE in the base directory
============================================================================= */

/** @file amg_interpol.hpp
    @brief Implementations of several variants of the AMG interpolation operators (setup phase). Experimental.
*/

#include <boost/numeric/ublas/vector.hpp>
#include <cmath>
#include "viennacl/linalg/detail/amg/amg_base.hpp"

#include <map>
#ifdef VIENNACL_WITH_OPENMP
#include <omp.h>
#endif

#include "viennacl/linalg/detail/amg/amg_debug.hpp"

namespace viennacl
{
namespace linalg
{
namespace detail
{
namespace amg
{

/** @brief Calls the right function to build interpolation matrix
 * @param level        Coarse level identifier
 * @param A            Operator matrix on all levels
 * @param P            Prolongation matrices. P[level] is constructed
 * @param pointvector  Vector of points on all levels
 * @param tag          AMG preconditioner tag
*/
template<typename InternalT1, typename InternalT2>
void amg_interpol(unsigned int level, InternalT1 & A, InternalT1 & P, InternalT2 & pointvector, amg_tag & tag)
{
  switch (tag.get_interpol())
  {
  case VIENNACL_AMG_INTERPOL_DIRECT:  amg_interpol_direct (level, A, P, pointvector, tag); break;
  case VIENNACL_AMG_INTERPOL_CLASSIC: amg_interpol_classic(level, A, P, pointvector, tag); break;
  case VIENNACL_AMG_INTERPOL_AG:      amg_interpol_ag     (level, A, P, pointvector, tag); break;
  case VIENNACL_AMG_INTERPOL_SA:      amg_interpol_sa     (level, A, P, pointvector, tag); break;
  }
}

/** @brief Direct interpolation. Multi-threaded! (VIENNACL_AMG_INTERPOL_DIRECT)
 * @param level        Coarse level identifier
 * @param A            Operator matrix on all levels
 * @param P            Prolongation matrices. P[level] is constructed
 * @param pointvector  Vector of points on all levels
 * @param tag          AMG preconditioner tag
*/
template<typename InternalT1, typename InternalT2>
void amg_interpol_direct(unsigned int level, InternalT1 & A, InternalT1 & P, InternalT2 & pointvector, amg_tag & tag)
{
  typedef typename InternalT1::value_type         SparseMatrixType;
  //typedef typename InternalType2::value_type    PointVectorType;
  typedef typename SparseMatrixType::value_type   ScalarType;
  typedef typename SparseMatrixType::iterator1    InternalRowIterator;
  typedef typename SparseMatrixType::iterator2    InternalColIterator;

  unsigned int c_points = pointvector[level].get_cpoints();

  // Setup Prolongation/Interpolation matrix
  P[level] = SparseMatrixType(static_cast<unsigned int>(A[level].size1()), c_points);
  P[level].clear();

  // Assign indices to C points
  pointvector[level].build_index();

  // Direct Interpolation (Yang, p.14)
#ifdef VIENNACL_WITH_OPENMP
  #pragma omp parallel for
#endif
  for (long x=0; x < static_cast<long>(pointvector[level].size()); ++x)
  {
    amg_point *pointx = pointvector[level][static_cast<unsigned int>(x)];
    /*if (A[level](x,x) > 0)
      diag_sign = 1;
    else
      diag_sign = -1;*/

    // When the current line corresponds to a C point then the diagonal coefficient is 1 and the rest 0
    if (pointx->is_cpoint())
      P[level](static_cast<unsigned int>(x),pointx->get_coarse_index()) = 1;

    // When the current line corresponds to a F point then the diagonal is 0 and the rest has to be computed (Yang, p.14)
    if (pointx->is_fpoint())
    {
      // Jump to row x
      InternalRowIterator row_iter = A[level].begin1();
      row_iter += vcl_size_t(x);

      // Row sum of coefficients (without diagonal) and sum of influencing C point coefficients has to be computed
      ScalarType row_sum = 0;
      ScalarType c_sum   = 0;
      ScalarType diag    = 0;
      for (InternalColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
      {
        long y = static_cast<long>(col_iter.index2());
        if (x == y)// || *col_iter * diag_sign > 0)
        {
          diag += *col_iter;
          continue;
        }

        // Sum all other coefficients in line x
        row_sum += *col_iter;

        amg_point *pointy = pointvector[level][static_cast<unsigned int>(y)];
        // Sum all coefficients that correspond to a strongly influencing C point
        if (pointy->is_cpoint())
          if (pointx->is_influencing(pointy))
            c_sum += *col_iter;
      }
      ScalarType temp_res = -row_sum/(c_sum*diag);

      // Iterate over all strongly influencing points of point x
      for (amg_point::iterator iter = pointx->begin_influencing(); iter != pointx->end_influencing(); ++iter)
      {
        amg_point *pointy = *iter;
        // The value is only non-zero for columns that correspond to a C point
        if (pointy->is_cpoint())
        {
          if (temp_res > 0 || temp_res < 0)
            P[level](static_cast<unsigned int>(x), pointy->get_coarse_index()) = temp_res * A[level](static_cast<unsigned int>(x),pointy->get_index());
        }
      }

      //Truncate interpolation if chosen
      if (tag.get_interpolweight() > 0)
        amg_truncate_row(P[level], static_cast<unsigned int>(x), tag);
    }
  }

  // P test
  //test_interpolation(A[level], P[level], Pointvector[level]);

  #ifdef VIENNACL_AMG_DEBUG
  std::cout << "Prolongation Matrix:" << std::endl;
  printmatrix (P[level]);
  #endif
}

/** @brief Classical interpolation. Don't use with onepass classical coarsening or RS0 (Yang, p.14)! Multi-threaded! (VIENNACL_AMG_INTERPOL_CLASSIC)
 * @param level        Coarse level identifier
 * @param A            Operator matrix on all levels
 * @param P            Prolongation matrices. P[level] is constructed
 * @param pointvector  Vector of points on all levels
 * @param tag          AMG preconditioner tag
*/
template<typename InternalT1, typename InternalT2>
void amg_interpol_classic(unsigned int level, InternalT1 & A, InternalT1 & P, InternalT2 & pointvector, amg_tag & tag)
{
  typedef typename InternalT1::value_type           SparseMatrixType;
  //typedef typename InternalType2::value_type      PointVectorType;
  typedef typename SparseMatrixType::value_type     ScalarType;
  typedef typename SparseMatrixType::iterator1      InternalRowIterator;
  typedef typename SparseMatrixType::iterator2      InternalColIterator;

  unsigned int c_points = pointvector[level].get_cpoints();

  // Setup Prolongation/Interpolation matrix
  P[level] = SparseMatrixType(static_cast<unsigned int>(A[level].size1()), c_points);
  P[level].clear();

  // Assign indices to C points
  pointvector[level].build_index();

  // Classical Interpolation (Yang, p.13-14)
#ifdef VIENNACL_WITH_OPENMP
  #pragma omp parallel for
#endif
  for (long x=0; x < static_cast<long>(pointvector[level].size()); ++x)
  {
    amg_point *pointx = pointvector[level][static_cast<unsigned int>(x)];
    int diag_sign = (A[level](static_cast<unsigned int>(x),static_cast<unsigned int>(x)) > 0) ? 1 : -1;

    // When the current line corresponds to a C point then the diagonal coefficient is 1 and the rest 0
    if (pointx->is_cpoint())
      P[level](static_cast<unsigned int>(x),pointx->get_coarse_index()) = 1;

    // When the current line corresponds to a F point then the diagonal is 0 and the rest has to be computed (Yang, p.14)
    if (pointx->is_fpoint())
    {
      // Jump to row x
      InternalRowIterator row_iter = A[level].begin1();
      row_iter += vcl_size_t(x);

      ScalarType weak_sum = 0;
      amg_sparsevector<ScalarType> c_sum_row(static_cast<unsigned int>(A[level].size1()));
      c_sum_row.clear();
      for (InternalColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
      {
        long k = static_cast<unsigned int>(col_iter.index2());
        amg_point *pointk = pointvector[level][static_cast<unsigned int>(k)];

        // Sum of weakly influencing neighbors + diagonal coefficient
        if (x == k || !pointx->is_influencing(pointk))// || *col_iter * diag_sign > 0)
        {
          weak_sum += *col_iter;
          continue;
        }

        // Sums of coefficients in row k (strongly influening F neighbors) of C point neighbors of x are calculated
        if (pointk->is_fpoint() && pointx->is_influencing(pointk))
        {
          for (amg_point::iterator iter = pointx->begin_influencing(); iter != pointx->end_influencing(); ++iter)
          {
            amg_point *pointm = *iter;
            long m = pointm->get_index();

            if (pointm->is_cpoint())
              // Only use coefficients that have opposite sign of diagonal.
              if (A[level](static_cast<unsigned int>(k),static_cast<unsigned int>(m)) * ScalarType(diag_sign) < 0)
                c_sum_row[static_cast<unsigned int>(k)] += A[level](static_cast<unsigned int>(k), static_cast<unsigned int>(m));
          }
          continue;
        }
      }

      // Iterate over all strongly influencing points of point x
      for (amg_point::iterator iter = pointx->begin_influencing(); iter != pointx->end_influencing(); ++iter)
      {
        amg_point *pointy = *iter;
        long y = pointy->get_index();

        // The value is only non-zero for columns that correspond to a C point
        if (pointy->is_cpoint())
        {
          ScalarType strong_sum = 0;
          // Calculate term for strongly influencing F neighbors
          for (typename amg_sparsevector<ScalarType>::iterator iter2 = c_sum_row.begin(); iter2 != c_sum_row.end(); ++iter2)
          {
            long k = iter2.index();
            // Only use coefficients that have opposite sign of diagonal.
            if (A[level](static_cast<unsigned int>(k),static_cast<unsigned int>(y)) * ScalarType(diag_sign) < 0)
              strong_sum += (A[level](static_cast<unsigned int>(x),static_cast<unsigned int>(k)) * A[level](static_cast<unsigned int>(k),static_cast<unsigned int>(y))) / (*iter2);
          }

          // Calculate coefficient
          ScalarType temp_res = - (A[level](static_cast<unsigned int>(x),static_cast<unsigned int>(y)) + strong_sum) / (weak_sum);
          if (temp_res < 0 || temp_res > 0)
            P[level](static_cast<unsigned int>(x),pointy->get_coarse_index()) = temp_res;
        }
      }

      //Truncate iteration if chosen
      if (tag.get_interpolweight() > 0)
        amg_truncate_row(P[level], static_cast<unsigned int>(x), tag);
    }
  }

  #ifdef VIENNACL_AMG_DEBUG
  std::cout << "Prolongation Matrix:" << std::endl;
  printmatrix (P[level]);
  #endif
}

/** @brief Interpolation truncation (for VIENNACL_AMG_INTERPOL_DIRECT and VIENNACL_AMG_INTERPOL_CLASSIC)
*
* @param P    Interpolation matrix
* @param row  Row which has to be truncated
* @param tag  AMG preconditioner tag
*/
template<typename SparseMatrixT>
void amg_truncate_row(SparseMatrixT & P, unsigned int row, amg_tag & tag)
{
  typedef typename SparseMatrixT::value_type   ScalarType;
  typedef typename SparseMatrixT::iterator1    InternalRowIterator;
  typedef typename SparseMatrixT::iterator2    InternalColIterator;

  ScalarType row_max, row_min, row_sum_pos, row_sum_neg, row_sum_pos_scale, row_sum_neg_scale;

  InternalRowIterator row_iter = P.begin1();
  row_iter += row;

  row_max = 0;
  row_min = 0;
  row_sum_pos = 0;
  row_sum_neg = 0;

  // Truncate interpolation by making values to zero that are a lot smaller than the biggest value in a row
  // Determine max entry and sum of row (seperately for negative and positive entries)
  for (InternalColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
  {
    if (*col_iter > row_max)
      row_max = *col_iter;
    if (*col_iter < row_min)
      row_min = *col_iter;
    if (*col_iter > 0)
      row_sum_pos += *col_iter;
    if (*col_iter < 0)
      row_sum_neg += *col_iter;
  }

  row_sum_pos_scale = row_sum_pos;
  row_sum_neg_scale = row_sum_neg;

  // Make certain values to zero (seperately for negative and positive entries)
  for (InternalColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
  {
    if (*col_iter > 0 && *col_iter < tag.get_interpolweight() * row_max)
    {
      row_sum_pos_scale -= *col_iter;
      *col_iter = 0;
    }
    if (*col_iter < 0 && *col_iter > tag.get_interpolweight() * row_min)
    {
      row_sum_pos_scale -= *col_iter;
      *col_iter = 0;
    }
  }

  // Scale remaining values such that row sum is unchanged
  for (InternalColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
  {
    if (*col_iter > 0)
      *col_iter = *col_iter *(row_sum_pos/row_sum_pos_scale);
    if (*col_iter < 0)
      *col_iter = *col_iter *(row_sum_neg/row_sum_neg_scale);
  }
}

/** @brief AG (aggregation based) interpolation. Multi-Threaded! (VIENNACL_INTERPOL_SA)
 * @param level        Coarse level identifier
 * @param A            Operator matrix on all levels
 * @param P            Prolongation matrices. P[level] is constructed
 * @param pointvector  Vector of points on all levels
*/
template<typename InternalT1, typename InternalT2>
void amg_interpol_ag(unsigned int level, InternalT1 & A, InternalT1 & P, InternalT2 & pointvector, amg_tag)
{
  typedef typename InternalT1::value_type SparseMatrixType;
  //typedef typename InternalType2::value_type PointVectorType;
  //typedef typename SparseMatrixType::value_type ScalarType;
  //typedef typename SparseMatrixType::iterator1 InternalRowIterator;
  //typedef typename SparseMatrixType::iterator2 InternalColIterator;

  unsigned int c_points = pointvector[level].get_cpoints();

  P[level] = SparseMatrixType(static_cast<unsigned int>(A[level].size1()), c_points);
  P[level].clear();

  // Assign indices to C points
  pointvector[level].build_index();

  // Set prolongation such that F point is interpolated (weight=1) by the aggregate it belongs to (Vanek et al p.6)
#ifdef VIENNACL_WITH_OPENMP
  #pragma omp parallel for
#endif
  for (long x=0; x<static_cast<long>(pointvector[level].size()); ++x)
  {
    amg_point *pointx = pointvector[level][static_cast<unsigned int>(x)];
    amg_point *pointy = pointvector[level][pointx->get_aggregate()];
    // Point x belongs to aggregate y.
    P[level](static_cast<unsigned int>(x), pointy->get_coarse_index()) = 1;
  }

  #ifdef VIENNACL_AMG_DEBUG
  std::cout << "Aggregation based Prolongation:" << std::endl;
  printmatrix(P[level]);
  #endif
}

/** @brief SA (smoothed aggregate) interpolation. Multi-Threaded! (VIENNACL_INTERPOL_SA)
 * @param level        Coarse level identifier
 * @param A            Operator matrix on all levels
 * @param P            Prolongation matrices. P[level] is constructed
 * @param pointvector  Vector of points on all levels
 * @param tag          AMG preconditioner tag
*/
template<typename InternalT1, typename InternalT2>
void amg_interpol_sa(unsigned int level, InternalT1 & A, InternalT1 & P, InternalT2 & pointvector, amg_tag & tag)
{
  typedef typename InternalT1::value_type         SparseMatrixType;
  //typedef typename InternalType2::value_type    PointVectorType;
  typedef typename SparseMatrixType::value_type   ScalarType;
  typedef typename SparseMatrixType::iterator1    InternalRowIterator;
  typedef typename SparseMatrixType::iterator2    InternalColIterator;

  unsigned int c_points = pointvector[level].get_cpoints();

  InternalT1 P_tentative = InternalT1(P.size());
  SparseMatrixType Jacobi = SparseMatrixType(static_cast<unsigned int>(A[level].size1()), static_cast<unsigned int>(A[level].size2()));
  Jacobi.clear();
  P[level] = SparseMatrixType(static_cast<unsigned int>(A[level].size1()), c_points);
  P[level].clear();

  // Build Jacobi Matrix via filtered A matrix (Vanek et al. p.6)
#ifdef VIENNACL_WITH_OPENMP
  #pragma omp parallel for
#endif
  for (long x=0; x<static_cast<long>(A[level].size1()); ++x)
  {
    ScalarType diag = 0;
    InternalRowIterator row_iter = A[level].begin1();
    row_iter += vcl_size_t(x);
    for (InternalColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
    {
      long y = static_cast<long>(col_iter.index2());
      // Determine the structure of the Jacobi matrix by using a filtered matrix of A:
      // The diagonal consists of the diagonal coefficient minus all coefficients of points not in the neighborhood of x.
      // All other coefficients are the same as in A.
      // Already use Jacobi matrix to save filtered A matrix to speed up computation.
      if (x == y)
        diag += *col_iter;
      else if (!pointvector[level][static_cast<unsigned int>(x)]->is_influencing(pointvector[level][static_cast<unsigned int>(y)]))
        diag += -*col_iter;
      else
        Jacobi (static_cast<unsigned int>(x), static_cast<unsigned int>(y)) = *col_iter;
    }
    InternalRowIterator row_iter2 = Jacobi.begin1();
    row_iter2 += vcl_size_t(x);
    // Traverse through filtered A matrix and compute the Jacobi filtering
    for (InternalColIterator col_iter2 = row_iter2.begin(); col_iter2 != row_iter2.end(); ++col_iter2)
    {
        *col_iter2 = - static_cast<ScalarType>(tag.get_interpolweight())/diag * *col_iter2;
    }
    // Diagonal can be computed seperately.
    Jacobi (static_cast<unsigned int>(x), static_cast<unsigned int>(x)) = 1 - static_cast<ScalarType>(tag.get_interpolweight());
  }

  #ifdef VIENNACL_AMG_DEBUG
  std::cout << "Jacobi Matrix:" << std::endl;
  printmatrix(Jacobi);
  #endif

  // Use AG interpolation as tentative prolongation
  amg_interpol_ag(level, A, P_tentative, pointvector, tag);

  #ifdef VIENNACL_AMG_DEBUG
  std::cout << "Tentative Prolongation:" << std::endl;
  printmatrix(P_tentative[level]);
  #endif

  // Multiply Jacobi matrix with tentative prolongation to get actual prolongation
  amg_mat_prod(Jacobi,P_tentative[level],P[level]);

  #ifdef VIENNACL_AMG_DEBUG
  std::cout << "Prolongation Matrix:" << std::endl;
  printmatrix(P[level]);
  #endif
}

} //namespace amg
} //namespace detail
} //namespace linalg
} //namespace viennacl

#endif


#pragma once
#ifndef _INITIALIZER_HPP_
#define _INITIALIZER_HPP_

#include <list>
#include <set>
#include <vector>
#include <algorithm>
#include <string>
#include <cassert>

#include <Eigen/Dense>
#include <Eigen/StdVector>

#include <petsctime.h>

#include "Grid.hpp"
#include "Interface.hpp"
#include "BoundingBox.hpp"
#include "TriangleElement.hpp"
#include "Geometry.hpp"

#include "tictoc.hpp"
#include "utility.h"

/// compares two signed distances
/// the signed distance and also prepedicular distance is compared
void sdfMin(cDistance* in, cDistance* inout, int len, MPI_Datatype* dptr) {
	int i = 0;
	for(i = 0; i < len; ++i) {
		double ax  = fabs(in[i].signedDistance), ay = fabs(inout[i].signedDistance);
		double eps = my_eps;
		if ( fabs(ax - ay) < eps ) {
			if (in[i].pDistance > inout[i].pDistance) {
				inout[i] = in[i];
			}
		} else if (ax > ay) {
			inout[i] = in[i];
		}
	}
}

class Initializer {
public:
	Initializer() {};

	// This is where the magic happens and the initializator puts the data in
	template <typename type, int dim>
	void operator() (Geometry& geom, Interface<type, dim>& interface, int groupID, int nGroups);

private:
	// some states here and utility functions needed to do the jolb

	MPI_Datatype mpi_cDist_type;
	MPI_Op mpi_sdfmin;

	void createTypes();
	template <typename type, int dim>
	void initAll(const Grid<type, dim>& gr, Geometry& geom, double*** data_ptr);
	template <typename type, int dim>
	void initBoundaries_nlb(const Grid<type, dim>& gr, Geometry& geom, double*** data_ptr, int boundaries);
	template <typename type, int dim>
	void initBoundaries_lb(const Grid<type, dim>& gr, Geometry& geom, double*** data_ptr, int boundaries, int groupID, int nGroups);

	int selectBoundaries(const Box<double, 3>& geometryAABB, const Box<double, 3>& procAABB);

	template <typename type, int dim>
	void putDataToBoundaries(const Grid<type, dim>& gr, double*** data_ptr, cDistance* distanceData, int cellNum, int boundaries);

	template <typename type, int dim>
	void putLocalDataInside(const Grid<type, dim>& gr, double*** data_ptr, const std::vector<int>& localTriangles, const Geometry& geom, double*** perpendicualDistance);

};

///
/// Main routine
///

void Initializer::createTypes() {
	// TODO this should be rewritten as create type placed in cDistance and create minFcn
	// define according data type for cDistance in MPI
	const int    nitems = 2;
	int          blocklengths[2] = {1, 1};
	MPI_Datatype types[2] = {MPI_DOUBLE, MPI_DOUBLE};
	MPI_Aint     offsets[2];

	offsets[0] = offsetof(cDistance, signedDistance);
	offsets[1] = offsetof(cDistance, pDistance);

	MPI_Type_create_struct(nitems, blocklengths, offsets, types, &mpi_cDist_type);
	MPI_Type_commit(&mpi_cDist_type);
	// end type definition

	// commit custom mpi reduce operation
	MPI_Op_create((MPI_User_function *) sdfMin, 1, &mpi_sdfmin);
	// end custom reduce operation commit
}

template <typename type, int dim>
void Initializer::initAll(const Grid<type, dim>& gr, Geometry& geom, double*** data_ptr) {

	DM cda;
	Vec gc;
	DMDACoor3d ***coors;

	DMGetCoordinatesLocal(gr.getDA() ,&gc);
	DMGetCoordinateDM(gr.getDA(), &cda);
	DMDAVecGetArray(cda, gc, &coors);

	int       x, y, z, m, n, p;
	int       cellCounter = 0, gh = 1;

	DMDAGetGhostCorners(gr.getDA(), &x, &y, &z, &m, &n, &p);
	double data_ref = 0.0, data_acc = 0.0;
	for (int i = x; i < x+m; ++i) {
		for (int j = y; j < y+n; ++j) {
			for (int k = z; k < z+p; ++k) {
				data_acc =
						geom.computeDistance(
							Eigen::Vector3d(coors[k][j][i].x,
											coors[k][j][i].y,
											coors[k][j][i].z),
							true
						);

				data_ref = geom.computeDistance(
							Eigen::Vector3d(coors[k][j][i].x,
											coors[k][j][i].y,
											coors[k][j][i].z),
							false
						);

			}
		}
	}

}

template <typename type, int dim>
void Initializer::initBoundaries_nlb(const Grid<type, dim>& gr, Geometry& geom, double*** data_ptr, int boundaries) {

	DM cda;
	Vec gc;
	DMDACoor3d ***coors;

	DMGetCoordinatesLocal(gr.getDA() ,&gc);
	DMGetCoordinateDM(gr.getDA(), &cda);
	DMDAVecGetArray(cda, gc, &coors);

	int       x, y, z, m, n, p;
	int       cellCounter = 0, gh = 1;

	DMDAGetGhostCorners(gr.getDA(), &x, &y, &z, &m, &n, &p);

	int tmpSum = 0;
	if ( boundaries & 1 ) {
		tmpSum+= gh*n*p;
		// this means lets initialize left side
		for (int i = x; i < x+gh; ++i) {
			for (int j = y; j < y+n; ++j) {
				for (int k = z; k < z+p; ++k) {
					data_ptr[k][j][i] =
							geom.computeDistance(
								Eigen::Vector3d(coors[k][j][i].x,
												coors[k][j][i].y,
												coors[k][j][i].z),
								true
							);
					cellCounter++;
				}
			}
		}
	}
	if ( boundaries & 2 ) {
		tmpSum += gh*n*p;
		// this means lets initialize right side
		for (int i = x+m-1; i > x+m-1-gh; --i) {
			for (int j = y; j < y+n; ++j) {
				for (int k = z; k < z+p; ++k) {
					data_ptr[k][j][i] =
							geom.computeDistance(
								Eigen::Vector3d(coors[k][j][i].x,
												coors[k][j][i].y,
												coors[k][j][i].z),
								true
							);
					cellCounter++;
				}
			}
		}
	}
	if ( boundaries & 4 ) {
		tmpSum += gh*m*p;
		// this means initialize the bottom side
		for (int j = y; j < y+gh; ++j) {
			for (int i = x; i < x+m; ++i) {
				for (int k = z; k < z+p; ++k) {
					data_ptr[k][j][i] =
							geom.computeDistance(
								Eigen::Vector3d(coors[k][j][i].x,
												coors[k][j][i].y,
												coors[k][j][i].z),
								true
							);
					cellCounter++;
				}
			}
		}
	}
	if ( boundaries & 8 ) {
		tmpSum += gh*m*p;
		// this means initialize the top side
		for (int j = y+n-1; j > y+n-1-gh; --j) {
			for (int i = x; i < x+m; ++i) {
				for (int k = z; k < z+p; ++k) {
					data_ptr[k][j][i] =
							geom.computeDistance(
								Eigen::Vector3d(coors[k][j][i].x,
												coors[k][j][i].y,
												coors[k][j][i].z),
								true
							);
					cellCounter++;
				}
			}
		}
	}
	if ( boundaries & 16 ) {
		tmpSum += gh*m*n;
		// this means initialize the front side
		for (int k = z; k < z+gh; ++k) {
			for (int i = x; i < x+m; ++i) {
				for (int j = y; j < y+n; ++j) {
					data_ptr[k][j][i] =
							geom.computeDistance(
								Eigen::Vector3d(coors[k][j][i].x,
												coors[k][j][i].y,
												coors[k][j][i].z),
								true
							);
					cellCounter++;
				}
			}
		}
	}
	if ( boundaries & 32 ) {
		tmpSum += gh*m*n;
		// this means initialize the far side
		for (int k = z+p-1; k > z+p-1-gh; --k) {
			for (int i = x; i < x+m; ++i) {
				for (int j = y; j < y+n; ++j) {
					data_ptr[k][j][i] =
							geom.computeDistance(
								Eigen::Vector3d(coors[k][j][i].x,
												coors[k][j][i].y,
												coors[k][j][i].z),
								true
							);
					cellCounter++;
				}
			}
		}
	}

}

/*
template <typename type, int dim>
void Initializer::initBoundaries_lb(const Grid<type, dim>& gr,
		Geometry& geom,
		double*** data_ptr,
		int boundaries,
		int groupID, int nGroups) {

	tictoc tick;
	int myWorldRank, nProcsInWorld;
	int myBegin, myEnd;   // local indices start, local indices stop
	int myBoundaries;
	MPI_Comm_rank(MPI_COMM_WORLD, &myWorldRank);
	MPI_Comm_size(MPI_COMM_WORLD, &nProcsInWorld);

	tick.tic();
	// prepare memory to store the list of all boundary cells
	std::vector<Eigen::Vector3d, Eigen::aligned_allocator<Eigen::Vector3d> > boundaryCellList;
	boundaryCellList.reserve( nProcsInWorld * 4 * gr.getM(0)*gr.getM(1) );

	// this is where the results go (final results)
	std::vector<cDistance> vDistance;
	// create the fucking list
	for (int p = 0; p < nProcsInWorld; ++p) {
		// loop through the processes and get the proc bounding box
		Box<double, 3> procBB    = gr.getNodeSpan(p);
		int boundariesToInit     = selectBoundaries(geom.aabb, procBB);

		std::vector<Eigen::Vector3d, Eigen::aligned_allocator<Eigen::Vector3d> > l =
				gr.getNodeBoundaryCells(p, boundariesToInit);

		// ok I need to remember what part of the boundary is mine, otherwise I am screwed
		if (myWorldRank == p) {
			myBegin      = boundaryCellList.size();
			myEnd        = myBegin + l.size();
			myBoundaries = boundariesToInit;
		}

		boundaryCellList.insert( boundaryCellList.end(), l.begin(), l.end() );
	}

	tick.toc(myWorldRank, MAKECELLLIST, "Create boundary list");

	// number of cells in the boundary list for the WHOLE FUCKING BOUNDARY
	int nBCells = boundaryCellList.size();
	std::cout << "ncells: " << nBCells << std::endl;
	tick.tic();
	// loadbalance the problem.
	// first create groups for the problem
	MPI_Comm groupCommunicator = MPI_COMM_WORLD;
	int nProcsInGroup;
	int myGroupRank;
	if ( nGroups > 1 ) {
		// but if the number of groups is larger than one we need to create the right communicator.
		// luckily the group assigment is already sorted in setup phase.
		MPI_Comm_split(MPI_COMM_WORLD, groupID, myWorldRank, &groupCommunicator);
	}
	MPI_Comm_rank(groupCommunicator, &myGroupRank);
	MPI_Comm_size(groupCommunicator, &nProcsInGroup);
	// now each group will split its workload of boundary cells and start doing stuff

	int numberOfCellsPerTask     = geom.getNumberOfCellsPerDomainLocal();
	assert( numberOfCellsPerTask >= 0 );

	int max_numberOfCellsPerTask = -std::numeric_limits<int>::max();
	size_t totalCellsToFillTheGaps  = 0;
	int exchangedNumberOfCellsPerTask[nProcsInGroup];

	MPI_Allgather(&numberOfCellsPerTask, 1, MPI_INT, exchangedNumberOfCellsPerTask, nProcsInGroup, MPI_INT, groupCommunicator);

	max_numberOfCellsPerTask = *std::max_element(exchangedNumberOfCellsPerTask, exchangedNumberOfCellsPerTask + nProcsInGroup);
	assert( max_numberOfCellsPerTask >= 0 ); // maximum should not be negative

	double diffs[nProcsInGroup];// diffs is the number of cells going to be added as balance for
	for (int p = 0 ; p < nProcsInGroup; ++p) {
		diffs[p] = max_numberOfCellsPerTask - exchangedNumberOfCellsPerTask[p];
		assert( diffs[p] >= 0 );
		totalCellsToFillTheGaps += size_t(max_numberOfCellsPerTask - exchangedNumberOfCellsPerTask[p]);
		 if (myWorldRank == 0)
			 std::cout << "DIFFS : " << (size_t)diffs[p] << "; MAX : " << max_numberOfCellsPerTask << "; EXCHANGED: " << exchangedNumberOfCellsPerTask[p] << std::endl;
	}
	if (myWorldRank == 0)
		 std::cout << "SUM: " << totalCellsToFillTheGaps << std::endl;
	size_t tmpSum = 0; double pSum = 0.0;

	if ( totalCellsToFillTheGaps > 0 ) {
		for (int p = 0 ; p < nProcsInGroup; ++p) {
			pSum += diffs[p]/double(totalCellsToFillTheGaps);
			tmpSum += size_t(diffs[p]);

			 if (myWorldRank == 0)
				 std::cout << "PROC: " << p << " DIFF: " << double(diffs[p])  << " / " << double(totalCellsToFillTheGaps) << " - " << diffs[p]/double(totalCellsToFillTheGaps) << " - " << pSum
				 << " - " << tmpSum << std::endl;

			diffs[p] = double(diffs[p]) / double(totalCellsToFillTheGaps);
		}
		 if (myWorldRank == 0)
			 std::cout << "TMPSUM: " << tmpSum << std::endl;
		assert( tmpSum == totalCellsToFillTheGaps ); // the percentage is not allowed to be greater then 100%
	}

	int theRest = nBCells - totalCellsToFillTheGaps;
	if ( theRest < 0 ) {
		// to make sure that assigment of negative shit is not happening
		theRest                 = 0;
		totalCellsToFillTheGaps = nBCells;
	}

	int start = 0, finish = 0;
	int send_offsets[nProcsInGroup];
	int send_lens[nProcsInGroup];
	int debugSum = 0;

	for (int p = 0; p < nProcsInGroup; ++p) {
		start  = finish;

		finish = (p == nProcsInGroup-1) ? nBCells : start + int( diffs[p]*double(totalCellsToFillTheGaps) ) + int( double(theRest)/double(nProcsInGroup) );

		send_offsets[p] = start;
		send_lens[p]    = finish - start;
		debugSum += send_lens[p];
	}
	tick.toc(myWorldRank, SETUPGROUPS, "Setup groups");
	assert( debugSum <= nBCells );
	assert( send_lens[nProcsInGroup-1] + send_offsets[nProcsInGroup-1] == nBCells );

	tick.tic();
	// std::cout << "send_offsets = " << send_offsets[myGroupRank] << ", send_lens = " << send_lens[myGroupRank] << ", nBCells = " << nBCells << std::endl;
	assert( send_lens[myGroupRank] >= 0 );
	assert( send_offsets[myGroupRank] >= 0 );
	assert( (send_offsets[myGroupRank] + send_lens[myGroupRank]) <= nBCells );
//	std::vector<cDistance> localDistances; localDistances.reserve( send_lens[myGroupRank] );
	cDistance localDistances[ send_lens[myGroupRank] ];
	// compute the distance within the group

	std::vector<Eigen::Vector3d, Eigen::aligned_allocator<Eigen::Vector3d> >::const_iterator begin_range = boundaryCellList.begin() + send_offsets[myGroupRank];
	std::vector<Eigen::Vector3d, Eigen::aligned_allocator<Eigen::Vector3d> >::const_iterator end_range = boundaryCellList.begin() + send_offsets[myGroupRank] + send_lens[myGroupRank];
	std::vector<Eigen::Vector3d, Eigen::aligned_allocator<Eigen::Vector3d> > subList(begin_range, end_range);
	std::cout << "allocated for locality check" << std::endl;

	geom.computeDistance(subList, localDistances);
	tick.toc(myWorldRank, COMPUTEDIST, "Compute bval dist");

	boundaryCellList.clear();

	// put data together. That means call allgatherv and synchronize the shit.

	tick.tic();
	assert( nBCells > 0 );
	vDistance.resize( nBCells );
	MPI_Allgatherv(&localDistances[0], send_lens[myGroupRank], mpi_cDist_type,  &vDistance[0], send_lens, send_offsets, mpi_cDist_type, groupCommunicator);

	tick.toc(myWorldRank, ALLGATHER, "Allgatherv vector");
	// now the data is placed together in one nice vector
	std::cout << "dumping vDistance: " << std::endl;
	for (auto d: vDistance) {
		std::cout << d.signedDistance << ", ";
	}
	std::cout << std::endl;

	// synchronize the groups by reduce operation. This is only needed when you have more then one group

	tick.tic();
	if ( nGroups > 1 ) {
//		mpi::all_reduce(world, &vDistance[0], nBCells, &vDistance[0], sdfMinimum<cDistance>());
		MPI_Allreduce(&vDistance[0], &vDistance[0], nBCells, mpi_cDist_type, mpi_sdfmin, MPI_COMM_WORLD);
	}
	tick.toc(myWorldRank, REDUCE, "Reduce between groups");

	tick.tic();
	// step THREE put into the grid
	assert( myBegin >= 0 );
	assert( (myEnd-myBegin) < nBCells );
	putDataToBoundaries(gr, data_ptr, &vDistance[myBegin], (myEnd-myBegin), myBoundaries);

	vDistance.clear();
	tick.toc(myWorldRank, PUTTOBOUNDARIES, "Place to boundaries");

}
*/

template <typename type, int dim>
void Initializer::operator ()(Geometry& geom, Interface<type, dim>& interface, int groupID, int nGroups) {

	std::vector<Eigen::Vector3d, Eigen::aligned_allocator<Eigen::Vector3d> > boundaryCells;    // xyz coordinated to compute the distance to

	int                        myBoundaries;     // local boundaries to init

	MPI_Group worldGroup;
	MPI_Comm_group(MPI_COMM_WORLD, &worldGroup);
	// perpendicular distance computed towards the triangle, it is used when we are no longer sure, which normal shall we use

	double*** perpendicualDistance;

	// initialize the data

	Vec       localData = interface.getLocalData();
	double*** data_ptr;
	int       x, y, z, m, n, p;
	int       vecSize;

	VecGetLocalSize(localData, &vecSize);
	assert( vecSize > 0 );

	int myWorldRank;
	int nProcsInWorld;

	MPI_Comm_rank(MPI_COMM_WORLD, &myWorldRank);
	MPI_Comm_size(MPI_COMM_WORLD, &nProcsInWorld);

	const Grid<type, dim>& gr = interface.getGrid();

	DMDAVecGetArray(gr.getDA(), localData, &data_ptr);
	DMDAGetArray(gr.getDA(), PETSC_TRUE, &perpendicualDistance);
	DMDAGetGhostCorners(gr.getDA(), &x, &y, &z, &m, &n, &p);

	assert( vecSize == m*n*p );

	for (int k = z, pk = 0; k < z+p; ++k, ++pk) {
		for (int j = y, pj = 0; j < y+n; ++j, ++pj) {
			for (int i = x, pi = 0; i < x+m; ++i, ++pi) {
				data_ptr[k][j][i]             =  std::numeric_limits<double>::max();
				perpendicualDistance[k][j][i] = -std::numeric_limits<double>::max();
			}
		}
	}

	// step FIVE compute triangle distance inside the narrowband

	putLocalDataInside(gr, data_ptr, geom.localElements, geom, perpendicualDistance);

	DMDARestoreArray(gr.getDA(), PETSC_TRUE, &perpendicualDistance);

	// =====================================================================================================

	if ( nProcsInWorld == 1 ) {
//		DMDAGetGhostCorners(gr.getDA(), &x, &y, &z, &m, &n, &p);
//		for (int k = z; k < z+p; ++k) {
//			for (int j = y; j < y+n; ++j) {
//		 		for (int i = x; i < x+m; ++i) {
//		 			if ( data_ptr[k][j][i] >= 10E+5 )
//		 				data_ptr[k][j][i] = -10;
//				}
//			}
//		}
		DMDAVecRestoreArray(gr.getDA(), localData, &data_ptr);
		return;
	}

	int boundariesToInit = selectBoundaries(geom.aabb, gr.getNodeSpan(myWorldRank));
	initBoundaries_nlb(gr, geom, data_ptr, boundariesToInit);
	// initAll(gr, geom, data_ptr);

//=======================================================================================
//	  DMDAGetGhostCorners(gr.getDA(), &x, &y, &z, &m, &n, &p);
//	  for (int k = z; k < z+p; ++k) {
//	  	for (int j = y; j < y+n; ++j) {
//	  		for (int i = x; i < x+m; ++i) {
//	  			if ( data_ptr[k][j][i] >= 10E+5 ) {
//	  				data_ptr[k][j][i] = -10;
//	  			}
//	  		}
//	  	}
//	  }
//=======================================================================================

	DMDAVecRestoreArray(gr.getDA(), localData, &data_ptr);

}

int Initializer::selectBoundaries(const Box<double, 3>& geometryAABB, const Box<double, 3>& procAABB) {
	// chooses which boundaries of the domain have to be initialized
	// ordering : [left, right, bottom, top, front, back]

	int selectedBoundaries_int = 0;

	for (int i = 0; i < 3; ++i) {
		//gr->getLocalGhostedMax(i) <= dataProvider->getMaxX(i)
		if ( procAABB.maxX(i) <= geometryAABB.maxX(i) ) {
			selectedBoundaries_int |= ( 1 << (i*2 + 1) );
		}
		// gr->getLocalGhostedMin(i) >= dataProvider->getMinX(i)
		if ( procAABB.minX(i) >= geometryAABB.minX(i) ) {
			selectedBoundaries_int |= ( 1 << (i*2) );
		}
	}

	return selectedBoundaries_int;

}

template <typename type, int dim>
void Initializer::putDataToBoundaries(const Grid<type, dim>& gr, double*** data_ptr, cDistance* distanceData, int cellNum, int boundaries) {

	int       x, y, z, m, n, p;
	int       cellCounter = 0, gh = 1;

	DMDAGetGhostCorners(gr.getDA(), &x, &y, &z, &m, &n, &p);
//	DMDAGetCorners(gr.getDA(), &x, &y, &z, &m, &n, &p);
	int tmpSum = 0;
	if ( boundaries & 1 ) {
		tmpSum+= gh*n*p;
		// this means lets initialize left side
		for (int i = x; i < x+gh; ++i) {
			for (int j = y; j < y+n; ++j) {
				for (int k = z; k < z+p; ++k) {
					data_ptr[k][j][i+1] = distanceData[cellCounter].signedDistance;
					cellCounter++;
				}
			}
		}
	}
	if ( boundaries & 2 ) {
		tmpSum += gh*n*p;
		// this means lets initialize right side
		for (int i = x+m-1; i > x+m-1-gh; --i) {
			for (int j = y; j < y+n; ++j) {
				for (int k = z; k < z+p; ++k) {
					data_ptr[k][j][i-1] = distanceData[cellCounter].signedDistance;
					cellCounter++;
				}
			}
		}
	}
	if ( boundaries & 4 ) {
		tmpSum += gh*m*p;
		// this means initialize the bottom side
		for (int j = y; j < y+gh; ++j) {
			for (int i = x; i < x+m; ++i) {
				for (int k = z; k < z+p; ++k) {
					data_ptr[k][j+1][i] = distanceData[cellCounter].signedDistance;
					cellCounter++;
				}
			}
		}
	}
	if ( boundaries & 8 ) {
		tmpSum += gh*m*p;
		// this means initialize the top side
		for (int j = y+n-1; j > y+n-1-gh; --j) {
			for (int i = x; i < x+m; ++i) {
				for (int k = z; k < z+p; ++k) {
					data_ptr[k][j-1][i] = distanceData[cellCounter].signedDistance;
					cellCounter++;
				}
			}
		}
	}
	if ( boundaries & 16 ) {
		tmpSum += gh*m*n;
		// this means initialize the front side
		for (int k = z; k < z+gh; ++k) {
			for (int i = x; i < x+m; ++i) {
				for (int j = y; j < y+n; ++j) {
					data_ptr[k+1][j][i] = distanceData[cellCounter].signedDistance;
					cellCounter++;
				}
			}
		}
	}
	if ( boundaries & 32 ) {
		tmpSum += gh*m*n;
		// this means initialize the far side
		for (int k = z+p-1; k > z+p-1-gh; --k) {
			for (int i = x; i < x+m; ++i) {
				for (int j = y; j < y+n; ++j) {
					data_ptr[k-1][j][i] = distanceData[cellCounter].signedDistance;
					cellCounter++;
				}
			}
		}
	}

	assert( cellNum == tmpSum );

}

template <typename type, int dim>
void Initializer::putLocalDataInside(const Grid<type, dim>& gr, double*** data_ptr, const std::vector<int>& localTriangles, const Geometry& geom, double*** perpendicualDistance) {
	int       x, y, z, m, n, p;
	double    narrowBand = gr.getDx(0) * 3;
	double    minDX      = Eigen::Array3d(gr.getDx(0), gr.getDx(1), gr.getDx(2)).minCoeff();
	double    eps        = my_eps;

	DMDAGetGhostCorners(gr.getDA(), &x, &y, &z, &m, &n, &p);

//	 std::cout << "number of triangles = " << localTriangles.size() << std::endl;
//	 for (auto i: localTriangles) {
//		 std::cout << i << ", ";
//	 }
//	 std::cout << std::endl;

	if ( localTriangles.size() == 0 )
		return;

	for (auto index: localTriangles) {
		TriangleElement<double> currentElement = geom.getElement(index);

//		std::cout << currentElement << std::endl;
//		std::cout << currentElement.getBoundingBox() << std::endl;

		Box<int, 3> ind = gr.getGlobalBoxIndices( currentElement.getBoundingBox() );

//		std::cout << ind << std::endl;

		auto bl = ind.bl();
		auto tr = ind.tr();

		for ( int k = bl[2]; k <= tr[2]; ++k ) {

			if ( k < z || k >= (z+p) )
				continue;

			for ( int j = bl[1]; j <= tr[1]; ++j ) {

				if ( j < y || j >= (y+n) )
					continue;

				for ( int i = bl[0]; i <= tr[0]; ++i ) {

					if ( i < x || i >= (x+m) )
						continue;

					Eigen::Vector3d        p       = gr.getCoord( Eigen::Vector3i(i,j,k) );

					SignedDistance<double> v       = TriangleElement<double>::computeDistance(currentElement, p);
					double                 pd      = TriangleElement<double>::computePerpendicularDistance(currentElement, p);

					if ( v.dist > narrowBand ) {
						continue;
					}

					if ( v.dist < eps ) {

						data_ptr[k][j][i]             = eps * v.sign;
						perpendicualDistance[k][j][i] = eps;

					}  else if ( fabs( v.dist - fabs(data_ptr[k][j][i]) ) < eps ) {

						// equal not funny
						if ( perpendicualDistance[k][j][i] <= pd ) {
							data_ptr[k][j][i]             = v.sign * v.dist;
							perpendicualDistance[k][j][i] = pd;
						}

					} else if ( v.dist < fabs(data_ptr[k][j][i]) ) {

						// less so ok than
						data_ptr[k][j][i]             = v.sign * v.dist;
						perpendicualDistance[k][j][i] = pd;
					}

				}
			}
		}

	}

}

#endif


/*
 * L2RightHandSide.cc
 *
 *  Created on: 29.06.2017
 *      Author: thies
 */

#include <deal.II/base/exceptions.h>
#include <deal.II/base/work_stream.h>
#include <deal.II/fe/fe_update_flags.h>
#include <deal.II/numerics/vector_tools.h>

#include <base/DiscretizedFunction.h>
#include <forward/L2RightHandSide.h>

#include <functional>

namespace wavepi {
namespace forward {
using namespace dealii;
using namespace wavepi::base;

template <int dim>
L2RightHandSide<dim>::L2RightHandSide(std::shared_ptr<Function<dim>> f) : base_rhs(f) {}

template <int dim>
L2RightHandSide<dim>::AssemblyScratchData::AssemblyScratchData(const FiniteElement<dim> &fe,
                                                               const Quadrature<dim> &quad)
    : fe_values(fe, quad, update_values | update_quadrature_points | update_JxW_values) {}

template <int dim>
L2RightHandSide<dim>::AssemblyScratchData::AssemblyScratchData(const AssemblyScratchData &scratch_data)
    : fe_values(scratch_data.fe_values.get_fe(), scratch_data.fe_values.get_quadrature(),
                update_values | update_quadrature_points | update_JxW_values) {}

template <int dim>
void L2RightHandSide<dim>::copy_local_to_global(Vector<double> &result, const AssemblyCopyData &copy_data) {
  for (unsigned int i = 0; i < copy_data.local_dof_indices.size(); ++i)
    result(copy_data.local_dof_indices[i]) += copy_data.cell_rhs(i);
}

template <int dim>
std::shared_ptr<Function<dim>> L2RightHandSide<dim>::get_base_rhs() const {
  return base_rhs;
}

template <int dim>
void L2RightHandSide<dim>::set_base_rhs(std::shared_ptr<Function<dim>> base_rhs) {
  this->base_rhs = base_rhs;
}

template <int dim>
void L2RightHandSide<dim>::local_assemble(const Vector<double> &f,
                                          const typename DoFHandler<dim>::active_cell_iterator &cell,
                                          AssemblyScratchData &scratch_data, AssemblyCopyData &copy_data) {
  const unsigned int dofs_per_cell = scratch_data.fe_values.get_fe().dofs_per_cell;
  const unsigned int n_q_points    = scratch_data.fe_values.get_quadrature().size();

  copy_data.cell_rhs.reinit(dofs_per_cell);
  copy_data.local_dof_indices.resize(dofs_per_cell);
  scratch_data.fe_values.reinit(cell);

  cell->get_dof_indices(copy_data.local_dof_indices);

  for (unsigned int q_point = 0; q_point < n_q_points; ++q_point)
    for (unsigned int i = 0; i < dofs_per_cell; ++i)
      for (unsigned int k = 0; k < dofs_per_cell; ++k)
        copy_data.cell_rhs(i) += f[copy_data.local_dof_indices[k]] * scratch_data.fe_values.shape_value(k, q_point) *
                                 scratch_data.fe_values.shape_value(i, q_point) * scratch_data.fe_values.JxW(q_point);
}

template <int dim>
void L2RightHandSide<dim>::create_right_hand_side(const DoFHandler<dim> &dof, const Quadrature<dim> &quad,
                                                  Vector<double> &rhs) const {
  AssertThrow(base_rhs, ExcInternalError());
  base_rhs->set_time(this->get_time());

  auto base_rhs_d = std::dynamic_pointer_cast<DiscretizedFunction<dim>>(base_rhs);

  if (base_rhs_d) {
    Vector<double> coeffs = base_rhs_d->get_function_coefficients(base_rhs_d->get_time_index());
    Assert(coeffs.size() == dof.n_dofs(), ExcDimensionMismatch(coeffs.size(), dof.n_dofs()));

    WorkStream::run(dof.begin_active(), dof.end(),
                    std::bind(&L2RightHandSide<dim>::local_assemble, *this, std::ref(coeffs), std::placeholders::_1,
                              std::placeholders::_2, std::placeholders::_3),
                    std::bind(&L2RightHandSide<dim>::copy_local_to_global, *this, std::ref(rhs), std::placeholders::_1),
                    AssemblyScratchData(dof.get_fe(), quad), AssemblyCopyData());
  } else
    VectorTools::create_right_hand_side(dof, quad, *base_rhs.get(), rhs);
}

template class L2RightHandSide<1>;
template class L2RightHandSide<2>;
template class L2RightHandSide<3>;

} /* namespace forward */
} /* namespace wavepi */