//
// Created by Zhongshi Jiang on 4/11/17.
//

#include <Eigen/Core>
#include <Eigen/StdVector>
#include "tetrahedron_tetrahedron_adjacency.h"
#include "tetrahedron_tuple.h"
#include "retain_tetrahedral_adjacency.h"
#include "edge_removal.h"

#include <iostream>
#include <tuple>
#include <set>
#include <algorithm>
#include <map>
#include <list>
#include <iterator>
namespace igl {
namespace dev {

struct face_removal_neighbor_test {
  template<typename DerivedT, typename DerivedTT,
           typename DerivedTTif, typename DerivedTTie>
  std::tuple<double, double, std::list<int>, std::list<std::tuple<int, int>>>
  recurse(int ti, int fi, int ei, bool ai,
          std::vector<DerivedT> &T,
          std::vector<DerivedTT> &TT,
          std::vector<DerivedTTif> &TTif,
          std::vector<DerivedTTie> &TTie,
          int a, int b,
          const std::function<double(int, int,
                                     int, int)> &tet_quality,
          const std::function<bool(int, int,
                                   int, int)> &orient3D) {
    using namespace igl::dev;

    auto u = tet_tuple_get_vert(ti, fi, ei, ai, T, TT, TTif, TTie);
    auto w = tet_tuple_get_vert(ti, fi, ei, !ai, T, TT, TTif, TTie);

    auto q_uw = tet_quality(a, b, u, w);

    // revolve around the edge : count one-ring tet and decide non-boundary.
    bool flag = true;
    int revolving_face_num = 0;
    std::tuple<int, int, int> other_sandwiched_face;
    int f = fi, t = ti, e = ei;
    do {
      // swtch t/f
      if (tet_tuple_is_on_boundary(t, f, e, true, T, TT, TTif, TTie)) {
        flag = false;
        break;
      }
      std::tie(t, f, e) = std::make_tuple(TT[t][f], TTif[t][f],
                                          TTie[t](f, e));
      f = igl::dev::tetrahedron_local_FF(f, (e + 2) % 3);

      revolving_face_num++;
      if (revolving_face_num == 2) {
        other_sandwiched_face = std::make_tuple(t, f, e);
      }
      if (revolving_face_num > 4) {
        flag = false;
        break;
      }
    } while (t != ti);

    if(revolving_face_num != 4) flag = false;

    if (flag) {
      std::tie(t, f, e) = other_sandwiched_face;
      int v = tet_tuple_get_vert(t, f, (e + 2) % 3, true, T, TT, TTif, TTie);

      auto j_uv = orient3D(a, b, u, v);
      auto j_vw = orient3D(a, b, v, w);
      auto j_wu = orient3D(a, b, w, u);

      double o_uv, n_uv, o_vw, n_vw;

      std::list<int> h_uv, h_vw;
      std::list<std::tuple<int, int>> dt_uv, dt_vw;
      if ((int) j_uv + (int) j_vw + (int) j_wu >= 2) {
        std::tie(o_uv, n_uv, h_uv, dt_uv) =
            recurse(t, f, (e + 1) % 3, false,
                    T, TT, TTif, TTie,
                    a, b, tet_quality, orient3D);
        std::tie(o_vw, n_vw, h_vw, dt_vw) =
            recurse(t, f, (e + 2) % 3, false,
                    T, TT, TTif, TTie,
                    a, b, tet_quality, orient3D);

        auto q_old = (std::min)({tet_quality(a, u, v, w), tet_quality(u, v, w,
                                                                    b),
                               o_uv, o_vw});
        auto q_new = (std::min)(n_uv, n_vw);

        if (q_new > q_old || q_new > q_uw) {
//        one_ring_of_removal.insert(current_pos, v);
          h_uv.push_back(v);
          h_uv.splice(h_uv.end(), h_vw);
          dt_uv.emplace_back(t, f);
          dt_uv.splice(dt_uv.end(), dt_vw);
          return std::make_tuple(q_old, q_new, h_uv, dt_uv);
        }
      }
    }

    return std::make_tuple(INFINITY, q_uw, std::list<int>(),
                           std::list<std::tuple<int, int>>());
  };
};

template<typename DerivedT, typename DerivedTT,
         typename DerivedTTif, typename DerivedTTie>
bool tet_tuple_multi_face_removal(int ti,
                                  int fi,
                                  int ei,
                                  bool ai,
                                  std::function<double(int, int,
                                                       int, int)> tet_quality,
                                  std::function<bool(int, int,
                                                     int, int)> orient3D,
                                  std::vector<DerivedT> &T,
                                  std::vector<DerivedTT> &TT,
                                  std::vector<DerivedTTif> &TTif,
                                  std::vector<DerivedTTie> &TTie,
                                  std::vector<int> &new_tets_id) {

  if (igl::dev::tet_tuple_is_on_boundary(ti, fi, ei, ai,
                                         T, TT, TTif, TTie))
    return false;

  int apex_a, apex_b;
  double q_old = tet_quality(T[ti](0), T[ti](1), T[ti](2), T[ti](3));
  double q_new = INFINITY;
  {
    // get apex_a: switch f/e/v
    auto t = ti, f = fi, e = ei;
    auto a = true;
    igl::dev::tet_tuple_switch_face(t, f, e, a, T, TT, TTif, TTie);
    igl::dev::tet_tuple_switch_edge(t, f, e, a, T, TT, TTif, TTie);
    apex_a = igl::dev::tet_tuple_get_vert(t, f, e, !a,
                                          T, TT, TTif, TTie);
  }
  {
    auto t = ti, f = fi, e = ei;
    auto a = true;
    igl::dev::tet_tuple_switch_tet(t, f, e, a, T, TT, TTif, TTie);
    igl::dev::tet_tuple_switch_face(t, f, e, a, T, TT, TTif, TTie);
    igl::dev::tet_tuple_switch_edge(t, f, e, a, T, TT, TTif, TTie);
    apex_b = igl::dev::tet_tuple_get_vert(t, f, e, !a,
                                          T, TT, TTif, TTie);
    q_old = (std::min)(q_old, tet_quality(T[t](0), T[t](1), T[t](2), T[t](3)));
  }

  assert(q_old > 0);

  face_removal_neighbor_test neighbor_test;
  std::list<int> polygon;
  std::list<std::tuple<int, int>> delete_faces{std::make_tuple(ti, fi)};;
  for (auto e:{0, 1, 2}) {
    std::list<int> added_points;
    decltype(delete_faces) local_del_faces;
    double local_q_old, local_q_new;
    polygon.push_back(igl::dev::tet_tuple_get_vert(ti, fi, e, ai,
                                                   T, TT, TTif, TTie));
    std::tie(local_q_old, local_q_new, added_points, local_del_faces) =
        neighbor_test.recurse(ti, fi, e, ai, T, TT, TTif, TTie, apex_a, apex_b,
                              tet_quality, orient3D);

    q_old = (std::min)(q_old, local_q_old);
    q_new = (std::min)(q_new, local_q_new);
    polygon.splice(polygon.end(), added_points);
    delete_faces.splice(delete_faces.end(), local_del_faces);
  }
  if (q_new <= q_old)
    return false;
  if (q_new < 0) {
//    std::cout<<"Warning q_new < 0 in Multiface removal"<<std::endl;
    return false;
  }
  // delete those sandwiched between a/b
  std::set<int> delete_tets;
  std::set<int> influence_id;
  for (auto tf:delete_faces) {
    int t, f;
    std::tie(t, f) = tf;
    delete_tets.insert(t);
    delete_tets.insert(TT[t][f]);
  }

  for (auto t:delete_tets)
    for (auto f:{0, 1, 2, 3}) influence_id.insert(TT[t][f]);
  influence_id.erase(-1);

  std::vector<Eigen::RowVector4i> new_tets;
  auto p = std::rbegin(polygon);
  auto q = std::next(p);
  for (; q != std::rend(polygon); ++p, ++q) {
    new_tets.emplace_back(apex_a, apex_b, *p, *q);
  }
  if (!new_tets.empty())
    new_tets.emplace_back(apex_a, apex_b, *p, *std::rbegin(polygon));

  std::set<int> surround_id;
  std::set_difference(influence_id.begin(), influence_id.end(),
                      delete_tets.begin(), delete_tets.end(),
                      std::inserter(surround_id, surround_id.begin()));

  // additional procedure for the recomputation of new tets.
  // is actually reusable for retain_tet_adj. But not much.
  new_tets_id.clear();
  new_tets_id.insert(new_tets_id.end(), delete_tets.begin(), delete_tets.end());
  for (int i = 0, loop_num = new_tets.size() - delete_tets.size(); i < loop_num;
       i++)
    new_tets_id.push_back(i + T.size());
  if (delete_tets.size() > new_tets.size()) new_tets_id.resize(new_tets.size());

#ifndef NDEBUG
  {
    double qq = INFINITY;
    for (auto r:new_tets) {
      double new_qq = tet_quality(r[0], r[1], r[2], r[3]);
      if (qq > new_qq)
        qq = new_qq;
    }
    assert(qq == q_new);
  }
  assert(q_new > 0);

#endif
  retain_tetrahedral_adjacency(delete_tets, surround_id, new_tets, T, TT, TTif,
                               TTie);
#ifndef NDEBUG
  {
    double qq = INFINITY;
    for (auto i:new_tets_id) {
      auto r = T[i];
      double new_qq = tet_quality(r[0], r[1], r[2], r[3]);
      if (qq > new_qq)
        qq = new_qq;
    }
    assert(qq == q_new);
  }
#endif
  return true;
};

template<typename DerivedT, typename DerivedTT,
         typename DerivedTTif, typename DerivedTTie>
bool tet_tuple_multi_face_removal_force(int ti,
                                        int fi,
                                        int ei,
                                        bool ai,
                                        std::function<double(int,
                                                             int,
                                                             int,
                                                             int)> tet_quality,
                                        std::function<bool(int, int,
                                                           int, int)> orient3D,
                                        std::vector<DerivedT> &T,
                                        std::vector<DerivedTT> &TT,
                                        std::vector<DerivedTTif> &TTif,
                                        std::vector<DerivedTTie> &TTie) {

  if (igl::dev::tet_tuple_is_on_boundary(ti, fi, ei, ai,
                                         T, TT, TTif, TTie))
    return false;

  int apex_a, apex_b;
  double q_old = tet_quality(T[ti](0), T[ti](1), T[ti](2), T[ti](3));
  double q_new = INFINITY;
  {
    // get apex_a: switch f/e/v
    auto t = ti, f = fi, e = ei;
    auto a = true;
    igl::dev::tet_tuple_switch_face(t, f, e, a, T, TT, TTif, TTie);
    igl::dev::tet_tuple_switch_edge(t, f, e, a, T, TT, TTif, TTie);
    apex_a = igl::dev::tet_tuple_get_vert(t, f, e, !a,
                                          T, TT, TTif, TTie);
  }
  {
    auto t = ti, f = fi, e = ei;
    auto a = true;
    igl::dev::tet_tuple_switch_tet(t, f, e, a, T, TT, TTif, TTie);
    igl::dev::tet_tuple_switch_face(t, f, e, a, T, TT, TTif, TTie);
    igl::dev::tet_tuple_switch_edge(t, f, e, a, T, TT, TTif, TTie);
    apex_b = igl::dev::tet_tuple_get_vert(t, f, e, !a,
                                          T, TT, TTif, TTie);
    q_old = (std::min)(q_old, tet_quality(T[t](0), T[t](1), T[t](2), T[t](3)));
  }

  assert(q_old > 0);

  face_removal_neighbor_test neighbor_test;
  std::list<int> polygon;
  std::list<std::tuple<int, int>> delete_faces{std::make_tuple(ti, fi)};;
  for (auto e:{0, 1, 2}) {
    std::list<int> added_points;
    decltype(delete_faces) local_del_faces;
    double local_q_old, local_q_new;
    polygon.push_back(igl::dev::tet_tuple_get_vert(ti, fi, e, ai,
                                                   T, TT, TTif, TTie));
    std::tie(local_q_old, local_q_new, added_points, local_del_faces) =
        neighbor_test.recurse(ti, fi, e, ai, T, TT, TTif, TTie, apex_a, apex_b,
                              tet_quality, orient3D);

    q_old = (std::min)(q_old, local_q_old);
    q_new = (std::min)(q_new, local_q_new);
    polygon.splice(polygon.end(), added_points);
    delete_faces.splice(delete_faces.end(), local_del_faces);
  }
  // delete those sandwiched between a/b
  std::set<int> delete_tets;
  std::set<int> influence_id;
  for (auto tf:delete_faces) {
    int t, f;
    std::tie(t, f) = tf;
    delete_tets.insert(t);
    delete_tets.insert(TT[t][f]);
  }

  for (auto t:delete_tets)
    for (auto f:{0, 1, 2, 3}) influence_id.insert(TT[t][f]);
  influence_id.erase(-1);

  std::vector<Eigen::RowVector4i> new_tets;
  auto p = std::rbegin(polygon);
  auto q = std::next(p);
  for (; q != std::rend(polygon); ++p, ++q) {
    new_tets.emplace_back(apex_a, apex_b, *p, *q);
  }
  if (!new_tets.empty())
    new_tets.emplace_back(apex_a, apex_b, *p, *std::rbegin(polygon));

  std::set<int> surround_id;
  std::set_difference(influence_id.begin(), influence_id.end(),
                      delete_tets.begin(), delete_tets.end(),
                      std::inserter(surround_id, surround_id.begin()));

#ifndef NDEBUG
  {
    double qq = INFINITY;
    for (auto r:new_tets) {
      double new_qq = tet_quality(r[0], r[1], r[2], r[3]);
      if (qq > new_qq)
        qq = new_qq;
    }
    assert(qq == q_new);
  }
  assert(q_new >= 0);

#endif
  retain_tetrahedral_adjacency(delete_tets, surround_id, new_tets, T, TT, TTif,
                               TTie);
  return true;
};

}
}

#ifdef IGL_STATIC_LIBRARY
template bool igl::dev::tet_tuple_multi_face_removal<Eigen::Matrix<int, 1, 4,
                                                                   1, 1, 4>,
                                                     Eigen::Matrix<int,
                                                                   1,
                                                                   4,
                                                                   1,
                                                                   1,
                                                                   4>,
                                                     Eigen::Matrix<int,
                                                                   1,
                                                                   4,
                                                                   1,
                                                                   1,
                                                                   4>,
                                                     Eigen::Matrix<int,
                                                                   4,
                                                                   3,
                                                                   0,
                                                                   4,
                                                                   3> >(int,
                                                                        int,
                                                                        int,
                                                                        bool,
                                                                        std::function<
                                                                            double(
                                                                                int,
                                                                                int,
                                                                                int,
                                                                                int)>,
                                                                        std::function<
                                                                            bool(
                                                                                int,
                                                                                int,
                                                                                int,
                                                                                int)>,
                                                                        std::vector<
                                                                            Eigen::Matrix<
                                                                                int,
                                                                                1,
                                                                                4,
                                                                                1,
                                                                                1,
                                                                                4>,
                                                                            std::allocator<
                                                                                Eigen::Matrix<
                                                                                int,
                                                                                1,
                                                                                4,
                                                                                1,
                                                                                1,
                                                                                4> > > &,
                                                                        std::vector<
                                                                            Eigen::Matrix<
                                                                                int,
                                                                                1,
                                                                                4,
                                                                                1,
                                                                                1,
                                                                                4>,
                                                                            std::allocator<
                                                                                Eigen::Matrix<
                                                                                int,
                                                                                1,
                                                                                4,
                                                                                1,
                                                                                1,
                                                                                4> > > &,
                                                                        std::vector<
                                                                            Eigen::Matrix<
                                                                                int,
                                                                                1,
                                                                                4,
                                                                                1,
                                                                                1,
                                                                                4>,
                                                                            std::allocator<
                                                                                Eigen::Matrix<
                                                                                int,
                                                                                1,
                                                                                4,
                                                                                1,
                                                                                1,
                                                                                4> > > &,
                                                                        std::vector<
                                                                            Eigen::Matrix<
                                                                                int,
                                                                                4,
                                                                                3,
                                                                                0,
                                                                                4,
                                                                                3>,
                                                                            std::allocator<
                                                                                Eigen::Matrix<
                                                                                int,
                                                                                4,
                                                                                3,
                                                                                0,
                                                                                4,
                                                                                3> > > &,
                                                                        std::vector<
                                                                            int,
                                                                            std::allocator<
                                                                                int> > &);

template bool igl::dev::tet_tuple_multi_face_removal_force<Eigen::Matrix<int,
                                                                         1,
                                                                         4,
                                                                         1,
                                                                         1,
                                                                         4>,
                                                           Eigen::Matrix<int,
                                                                         1,
                                                                         4,
                                                                         1,
                                                                         1,
                                                                         4>,
                                                           Eigen::Matrix<int,
                                                                         1,
                                                                         4,
                                                                         1,
                                                                         1,
                                                                         4>,
                                                           Eigen::Matrix<int,
                                                                         4,
                                                                         3,
                                                                         0,
                                                                         4,
                                                                         3> >(
    int,
    int,
    int,
    bool,
    std::function<double(int, int, int, int)>,
    std::function<bool(int, int, int, int)>,
    std::vector<Eigen::Matrix<int, 1, 4, 1, 1, 4>,
                     std::allocator<Eigen::Matrix<int,
                                                       1,
                                                       4,
                                                       1,
                                                       1,
                                                       4> > > &,
    std::vector<Eigen::Matrix<int, 1, 4, 1, 1, 4>,
                     std::allocator<Eigen::Matrix<int,
                                                       1,
                                                       4,
                                                       1,
                                                       1,
                                                       4> > > &,
    std::vector<Eigen::Matrix<int, 1, 4, 1, 1, 4>,
                     std::allocator<Eigen::Matrix<int,
                                                       1,
                                                       4,
                                                       1,
                                                       1,
                                                       4> > > &,
    std::vector<Eigen::Matrix<int, 4, 3, 0, 4, 3>,
                     std::allocator<Eigen::Matrix<int,
                                                       4,
                                                       3,
                                                       0,
                                                       4,
                                                       3> > > &);

#endif


#pragma once

#include <boost/container/static_vector.hpp>
#include <iostream>
#include <unordered_map>

#include "haar_basis.hpp"
#include "linear_operator.hpp"
#include "orthonormal_basis.hpp"
#include "three_point_basis.hpp"
namespace Time {

/**
 *  Implementations of LinearOperator.
 */
template <typename I, typename BasisIn, typename BasisOut>
SparseVector<BasisOut> LinearOperator<I, BasisIn, BasisOut>::MatVec(
    const SparseVector<BasisIn> &vec) const {
  SparseVector<BasisOut> result;
  result.reserve(vec.size() * 2);

  for (const auto [labda_in, coeff_in] : vec)
    for (const auto [labda_out, coeff_out] : I::Column(labda_in))
      result.emplace_back(labda_out, coeff_in * coeff_out);
  result.Compress();
  return result;
}

template <typename I, typename BasisIn, typename BasisOut>
SparseVector<BasisIn> LinearOperator<I, BasisIn, BasisOut>::RMatVec(
    const SparseVector<BasisOut> &vec) const {
  SparseVector<BasisIn> result;
  result.reserve(vec.size() * 2);

  for (const auto [labda_out, coeff_out] : vec)
    for (const auto [labda_in, coeff_in] : I::Row(labda_out))
      result.emplace_back(labda_in, coeff_out * coeff_in);
  result.Compress();
  return result;
}

template <typename I, typename BasisIn, typename BasisOut>
SparseVector<BasisOut> LinearOperator<I, BasisIn, BasisOut>::MatVec(
    const SparseVector<BasisIn> &vec,
    const SparseIndices<BasisOut> &indices_out) const {
  if (vec.empty() || indices_out.empty()) return {};
  assert(indices_out.IsUnique());
  SparseVector<BasisOut> result;
  result.reserve(indices_out.size());

  vec.StoreInTree();
  for (const auto labda_out : indices_out) {
    double val = 0;
    for (const auto [labda_in, coeff_in] : I::Row(labda_out))
      if (labda_in->has_data())
        val += coeff_in * (*labda_in->template data<double>());
    result.emplace_back(labda_out, val);
  }
  vec.RemoveFromTree();
  return result;
}

template <typename I, typename BasisIn, typename BasisOut>
SparseVector<BasisIn> LinearOperator<I, BasisIn, BasisOut>::RMatVec(
    const SparseVector<BasisOut> &vec,
    const SparseIndices<BasisIn> &indices_in) const {
  if (vec.empty() || indices_in.empty()) return {};
  assert(indices_in.IsUnique());
  SparseVector<BasisIn> result;
  result.reserve(indices_in.size());

  vec.StoreInTree();
  for (const auto labda_in : indices_in) {
    double val = 0;
    for (const auto [labda_out, coeff_out] : I::Column(labda_in))
      if (labda_out->has_data())
        val += coeff_out * (*labda_out->template data<double>());
    result.emplace_back(labda_in, val);
  }
  vec.RemoveFromTree();
  return result;
}

template <typename I, typename BasisIn, typename BasisOut>
SparseIndices<BasisOut> LinearOperator<I, BasisIn, BasisOut>::Range(
    const SparseIndices<BasisIn> &ind) const {
  SparseIndices<BasisOut> result;
  result.reserve(ind.size() * 2);

  for (const auto labda_in : ind)
    for (const auto [labda_out, _] : I::Column(labda_in))
      result.emplace_back(labda_out);
  result.Compress();
  return result;
}

template <typename I, typename BasisIn, typename BasisOut>
Eigen::MatrixXd LinearOperator<I, BasisIn, BasisOut>::ToMatrix(
    const SparseIndices<BasisIn> &indices_in,
    const SparseIndices<BasisOut> &indices_out) const {
  assert(indices_in.IsUnique() && indices_out.IsUnique());
  std::unordered_map<BasisIn *, int> indices_in_map;
  std::unordered_map<BasisOut *, int> indices_out_map;
  Eigen::MatrixXd A =
      Eigen::MatrixXd::Zero(indices_out.size(), indices_in.size());

  for (int i = 0; i < indices_in.size(); ++i) {
    assert(!indices_in_map.count(indices_in[i]));
    indices_in_map[indices_in[i]] = i;
  }
  for (int i = 0; i < indices_out.size(); ++i) {
    assert(!indices_out_map.count(indices_out[i]));
    indices_out_map[indices_out[i]] = i;
  }

  // Create A.
  for (int i = 0; i < indices_in.size(); ++i) {
    SparseVector<BasisIn> vec{{{indices_in[i], 1.0}}};
    auto op_vec = MatVec(vec);
    for (auto [fn, coeff] : op_vec) {
      A(indices_out_map[fn], i) = coeff;
    }
  }

  return A;
}

/**
 *  Below implementations have various return types based. The aliases
 *  below are some handy containers with fixed memory size.
 */
template <typename Basis, size_t N>
using StaticSparseVector =
    boost::container::static_vector<std::pair<Basis *, double>, N>;
template <typename Basis, size_t N>
using ArraySparseVector = std::array<std::pair<Basis *, double>, N>;

/**
 *  Implementations of Prolongate.
 */
template <>
inline auto Prolongate<ContLinearScalingFn>::Column(
    ContLinearScalingFn *phi_in) {
  StaticSparseVector<ContLinearScalingFn, 3> result;

  auto [l, n] = phi_in->labda();
  result.emplace_back(phi_in->RefineMiddle(), 1.0);
  if (n > 0) result.emplace_back(phi_in->RefineLeft(), 0.5);
  if (n < (1LL << l)) result.emplace_back(phi_in->RefineRight(), 0.5);
  return result;
}

template <>
inline auto Prolongate<ContLinearScalingFn>::Row(ContLinearScalingFn *phi_in) {
  StaticSparseVector<ContLinearScalingFn, 2> result;
  const auto &parents = phi_in->parents();
  if (parents.size() == 1)
    result = {{{parents[0], 1.0}}};
  else if (parents.size() == 2)
    result = {{{parents[0], 0.5}, {parents[1], 0.5}}};
  else
    assert(false);
  return result;
}

template <>
inline auto Prolongate<DiscLinearScalingFn>::Column(
    DiscLinearScalingFn *phi_in) {
  StaticSparseVector<DiscLinearScalingFn, 4> result;
  auto [l, n] = phi_in->labda();
  phi_in->Refine();
  const auto &children = phi_in->children();
  if (phi_in->pw_constant()) {
    result.emplace_back(children[0], 1);
    result.emplace_back(children[2], 1);
  } else {
    result.emplace_back(children[0], -sqrt(3) / 2);
    result.emplace_back(children[1], 0.5);
    result.emplace_back(children[2], sqrt(3) / 2);
    result.emplace_back(children[3], 0.5);
  }
  return result;
}

template <>
inline auto Prolongate<DiscLinearScalingFn>::Row(DiscLinearScalingFn *phi_in) {
  StaticSparseVector<DiscLinearScalingFn, 2> result;
  auto [l, n] = phi_in->labda();
  const auto &parents = phi_in->parents();
  switch (n % 4) {
    case 0:
      result = {{{parents[0], 1.0}, {parents[1], -sqrt(3) / 2}}};
      return result;
    case 1:
      result = {{{parents[1], 0.5}}};
      return result;
    case 2:
      result = {{{parents[0], 1.0}, {parents[1], sqrt(3) / 2}}};
      return result;
    case 3:
    default:
      result = {{{parents[1], 0.5}}};
      return result;
  }
}

/**
 *  Implementations of the Mass operator.
 */
template <>
inline auto MassOperator<ContLinearScalingFn, ContLinearScalingFn>::Column(
    ContLinearScalingFn *phi_in) {
  StaticSparseVector<ContLinearScalingFn, 3> result;

  auto [l, n] = phi_in->labda();
  double self_ip = 0;
  if (n > 0) {
    auto elem = phi_in->support()[0];
    result.emplace_back(elem->RefineContLinear()[0], 1. / ((1LL << l) * 6.0));
    self_ip += 1. / ((1LL << l) * 3.0);
  }
  if (n < (1LL << l)) {
    auto elem = phi_in->support().back();
    result.emplace_back(elem->RefineContLinear()[1], 1. / ((1LL << l) * 6.0));
    self_ip += 1. / ((1LL << l) * 3.0);
  }
  result.emplace_back(phi_in, self_ip);
  return result;
}

template <>
inline auto MassOperator<ContLinearScalingFn, ContLinearScalingFn>::Row(
    ContLinearScalingFn *phi_out) {
  // This MassOperator is symmetric.
  return Column(phi_out);
}

template <>
inline auto MassOperator<DiscConstantScalingFn, DiscConstantScalingFn>::Column(
    DiscConstantScalingFn *phi_in) {
  return ArraySparseVector<DiscConstantScalingFn, 1>{
      {{phi_in, 1. / (1LL << phi_in->level())}}};
}

template <>
inline auto MassOperator<DiscConstantScalingFn, DiscConstantScalingFn>::Row(
    DiscConstantScalingFn *phi_out) {
  // This MassOperator is symmetric.
  return Column(phi_out);
}

template <>
inline auto MassOperator<DiscLinearScalingFn, DiscLinearScalingFn>::Column(
    DiscLinearScalingFn *phi_in) {
  return ArraySparseVector<DiscLinearScalingFn, 1>{
      {{phi_in, 1. / (1LL << phi_in->level())}}};
}

template <>
inline auto MassOperator<DiscLinearScalingFn, DiscLinearScalingFn>::Row(
    DiscLinearScalingFn *phi_out) {
  // This MassOperator is symmetric.
  return Column(phi_out);
}

namespace Mass {
inline auto ThreeInOrthoOut(ContLinearScalingFn *phi_in) {
  StaticSparseVector<DiscLinearScalingFn, 4> result;

  auto [l, n] = phi_in->labda();
  const double s = 1. / (1LL << (l + 1));  // s = pow(2, -(l+1)).
  if (n > 0) {
    const auto &[pdl0, pdl1] = phi_in->support().front()->PhiDiscLinear();
    assert(pdl0 != nullptr && pdl1 != nullptr);
    result.emplace_back(pdl0, s);
    result.emplace_back(pdl1, s / sqrt(3));
  }
  if (n < (1LL << l)) {
    const auto &[pdl0, pdl1] = phi_in->support().back()->PhiDiscLinear();
    assert(pdl0 != nullptr && pdl1 != nullptr);
    result.emplace_back(pdl0, s);
    result.emplace_back(pdl1, -s / sqrt(3));
  }
  return result;
}

inline auto OrthoInThreeOut(DiscLinearScalingFn *phi_in) {
  ArraySparseVector<ContLinearScalingFn, 2> result;
  auto [l, n] = phi_in->labda();
  const auto &[pcl0, pcl1] = phi_in->support()[0]->RefineContLinear();
  assert(pcl0 != nullptr && pcl1 != nullptr);
  const double s = 1. / (1LL << (l + 1));  // s = pow(2, -(l+1)).
  if (phi_in->pw_constant())
    result = {{{pcl0, s}, {pcl1, s}}};
  else
    result = {{{pcl0, -s / sqrt(3)}, {pcl1, s / sqrt(3)}}};
  return result;
}
};  // namespace Mass

template <>
inline auto MassOperator<ContLinearScalingFn, DiscLinearScalingFn>::Column(
    ContLinearScalingFn *phi_in) {
  return Mass::ThreeInOrthoOut(phi_in);
}

template <>
inline auto MassOperator<ContLinearScalingFn, DiscLinearScalingFn>::Row(
    DiscLinearScalingFn *phi_out) {
  return Mass::OrthoInThreeOut(phi_out);
}

template <>
inline auto MassOperator<DiscLinearScalingFn, ContLinearScalingFn>::Column(
    DiscLinearScalingFn *phi_in) {
  return Mass::OrthoInThreeOut(phi_in);
}

template <>
inline auto MassOperator<DiscLinearScalingFn, ContLinearScalingFn>::Row(
    ContLinearScalingFn *phi_out) {
  return Mass::ThreeInOrthoOut(phi_out);
}

template <>
inline auto ZeroEvalOperator<ContLinearScalingFn, ContLinearScalingFn>::Column(
    ContLinearScalingFn *phi_in) {
  StaticSparseVector<ContLinearScalingFn, 1> result;
  auto [l, n] = phi_in->labda();
  if (n == 0) result = {{{phi_in, 1.0}}};
  return result;
}

template <>
inline auto ZeroEvalOperator<ContLinearScalingFn, ContLinearScalingFn>::Row(
    ContLinearScalingFn *phi_out) {
  return Column(phi_out);
}

template <>
inline auto ZeroEvalOperator<DiscLinearScalingFn, DiscLinearScalingFn>::Column(
    DiscLinearScalingFn *phi_in) {
  StaticSparseVector<DiscLinearScalingFn, 2> result;
  auto [l, n] = phi_in->labda();
  if (n <= 1) {
    const auto &[pdl0, pdl1] = phi_in->support().front()->PhiDiscLinear();
    assert(pdl0 != nullptr && pdl1 != nullptr);
    if (phi_in->pw_constant())
      result = {{{pdl0, 1.0}, {pdl1, -sqrt(3)}}};
    else
      result = {{{pdl0, -sqrt(3)}, {pdl1, 3.0}}};
  }
  return result;
}

template <>
inline auto ZeroEvalOperator<DiscLinearScalingFn, DiscLinearScalingFn>::Row(
    DiscLinearScalingFn *phi_out) {
  return Column(phi_out);
}

namespace ZeroEval {
inline auto ThreeInOrthoOut(ContLinearScalingFn *phi_in) {
  StaticSparseVector<DiscLinearScalingFn, 2> result;
  auto [l, n] = phi_in->labda();
  if (n == 0) {
    const auto &[pdl0, pdl1] = phi_in->support().front()->PhiDiscLinear();
    assert(pdl0 != nullptr && pdl1 != nullptr);
    result = {{{pdl0, 1.0}, {pdl1, -sqrt(3)}}};
  }
  return result;
}

inline auto OrthoInThreeOut(DiscLinearScalingFn *phi_in) {
  StaticSparseVector<ContLinearScalingFn, 1> result;
  auto [l, n] = phi_in->labda();
  if (n <= 1) {
    const auto &pcl0 = phi_in->support().front()->RefineContLinear()[0];
    assert(pcl0 != nullptr);
    if (phi_in->pw_constant())
      result = {{{pcl0, 1.0}}};
    else
      result = {{{pcl0, -sqrt(3)}}};
  }
  return result;
}
};  // namespace ZeroEval

template <>
inline auto ZeroEvalOperator<ContLinearScalingFn, DiscLinearScalingFn>::Column(
    ContLinearScalingFn *phi_in) {
  return ZeroEval::ThreeInOrthoOut(phi_in);
}

template <>
inline auto ZeroEvalOperator<ContLinearScalingFn, DiscLinearScalingFn>::Row(
    DiscLinearScalingFn *phi_out) {
  return ZeroEval::OrthoInThreeOut(phi_out);
}

template <>
inline auto ZeroEvalOperator<DiscLinearScalingFn, ContLinearScalingFn>::Column(
    DiscLinearScalingFn *phi_in) {
  return ZeroEval::OrthoInThreeOut(phi_in);
}

template <>
inline auto ZeroEvalOperator<DiscLinearScalingFn, ContLinearScalingFn>::Row(
    ContLinearScalingFn *phi_out) {
  return ZeroEval::ThreeInOrthoOut(phi_out);
}

namespace Transport {
inline auto ThreeInOrthoOut(ContLinearScalingFn *phi_in) {
  StaticSparseVector<DiscLinearScalingFn, 2> result;

  auto [l, n] = phi_in->labda();
  if (n > 0) {
    const auto &pdl0 = phi_in->support().front()->PhiDiscLinear()[0];
    assert(pdl0 != nullptr);
    result.emplace_back(pdl0, 1.0);
  }
  if (n < (1LL << l)) {
    const auto &pdl0 = phi_in->support().back()->PhiDiscLinear()[0];
    assert(pdl0 != nullptr);
    result.emplace_back(pdl0, -1.0);
  }
  return result;
}
inline auto OrthoInThreeOut(DiscLinearScalingFn *phi_in) {
  StaticSparseVector<ContLinearScalingFn, 2> result;
  auto [l, n] = phi_in->labda();
  const auto &[pcl0, pcl1] = phi_in->support().front()->RefineContLinear();
  assert(pcl0 != nullptr && pcl1 != nullptr);
  if (phi_in->pw_constant()) result = {{{pcl0, -1.0}, {pcl1, 1.0}}};
  return result;
}
}  // namespace Transport

template <>
inline auto TransportOperator<ContLinearScalingFn, DiscLinearScalingFn>::Column(
    ContLinearScalingFn *phi_in) {
  return Transport::ThreeInOrthoOut(phi_in);
}

template <>
inline auto TransportOperator<ContLinearScalingFn, DiscLinearScalingFn>::Row(
    DiscLinearScalingFn *phi_out) {
  return Transport::OrthoInThreeOut(phi_out);
}

template <>
inline auto TransportOperator<DiscLinearScalingFn, ContLinearScalingFn>::Column(
    DiscLinearScalingFn *phi_in) {
  return Transport::OrthoInThreeOut(phi_in);
}

template <>
inline auto TransportOperator<DiscLinearScalingFn, ContLinearScalingFn>::Row(
    ContLinearScalingFn *phi_out) {
  return Transport::ThreeInOrthoOut(phi_out);
}

}  // namespace Time


// ====================================================================
// This file is part of FlexibleSUSY.
//
// FlexibleSUSY is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// FlexibleSUSY is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with FlexibleSUSY.  If not, see
// <http://www.gnu.org/licenses/>.
// ====================================================================

// File generated at Sat 27 Aug 2016 12:40:14

#ifndef NSM_TWO_SCALE_susy_parameters_H
#define NSM_TWO_SCALE_susy_parameters_H

#include "betafunction.hpp"
#include "NSM_input_parameters.hpp"

#include <iosfwd>
#include <string>
#include <vector>
#include <Eigen/Core>

namespace flexiblesusy {

#ifdef TRACE_STRUCT_TYPE
   #undef TRACE_STRUCT_TYPE
#endif
#define TRACE_STRUCT_TYPE Susy_traces

class NSM_susy_parameters : public Beta_function {
public:
   explicit NSM_susy_parameters(const NSM_input_parameters& input_ = NSM_input_parameters());
   NSM_susy_parameters(double scale_, double loops_, double thresholds_, const NSM_input_parameters& input_, double g1_, double g2_, double g3_, double Lambda2_, double Lambda3_,
   double Lambda1_, const Eigen::Matrix<double,3,3>& Yu_, const Eigen::Matrix<
   double,3,3>& Yd_, const Eigen::Matrix<double,3,3>& Ye_
);
   virtual ~NSM_susy_parameters() {}
   virtual Eigen::ArrayXd beta() const;
   virtual Eigen::ArrayXd get() const;
   virtual void print(std::ostream&) const;
   virtual void set(const Eigen::ArrayXd&);
   const NSM_input_parameters& get_input() const;
   NSM_input_parameters& get_input();
   void set_input_parameters(const NSM_input_parameters&);

   NSM_susy_parameters calc_beta() const;
   virtual void clear();

   void set_g1(double g1_) { g1 = g1_; }
   void set_g2(double g2_) { g2 = g2_; }
   void set_g3(double g3_) { g3 = g3_; }
   void set_Lambda2(double Lambda2_) { Lambda2 = Lambda2_; }
   void set_Lambda3(double Lambda3_) { Lambda3 = Lambda3_; }
   void set_Lambda1(double Lambda1_) { Lambda1 = Lambda1_; }
   void set_Yu(const Eigen::Matrix<double,3,3>& Yu_) { Yu = Yu_; }
   void set_Yu(int i, int k, double value) { Yu(i,k) = value; }
   void set_Yd(const Eigen::Matrix<double,3,3>& Yd_) { Yd = Yd_; }
   void set_Yd(int i, int k, double value) { Yd(i,k) = value; }
   void set_Ye(const Eigen::Matrix<double,3,3>& Ye_) { Ye = Ye_; }
   void set_Ye(int i, int k, double value) { Ye(i,k) = value; }

   double get_g1() const { return g1; }
   double get_g2() const { return g2; }
   double get_g3() const { return g3; }
   double get_Lambda2() const { return Lambda2; }
   double get_Lambda3() const { return Lambda3; }
   double get_Lambda1() const { return Lambda1; }
   const Eigen::Matrix<double,3,3>& get_Yu() const { return Yu; }
   double get_Yu(int i, int k) const { return Yu(i,k); }
   const Eigen::Matrix<double,3,3>& get_Yd() const { return Yd; }
   double get_Yd(int i, int k) const { return Yd(i,k); }
   const Eigen::Matrix<double,3,3>& get_Ye() const { return Ye; }
   double get_Ye(int i, int k) const { return Ye(i,k); }

protected:
   double g1;
   double g2;
   double g3;
   double Lambda2;
   double Lambda3;
   double Lambda1;
   Eigen::Matrix<double,3,3> Yu;
   Eigen::Matrix<double,3,3> Yd;
   Eigen::Matrix<double,3,3> Ye;

   NSM_input_parameters input;

private:
   static const int numberOfParameters = 33;

   struct Susy_traces {
      double traceYdAdjYd;
      double traceYeAdjYe;
      double traceYuAdjYu;
      double traceYdAdjYdYdAdjYd;
      double traceYdAdjYuYuAdjYd;
      double traceYeAdjYeYeAdjYe;
      double traceYuAdjYuYuAdjYu;
      double traceYdAdjYdYdAdjYdYdAdjYd;
      double traceYdAdjYdYdAdjYuYuAdjYd;
      double traceYdAdjYuYuAdjYdYdAdjYd;
      double traceYdAdjYuYuAdjYuYuAdjYd;
      double traceYeAdjYeYeAdjYeYeAdjYe;
      double traceYuAdjYuYuAdjYuYuAdjYu;

   };
   void calc_susy_traces(Susy_traces&) const;

   double calc_beta_g1_one_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_g1_two_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_g1_three_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_g2_one_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_g2_two_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_g2_three_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_g3_one_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_g3_two_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_g3_three_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_Lambda2_one_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_Lambda2_two_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_Lambda2_three_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_Lambda3_one_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_Lambda3_two_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_Lambda3_three_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_Lambda1_one_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_Lambda1_two_loop(const TRACE_STRUCT_TYPE&) const;
   double calc_beta_Lambda1_three_loop(const TRACE_STRUCT_TYPE&) const;
   Eigen::Matrix<double,3,3> calc_beta_Yu_one_loop(const TRACE_STRUCT_TYPE&) const;
   Eigen::Matrix<double,3,3> calc_beta_Yu_two_loop(const TRACE_STRUCT_TYPE&) const;
   Eigen::Matrix<double,3,3> calc_beta_Yu_three_loop(const TRACE_STRUCT_TYPE&) const;
   Eigen::Matrix<double,3,3> calc_beta_Yd_one_loop(const TRACE_STRUCT_TYPE&) const;
   Eigen::Matrix<double,3,3> calc_beta_Yd_two_loop(const TRACE_STRUCT_TYPE&) const;
   Eigen::Matrix<double,3,3> calc_beta_Yd_three_loop(const TRACE_STRUCT_TYPE&) const;
   Eigen::Matrix<double,3,3> calc_beta_Ye_one_loop(const TRACE_STRUCT_TYPE&) const;
   Eigen::Matrix<double,3,3> calc_beta_Ye_two_loop(const TRACE_STRUCT_TYPE&) const;
   Eigen::Matrix<double,3,3> calc_beta_Ye_three_loop(const TRACE_STRUCT_TYPE&) const;

};

std::ostream& operator<<(std::ostream&, const NSM_susy_parameters&);

#undef TRACE_STRUCT_TYPE

} // namespace flexiblesusy

#endif


/*
*  Template of a compute module
*/

#include <QApplication>

#include <hxcore/HxMessage.h>
#include <hxcore/internal/HxWorkArea.h>
#include <hxfield/HxUniformScalarField3.h>

#include "CCPiXtekNoShiftReconstruction.h"
#include "base_types.hpp"
#include "mpi.hpp"
#include "utils.hpp"
#include "instruments.hpp"
#include "algorithms.hpp"
#include "cgls.hpp"
#include "sirt.hpp"
#include "mlem.hpp"
#include "results.hpp"
#include "voxels.hpp"
#include "ui_messages.hpp"
#include <boost/multi_array.hpp>

HX_INIT_CLASS(CCPiXtekNoShiftReconstruction, HxCompModule)

	CCPiXtekNoShiftReconstruction::CCPiXtekNoShiftReconstruction() :
HxCompModule(HxUniformScalarField3::getClassTypeId()),
	portAction(this,"action",QApplication::translate("CCPiXtekNoShiftReconstruction",
	"Action")),
	algorithm(this, "algorithm",
	QApplication::translate("CCPiXtekNoShiftReconstruction",
	"Reconstruction Algorithm")),
	iterations(this, "number of iterations",
	QApplication::translate("CCPiXtekNoShiftReconstruction", "Iterations")),
	resolution(this, "resolution", QApplication::translate("CCPiXtekNoShiftReconstruction", "Pixels per Voxel")),
	beam_harden(this, "beam harden",
	QApplication::translate("CCPiXtekNoShiftReconstruction", "Beam Hardening")),
	regularize(this, "regularize", QApplication::translate("CCPiXtekNoShiftReconstruction", "Regularization parameter"))
{
	portAction.setLabel(0,"DoIt");
	algorithm.setNum(5);
	algorithm.setLabel(0, "CGLS");
	algorithm.setLabel(1, "SIRT");
	algorithm.setLabel(2, "MLEM");
	algorithm.setLabel(3, "CGLS/Tikhonov");
	algorithm.setLabel(4, "CGLS/TV reg");
	algorithm.setValue(0);
	iterations.setMinMax(5, 30);
	iterations.setValue(20);
	resolution.setMinMax(1, 8);
	resolution.setValue(1);
	beam_harden.setValue(false);
	regularize.setNum(1);
	regularize.setMinMax(0.00001, 0.2);
	regularize.setValue(0.01);
	regularize.hide();
}

CCPiXtekNoShiftReconstruction::~CCPiXtekNoShiftReconstruction()
{
}

void CCPiXtekNoShiftReconstruction::compute()
{
	// Check whether the action port button was clicked
  if (!portAction.wasHit()) {
    if (algorithm.isNew()) {
      if (algorithm.getValue() < 3)
	regularize.hide();
      else
	regularize.show();
    }
    return;
  }
	ccpi_recon::do_progress = false;
	ccpi_recon::messages = theMsg;
	ccpi_recon::progress = theWorkArea;
	HxUniformScalarField3* field = (HxUniformScalarField3*) portData.getSource();
	// Check whether the input port is connected
	if (field == 0)
	{
		theMsg->stream()<<"Is not connected to uniform scalar field source"<<std::endl;
		return;
	}
	float *angles = new float[field->lattice().getDims()[2]];
	int result = field->parameters.findReal("Angles", field->lattice().getDims()[2], angles);
	//Check the Rotation Angle
	if (result!=1)
	{
		theMsg->stream()<<"Angles parameter not found in input data"<<result<<std::endl;
		return;
	}
	// Todo - check of optional shifts
	float *pixel_size = new float[2];
	result = field->parameters.findReal("DetectorPixelSize", 2,pixel_size);
	if(result!=1)
	{
		theMsg->stream()<<"DetectorPixelSize parameter not found in input data"<<result<<std::endl;
		return;
	}
	double srcToObject;
	result = field->parameters.findReal("SourceToObject", srcToObject);
	if(result!=1)
	{
		theMsg->stream()<<"SourceToObject parameter not found in input data"<<std::endl;
		return;
	}
	double srcToDetector;
	result = field->parameters.findReal("SourceToDetector", srcToDetector);
	if(result!=1)
	{
		theMsg->stream()<<"SourceToDetector parameter not found in input data"<<std::endl;
		return;
	}

	// Todo - check that its a float field?
	run_reconstruction();

	// end progress area
	if (ccpi_recon::do_progress) {
		theWorkArea->stopWorking();
		theWorkArea->undivide();
	}
}

void CCPiXtekNoShiftReconstruction::run_reconstruction()
{
	HxUniformScalarField3 *field = (HxUniformScalarField3 *) portData.getSource();
	McDim3l fdims = field->lattice().getDims();
	boost::multi_array_ref<float, 3>
		pixels_ref((float *)field->lattice().dataPtr(),
		boost::extents[fdims[0]][fdims[1]][fdims[2]],
		boost::fortran_storage_order());

	//Convert pixels to correct order
	pixel_3d pixels(boost::extents[fdims[2]][fdims[1]][fdims[0]]);
	for (int i = 0; i < fdims[2]; i++)
		for (int j = 0; j < fdims[1]; j++)
			for (int k = 0; k < fdims[0]; k++)
				pixels[i][j][k] = pixels_ref[k][j][i];

	float *angles_float = new float[field->lattice().getDims()[2]];
	field->parameters.findReal("Angles", field->lattice().getDims()[2], angles_float);
	float *pixel_size = new float[2];
	field->parameters.findReal("DetectorPixelSize", 2,pixel_size);
	double srcToObject;
	field->parameters.findReal("SourceToObject", srcToObject);
	double srcToDetector;
	field->parameters.findReal("SourceToDetector", srcToDetector);
	double MaskRadius;
	field->parameters.findReal("MaskRadius", MaskRadius);
	boost::multi_array_ref<float, 1> angles(angles_float,
						boost::extents[fdims[2]]);

	real h_size = pixel_size[0];
	real v_size = pixel_size[1];

	real source_x = - srcToObject;
	real detector_x = srcToDetector + source_x;

	int pixels_per_voxel = resolution.getValue();
	int niterations = iterations.getValue();
	bool beam_hardening = beam_harden.getValue();
	float reg_param = regularize.getValue();

	CCPi::instrument *instrument = new CCPi::Nikon_XTek();

	CCPi::reconstruction_alg *recon_algorithm = 0;
	switch (algorithm.getValue()) {
	case 0:
		//if (blocking_factor > 0 and instrument->supports_blocks())
		//  recon_algorithm = new CCPi::cgls_2d(niterations, pixels_per_voxel);
		recon_algorithm = new CCPi::cgls_3d(niterations);
		break;
	case 1:
		recon_algorithm = new CCPi::sirt(niterations);
		break;
	case 2:
		recon_algorithm = new CCPi::mlem(niterations);
		break;
	case 3:
	  recon_algorithm = new CCPi::cgls_tikhonov(niterations, reg_param);
		break;
	case 4:
	  recon_algorithm = new CCPi::cgls_tv_reg(niterations, reg_param);
		break;
	}

	machine::initialise(0);
	// instrument setup from pixels/angles will probably copy
	boost::multi_array<float, 1> h_offsets(boost::extents[1]);
	boost::multi_array<float, 1> v_offsets(boost::extents[1]);
	real vox_origin[3];
	real vox_size[3];
	voxel_data *voxels = reconstruct(instrument, recon_algorithm, pixels,
					 angles, h_offsets, v_offsets,
					 pixels_per_voxel, source_x, detector_x,
					 h_size, v_size, MaskRadius,
					 beam_hardening, vox_origin, vox_size,
					 false, false);
	theMsg->stream() << "completed the reconstruction: " << voxels<< std::endl;

	machine::exit();
	delete recon_algorithm;
	delete instrument;
	if (voxels != 0) {
		int dims[3];

		dims[0] = voxels->shape()[0];
		dims[1] = voxels->shape()[1];
		dims[2] = voxels->shape()[2];
		HxUniformScalarField3* output =
			new HxUniformScalarField3(dims, McPrimType::MC_FLOAT);
		for (int i = 0; i < dims[0]; i++)
			for (int j = 0; j < dims[1]; j++)
				for (int k = 0; k < dims[2]; k++)
					output->set(i, j, k, (*voxels)[i][j][k]);
		delete voxels;
		HxUniformCoord3 *coords =(HxUniformCoord3 *) output->lattice().coords();
		McBox3f bx = coords->getBoundingBox();
		bx[0] = vox_origin[0];
		bx[1] = vox_origin[0] + float(dims[0]) * vox_size[0];
		bx[2] = vox_origin[1];
		bx[3] = vox_origin[1] + float(dims[1]) * vox_size[1];
		bx[4] = vox_origin[2];
		bx[5] = vox_origin[2] + float(dims[2]) * vox_size[2];
		// publish reconstruction
		setResult(output);
	}
}


// Copyright (c) 2020, Oracle and/or its affiliates.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, version 2.0,
// as published by the Free Software Foundation.
//
// This program is also distributed with certain software (including
// but not limited to OpenSSL) that is licensed under separate terms,
// as designated in a particular file or component or in included license
// documentation.  The authors of MySQL hereby grant you an additional
// permission to link the program and your derivative works with the
// separately licensed software that they have included with MySQL.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License, version 2.0, for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.

/// @file
///
/// This file implements the discrete Hausdorff distance functor and function.

#include <cmath>  // std::isfinite

#include <boost/geometry.hpp>
#include "my_inttypes.h"                            // MYF
#include "my_sys.h"                                 // my_error
#include "mysqld_error.h"                           // Error codes
#include "sql/dd/types/spatial_reference_system.h"  // dd::Spatial_reference_system
#include "sql/gis/gc_utils.h"
#include "sql/gis/geometries.h"
#include "sql/gis/geometries_traits.h"
#include "sql/gis/hausdorff_distance.h"
#include "sql/gis/hausdorff_distance_functor.h"
#include "sql/sql_exception_handler.h"  // handle_gis_exception

namespace bg = boost::geometry;
namespace bgs = boost::geometry::srs;
namespace bgsd = boost::geometry::strategy::distance;

namespace gis {

Hausdorff_distance::Hausdorff_distance(double major, double minor)
    : m_geographic_strategy(
          new bgsd::geographic<boost::geometry::strategy::andoyer,
                               bgs::spheroid<double>>(
              bgs::spheroid<double>(major, minor))) {}

double Hausdorff_distance::operator()(const Geometry *g1,
                                      const Geometry *g2) const {
  return apply(*this, g1, g2);
}

double Hausdorff_distance::eval(const Geometry *g1, const Geometry *g2) const {
  throw not_implemented_exception::for_non_projected(*g1, *g2);
}

double Hausdorff_distance::eval(const Cartesian_point *g1,
                                const Cartesian_multipoint *g2) const {
  return bg::discrete_hausdorff_distance(*g1, *g2);
}

double Hausdorff_distance::eval(const Cartesian_multipoint *g1,
                                const Cartesian_point *g2) const {
  return bg::discrete_hausdorff_distance(*g2, *g1);
}

double Hausdorff_distance::eval(const Cartesian_linestring *g1,
                                const Cartesian_linestring *g2) const {
  return bg::discrete_hausdorff_distance(*g1, *g2);
}

double Hausdorff_distance::eval(const Cartesian_multipoint *g1,
                                const Cartesian_multipoint *g2) const {
  return bg::discrete_hausdorff_distance(*g1, *g2);
}

double Hausdorff_distance::eval(const Cartesian_linestring *g1,
                                const Cartesian_multilinestring *g2) const {
  return bg::discrete_hausdorff_distance(*g1, *g2);
}

double Hausdorff_distance::eval(const Cartesian_multilinestring *g1,
                                const Cartesian_linestring *g2) const {
  return bg::discrete_hausdorff_distance(*g2, *g1);
}

double Hausdorff_distance::eval(const Cartesian_multilinestring *g1,
                                const Cartesian_multilinestring *g2) const {
  return bg::discrete_hausdorff_distance(*g1, *g2);
}

double Hausdorff_distance::eval(const Geographic_point *g1,
                                const Geographic_multipoint *g2) const {
  return bg::discrete_hausdorff_distance(*g1, *g2, *m_geographic_strategy);
}

double Hausdorff_distance::eval(const Geographic_multipoint *g1,
                                const Geographic_point *g2) const {
  return bg::discrete_hausdorff_distance(*g2, *g1, *m_geographic_strategy);
}

double Hausdorff_distance::eval(const Geographic_linestring *g1,
                                const Geographic_linestring *g2) const {
  return bg::discrete_hausdorff_distance(*g1, *g2, *m_geographic_strategy);
}

double Hausdorff_distance::eval(const Geographic_multipoint *g1,
                                const Geographic_multipoint *g2) const {
  return bg::discrete_hausdorff_distance(*g1, *g2, *m_geographic_strategy);
}

double Hausdorff_distance::eval(const Geographic_linestring *g1,
                                const Geographic_multilinestring *g2) const {
  return bg::discrete_hausdorff_distance(*g1, *g2, *m_geographic_strategy);
}

double Hausdorff_distance::eval(const Geographic_multilinestring *g1,
                                const Geographic_linestring *g2) const {
  return bg::discrete_hausdorff_distance(*g2, *g1, *m_geographic_strategy);
}

double Hausdorff_distance::eval(const Geographic_multilinestring *g1,
                                const Geographic_multilinestring *g2) const {
  return bg::discrete_hausdorff_distance(*g1, *g2, *m_geographic_strategy);
}

/////////////////////////////////////////////////////////////////////////////

bool hausdorff_distance(const dd::Spatial_reference_system *srs,
                        const Geometry *g1, const Geometry *g2,
                        const char *func_name, double *hausdorff_distance,
                        bool *is_null) noexcept {
  try {
    DBUG_ASSERT(g1->coordinate_system() == g2->coordinate_system());
    DBUG_ASSERT(srs == nullptr ||
                ((srs->is_cartesian() &&
                  g1->coordinate_system() == Coordinate_system::kCartesian) ||
                 (srs->is_geographic() &&
                  g1->coordinate_system() == Coordinate_system::kGeographic)));

    if ((*is_null = (g1->is_empty() || g2->is_empty()))) return false;

    Hausdorff_distance hd(srs ? srs->semi_major_axis() : 0.0,
                          srs ? srs->semi_minor_axis() : 0.0);
    *hausdorff_distance = hd(g1, g2);
  } catch (...) {
    handle_gis_exception(func_name);
    return true;
  }

  if (!std::isfinite(*hausdorff_distance) || *hausdorff_distance < 0.0) {
    my_error(ER_DATA_OUT_OF_RANGE, MYF(0), "Hausdorff_distance", func_name);
    return true;
  }

  return false;
}

}  // namespace gis


#ifndef CELL_HPP
#define CELL_HPP

#include "vector"
#include "array"
#include "iostream"

#include <Eigen/Dense>
#include "mpi.h"

using namespace Eigen;

/// 4x1 vector FIXME multiple definitions of the same type also in pic.cpp
typedef Array<double, 4, 1> vec4;
// typedef Array<double, 3, 1> vec3;

/// Full 4x4 GR vector
typedef Array<double, 4, 4> mat4;

// Define cell types for different boundary conditions
#define NORMAL_CELL 0               ///< Cell in a normal state
#define METAL_BOUNDARY_X_CELL 1     ///< Cell with a conducting X boundary
#define METAL_BOUNDARY_Y_CELL 2     ///< Cell with a conducting Y boundary

// Enumarete particle populations
enum Population
{
    ELECTRONS, ///< Electron particle population
    POSITRONS,  ///< positron particle population
    N_POPULATIONS
};

/// Modular Cell class for mpiGrid
/**
Cell class to handle MPI communications
*/

class Cell
{
public:

    /// current number of electrons inside the Cell
	// unsigned int number_of_electrons = 0;

    /// current number of positrons inside the Cell
	// unsigned int number_of_positrons = 0;

    std::array<uint64_t , Population::N_POPULATIONS> particles_in_population;

    uint64_t& number_of(Population particle_population)
    {
        return particles_in_population[particle_population];
    };

    /// Particle 6D phase space populations
    ///@{
	/// 6D phase space coordinates of electrons in this cell
	std::vector<std::array<double, 6> > electrons;

	/// 6D phase space coordinates of positrons in this cell
	std::vector<std::array<double, 6> > positrons;
    ///@}

    /// Particle population switcher TODO fix/check performance
    std::vector<std::array<double, 6> >& particles(Population particle_population)
    {
        switch(particle_population) {
            case Population::ELECTRONS:
                return this->electrons;
                break;
            case Population::POSITRONS:
                return this->positrons;
                break;
            default:
                    std::cerr << __FILE__ << ":" << __LINE__
                    << " Invalid population switch: " << particle_population
                    << std::endl;
                abort();
                break;
        }
    };

    /// 4-current vector containing (rho, Jx, Jy, Jz)
    vec4 J;

    /// standard 3-vector for currents & field vectors at Yee lattice staggeration
    /**
        XXX Here we use 3+1 formalism which is more easily
        expressed via E and B, not F tensor
    */
    Vector3d JY, BY, EY;

    /* Yee staggered field tensor
        note that E and B elements are defined in different locations
        due to the staggering
     mat4 FY = mat4::Zero();
    */

    // Yee lattice/mesh stuff array
    /*
        0 - charge density rho
        DONE 1 2 3    - current vector on nodal points Jx, Jy, Jz
        DONE 4 5 6    - current vector on Yee lattice  JxY, JyY, JzY
        7 8 9    - E field on Yee lattice ExY, EyY, EzY
        10 11 12 - B field on Yee lattice BxY, ByY, BzY

        13 14 15 - E field on nodal lattice Ex, Ey, Ez
        16 17 18 - B field on nodal lattice Bx, By, Bz
    std::array<double, 19> field = {{0.0,
                                     0.0, 0.0, 0.0,
                                     0.0, 0.0, 0.0,
                                     0.0, 0.0, 0.0,
                                     0.0, 0.0, 0.0,
                                     0.0, 0.0, 0.0,
                                     0.0, 0.0, 0.0}};
    */

    /// incoming currents from other processes
    /**  3x3x3 cube with four-vector elements = 108 values */
    std::array<double, 108> incoming_currents;

    /** @name Auxiliary functions
     *  auxiliary functions to help disentangle lattice values
     *
     *  note how we always give normal and const definition
     *  this makes sure the variable pointed to by the returned
     *  pointer & won't be alterable and that the method does not
     *  alter the variable pointed to by the given pointer. (phew!)
    */

    /*
    ///@{
    /// overload array operator for object
    double& operator [](const std::size_t i){return this->field[i];}
    const double& operator [](const std::size_t i) const {return this->field[i];}

    /// charge density
    double& rho(){ return this->field[0]; }
    const double& rho() const { return this->field[0]; }

    /// nodal current x
    double& Jx(){ return this->field[1]; }
    const double& Jx() const { return this->field[1]; }

    /// nodal current y
    double& Jy(){ return this->field[2]; }
    const double& Jy() const { return this->field[2]; }

    /// nodal current z
    double& Jz(){ return this->field[3]; }
    const double& Jz() const { return this->field[3]; }

    /// Yee currents x
    double& JxY(){ return this->field[4]; }
    const double& JxY() const { return this->field[4]; }

    /// Yee currents y
    double& JyY(){ return this->field[5]; }
    const double& JyY() const { return this->field[5]; }

    /// Yee currents z
    double& JzY(){ return this->field[6]; }
    const double& JzY() const { return this->field[6]; }

    /// Yee E x
    double& ExY(){ return this->field[7]; }
    const double& ExY() const { return this->field[7]; }

    /// Yee E y
    double& EyY(){ return this->field[8]; }
    const double& EyY() const { return this->field[8]; }

    /// Yee E z
    double& EzY(){ return this->field[9]; }
    const double& EzY() const { return this->field[9]; }

    /// Yee B x
    double& BxY(){ return this->field[10]; }
    const double& BxY() const { return this->field[10]; }

    /// Yee B y
    double& ByY(){ return this->field[11]; }
    const double& ByY() const { return this->field[11]; }

    /// Yee B z
    double& BzY(){ return this->field[12]; }
    const double& BzY() const { return this->field[12]; }

    /// nodal E field vector x
    double& Ex(){ return this->field[13]; }
    const double& Ex() const { return this->field[13]; }

    /// nodal E field vector y
    double& Ey(){ return this->field[14]; }
    const double& Ey() const { return this->field[14]; }

    /// nodal E field vector z
    double& Ez(){ return this->field[15]; }
    const double& Ez() const { return this->field[15]; }

    /// nodal B x
    double& Bx(){ return this->field[16]; }
    const double& Bx() const { return this->field[16]; }

    /// nodal B y
    double& By(){ return this->field[17]; }
    const double& By() const { return this->field[17]; }

    /// nodal B z
    double& Bz(){ return this->field[18]; }
    const double& Bz() const { return this->field[18]; }
    ///@}
    */

    /// Cell type XXX: use me!
    double cell_type = 0;

    /// list of remote neighbors
    std::array<uint64_t, 27>
        remote_neighbor_list = {{0, 0, 0, 0, 0, 0, 0, 0, 0,
                                 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                 0, 0, 0, 0, 0, 0, 0, 0, 0
                                }};

    /// transfer switch
	/**
     Defines what is transferred over MPI
	*/
	static int transfer_mode;

    // Enumarete data types
    enum {
        INIT,                 /// < data related to initialization
        NUMBER_OF_ELECTRONS,  /// < Number of electrons to expect
        NUMBER_OF_POSITRONS,  /// < Number of positrons to expect
        ELECTRONS,            /// < 6D electron phase phases
        POSITRONS,            /// < 6D positron phase phases
        FIELDS,               /// < data of EM fields
		REMOTE_NEIGHBOR_LIST, /// < List of remote neighbors sending to me
		INCOMING_CURRENTS,    /// < data of incoming currents
        CURRENT,              /// < current four vector
        YEE_CURRENT,          /// < staggered current four vector
        YEE_B,                /// < Staggered B field
        YEE_E,                /// < Staggered E field
		TYPE                  /// < Cell type
    };

    /// handle the MPI calls depending on cell state
	std::tuple<void*, int, MPI_Datatype> get_mpi_datatype();

	/// reserves space for electron phase space data coming over MPI
    /// TODO make ambigious of population
    void resize_population(Population particle_population)
    {
        switch(particle_population) {
            case Population::ELECTRONS:
                this->electrons.resize(this->number_of(Population::ELECTRONS));
                break;
            case Population::POSITRONS:
                this->positrons.resize(this->number_of(Population::POSITRONS));
                break;
            default:
                    std::cerr << __FILE__ << ":" << __LINE__
                    << " Invalid population switch: " << particle_population
                    << std::endl;
                abort();
                break;
        }
    };

};

#endif


#include "anomaly.h"
#include "../util.h"

#include <boost/exception/all.hpp>

namespace Akumuli {
namespace QP {

static AnomalyDetector::FcastMethod parse_anomaly_detector_type(boost::property_tree::ptree const& ptree) {
    bool approx = ptree.get<bool>("approx");
    std::string name = ptree.get<std::string>("method");
    AnomalyDetector::FcastMethod method;
    if (name == "ewma" || name == "exp-smoothing") {
        method = approx ? AnomalyDetector::EWMA_SKETCH : AnomalyDetector::EWMA;
    } else if (name == "sma" || name == "simple-moving-average") {
        method = approx ? AnomalyDetector::SMA_SKETCH : AnomalyDetector::SMA;
    } else if (name == "double-exp-smoothing") {
        method = approx ? AnomalyDetector::DOUBLE_EXP_SMOOTHING_SKETCH : AnomalyDetector::DOUBLE_EXP_SMOOTHING;
    } else if (name == "holt-winters") {
        method = approx ? AnomalyDetector::HOLT_WINTERS_SKETCH : AnomalyDetector::HOLT_WINTERS;
    } else {
        QueryParserError err("Unknown forecasting method");
        BOOST_THROW_EXCEPTION(err);
    }
    return method;
}

static void validate_sketch_params(boost::property_tree::ptree const& ptree) {
    uint32_t bits = ptree.get<uint32_t>("bits", 8);
    uint32_t hashes = ptree.get<uint32_t>("hashes", 1);
    // bits should be in range
    if (bits < 8 || bits > 16) {
        QueryParserError err("Anomaly detector parameter `bits` out of range");
        BOOST_THROW_EXCEPTION(err);
    }
    // hashes should be in range and odd
    if (hashes % 2 == 0) {
        QueryParserError err("Anomaly detector parameter `hashes` should be odd");
        BOOST_THROW_EXCEPTION(err);
    }
    if (hashes == 0 || hashes > 9) {
        QueryParserError err("Anomaly detector parameter `hashes` out of range");
        BOOST_THROW_EXCEPTION(err);
    }
}

static void validate_all_params(std::vector<std::string> required, boost::property_tree::ptree const& ptree) {
    for (auto name: required) {
        auto o = ptree.get_optional<std::string>(name);
        if (!o) {
            std::string err_msg = "Parameter " + name + " should be set";
            QueryParserError err(err_msg.c_str());
            BOOST_THROW_EXCEPTION(err);
        }
    }
}

static void validate_anomaly_detector_params(boost::property_tree::ptree const& ptree) {
    auto type = parse_anomaly_detector_type(ptree);
    switch(type) {
    case AnomalyDetector::SMA_SKETCH:
        validate_sketch_params(ptree);
    case AnomalyDetector::SMA:
        validate_all_params({"period"}, ptree);
        break;

    case AnomalyDetector::EWMA_SKETCH:
        validate_sketch_params(ptree);
    case AnomalyDetector::EWMA:
        validate_all_params({"alpha"}, ptree);
        break;

    case AnomalyDetector::DOUBLE_EXP_SMOOTHING_SKETCH:
        validate_sketch_params(ptree);
    case AnomalyDetector::DOUBLE_EXP_SMOOTHING:
        validate_all_params({"alpha", "gamma"}, ptree);
        break;

    case AnomalyDetector::HOLT_WINTERS_SKETCH:
        validate_sketch_params(ptree);
    case AnomalyDetector::HOLT_WINTERS:
        validate_all_params({"alpha", "beta", "gamma", "period"}, ptree);
        break;
    }
}

static void validate_coef(double value, double range_begin, double range_end, const char* err_msg) {
    if (value >= range_begin && value <= range_end) {
        return;
    }
    QueryParserError err(err_msg);
    BOOST_THROW_EXCEPTION(err);
}

AnomalyDetector::AnomalyDetector(boost::property_tree::ptree const& ptree, std::shared_ptr<Node> next)
    : next_(next)
{
    validate_anomaly_detector_params(ptree);
    double threshold = ptree.get<double>("threshold");
    uint32_t bits = ptree.get<uint32_t>("bits", 10u);
    uint32_t nhashes = ptree.get<uint32_t>("hashes", 3u);
    AnomalyDetector::FcastMethod method = parse_anomaly_detector_type(ptree);
    double alpha = ptree.get<double>("alpha", 0.0);
    double beta = ptree.get<double>("beta", 0.0);
    double gamma = ptree.get<double>("gamma", 0.0);
    int period = ptree.get<int>("period", 0);
    validate_coef(alpha, 0.0, 1.0, "`alpha` should be in [0, 1] range");
    validate_coef(beta,  0.0, 1.0, "`beta` should be in [0, 1] range");
    validate_coef(gamma, 0.0, 1.0, "`gamma` should be in [0, 1] range");

    switch(method) {
    case SMA:
        detector_ = AnomalyDetectorUtil::create_precise_sma(threshold, period);
        break;
    case SMA_SKETCH:
        detector_ = AnomalyDetectorUtil::create_approx_sma(nhashes, 1 << bits, threshold, period);
        break;
    case EWMA:
        detector_ = AnomalyDetectorUtil::create_precise_ewma(threshold, alpha);
        break;
    case EWMA_SKETCH:
        detector_ = AnomalyDetectorUtil::create_approx_ewma(nhashes, 1 << bits, threshold, alpha);
        break;
    case DOUBLE_EXP_SMOOTHING:
        detector_ = AnomalyDetectorUtil::create_precise_double_exp_smoothing(threshold, alpha, gamma);
        break;
    case DOUBLE_EXP_SMOOTHING_SKETCH:
        detector_ = AnomalyDetectorUtil::create_approx_double_exp_smoothing(nhashes, 1 << bits, threshold, alpha, gamma);
        break;
    case HOLT_WINTERS:
        detector_ = AnomalyDetectorUtil::create_precise_holt_winters(threshold, alpha, beta, gamma, period);
        break;
    case HOLT_WINTERS_SKETCH:
        detector_ = AnomalyDetectorUtil::create_approx_holt_winters(nhashes, 1 << bits, threshold, alpha, beta, gamma, period);
        break;
    default:
        AKU_PANIC("AnomalyDetector building error");
    }
}

AnomalyDetector::AnomalyDetector(
        uint32_t nhashes,
        uint32_t bits,
        double   threshold,
        double   alpha,
        double   beta,
        double   gamma,
        int      period,
        FcastMethod method,
        std::shared_ptr<Node> next)
    : next_(next)
{
    try {
        switch(method) {
        case SMA:
            detector_ = AnomalyDetectorUtil::create_precise_sma(threshold, period);
            break;
        case SMA_SKETCH:
            detector_ = AnomalyDetectorUtil::create_approx_sma(nhashes, 1 << bits, threshold, period);
            break;
        case EWMA:
            detector_ = AnomalyDetectorUtil::create_precise_ewma(threshold, alpha);
            break;
        case EWMA_SKETCH:
            detector_ = AnomalyDetectorUtil::create_approx_ewma(nhashes, 1 << bits, threshold, alpha);
            break;
        case DOUBLE_EXP_SMOOTHING:
            detector_ = AnomalyDetectorUtil::create_precise_double_exp_smoothing(threshold, alpha, gamma);
            break;
        case DOUBLE_EXP_SMOOTHING_SKETCH:
            detector_ = AnomalyDetectorUtil::create_approx_double_exp_smoothing(nhashes, 1 << bits, threshold, alpha, gamma);
            break;
        case HOLT_WINTERS:
            detector_ = AnomalyDetectorUtil::create_precise_holt_winters(threshold, alpha, beta, gamma, period);
            break;
        case HOLT_WINTERS_SKETCH:
            detector_ = AnomalyDetectorUtil::create_approx_holt_winters(nhashes, 1 << bits, threshold, alpha, beta, gamma, period);
            break;
        default:
            std::logic_error err("AnomalyDetector building error");  // invalid use of the constructor
            BOOST_THROW_EXCEPTION(err);
        }
    } catch (...) {
        // std::cout << boost::current_exception_diagnostic_information() << std::endl;
        throw;
    }
}

void AnomalyDetector::complete() {
    next_->complete();
}

bool AnomalyDetector::put(const aku_Sample &sample) {
    if (sample.payload.type == aku_PData::EMPTY) {
        detector_->move_sliding_window();
        return next_->put(sample);
    } else if (sample.payload.type & aku_PData::FLOAT_BIT) {
        detector_->add(sample.paramid, sample.payload.float64);
        if (detector_->is_anomaly_candidate(sample.paramid)) {
            aku_Sample anomaly = sample;
            anomaly.payload.type |= aku_PData::URGENT;
            return next_->put(anomaly);
        }
    }
    // Ignore BLOBs
    return true;
}

void AnomalyDetector::set_error(aku_Status status) {
    next_->set_error(status);
}

int AnomalyDetector::get_requirements() const {
    return TERMINAL|GROUP_BY_REQUIRED;
}

//! Register anomaly detector for use in queries
static QueryParserToken<AnomalyDetector> detector_token("anomaly-detector");

}}  // namespace


/***
 *  $Id$
 **
 *  File: performance_indicators_diss.hpp
 *  Created: May 9, 2012
 *
 *  Author: Olga Wodo, Baskar Ganapathysubramanian
 *  Copyright (c) 2012 Olga Wodo, Baskar Ganapathysubramanian
 *  See accompanying LICENSE.
 *
 *  This file is part of GraSPI.
 */

#ifndef PERFORMANCE_INDICATORS_DISS_HPP
#define PERFORMANCE_INDICATORS_DISS_HPP

#include <climits>
#include <sstream>

#include "graspi_types.hpp"
#include "graph_dijkstra.hpp"
#include "graspi_predicates.hpp"
#include <boost/graph/filtered_graph.hpp>

namespace graspi {

    /// structure used to estimate the contribution of electron donor to exciton dissociation
    struct foo_w_diss{
        double A1; ///< coefficient to define the exciton diffusion weighting function
        double B1; ///< coefficient to define the exciton diffusion weighting function
        double C1; ///< coefficient to define the exciton diffusion weighting function
        foo_w_diss(){ A1=6.265; B1=-23.0; C1=17.17; }
        double operator()(double d)const{
            return A1*exp(-((d-B1)/C1)*((d-B1)/C1));
        }
    };

    /// Function to count number of vertices within predefined distance to target vertices
    ///
    /// @param d is the vector of distances
    /// @param Ld is the predefined distance to be checked against
    /// return the total number of vertices within the Ld distance to the targeted meta-vertex
    inline int
    identify_n_vertices_within_distance( const std::vector<float>& d,
                                        double Ld){
        int n_Ld = 0;
        for(unsigned int i = 0; i < d.size(); i++){
            if( (d[i] < Ld) && (d[i] > 0) ) n_Ld++;
        }
        return n_Ld;
    }

    /// Function to compute the weighted sum of electron-donor contributing to exciton dissociation located with Ld distance to the interface
    ///
    /// @param d is the vector of distances
    /// @param Ld is the predefined distance to be checked against
    /// return the weigthed sum over all vertices within the Ld distance to the targeted meta-vertex
    inline double
    identify_weighted_vertices_within_distance( const std::vector<float>& d,
                                               double Ld){
        double wn_Ld = 0;
        foo_w_diss wfoo;

        for(unsigned int i = 0; i < d.size(); i++){
            double d_i = d[i];
            double wd_i = wfoo(d_i);
            if( (d_i < Ld) && (d_i > 0) ) wn_Ld+= wd_i;
        }
        return wn_Ld;
    }

    /// Function to compute the weighted fraction of electron-donor contributing to exciton dissociation located with Ld distance to the interface
    ///
    /// @param d is the vector of distances
    /// @param Ld is the predefined distance to be checked against
    /// return the pair of values required to compute the weigthed fraction
    /// the pair consists of weigthed sum of contribution
    /// and the total number of vertices within the Ld distance to the targeted meta-vertex (here GREEN)
    inline std::pair<int,double>
    identify_n_weighted_vertices_within_distance( const std::vector<float>& d,
                                                 double Ld){
        double wn_Ld = 0;
        int n_Ld = 0;
        foo_w_diss wfoo;

        for(unsigned int i = 0; i < d.size(); i++){
            double d_i = d[i];
            double wd_i = wfoo(d_i);
            if( (d_i < Ld) && (d_i > 0) ){
                wn_Ld+= wd_i;
                n_Ld++;
            }
        }
        return std::pair<int,double>(n_Ld,wn_Ld);
    }

    /// This function computes two descriptors related to exciton dissociation
    ///
    /// @param G is the input graph
    /// @param d_g is the structure storing basic informations about the graph dimensionality
    /// @param C is the vector storing the labels/colors of vertices in the graph G
    /// @param W is the map storing the weights of the edges
    /// @param vCC is the vector storing indices of the connected components (CC) of each vector in the graph
    /// @param CC is the vector of CC,
    /// @param Ld is the exciton diffusion length
    /// @param filename_ColorToGreen is the filename where all distances from GREEN to BLACK vertices will be outputted
    /// @param filename_WColorToGreen is the filename where all weighted distances from GREEN to BLACK vertices will be outputed
    /// @param color is the color code for vertices that will be analyzed (default value is BLACK)
    /// @param green is the color code for targeted meta-vertex (default value is GREEN)
    /// @return the pair of fractions: the weighted fraction of vertices and the fraction of vertices within Ld distance to the targeted meta-vertex
    inline std::pair<double,double>
    wf_diss(
            graph_t* G, const dim_g_t& d_g, const vertex_colors_t& C,
            const edge_weights_t& W, const vertex_ccs_t& vCC,
            const ccs_t& CC,
            double Ld,
            const std::string& filename_ColorToGreen,
            const std::string& filename_WColorToGreen,
            COLOR color = BLACK,
            COLOR green = GREEN
            ){
        int n_color = 0;
        int n_color_Ld = 0;
        double wn_color_Ld = 0;

        connect_color_green pred(*G,C,color,green);
        unsigned int n = boost::num_vertices(*G);
        vertex_t int_id = d_g.id(green);
        std::vector<float> d(n);

        determine_shortest_distances( G, W, int_id, pred, d);

        foo_w_diss wfoo;
        std::ostringstream oss_out_d;
        std::ostringstream oss_out_wd;
        for (unsigned int i = 0; i < d.size(); i++) {
            unsigned int c = C[i];
            if (c == color) n_color++;
            if ( ( c == color )
                && ( fabs(d[i]) < std::numeric_limits<float>::max() )
                ) {
                double d_i = d[i];
                oss_out_d  << d_i       << std::endl;
                oss_out_wd << d_i << " " << wfoo(d_i) << std::endl;
            }
        }
        std::ofstream f_out(filename_ColorToGreen.c_str());
        std::string buffer = oss_out_d.str();
        int size = oss_out_d.str().size();
        f_out.write (buffer.c_str(),size);
        f_out.close();
        f_out.open(filename_WColorToGreen.c_str());
        buffer = oss_out_wd.str();
        size = oss_out_wd.str().size();
        f_out.write (buffer.c_str(),size);
        f_out.close();

        std::pair<int,double> pLd
        = identify_n_weighted_vertices_within_distance(d,Ld);
        n_color_Ld = pLd.first;
        wn_color_Ld = pLd.second;

#ifdef DEBUG
        std::cout << "[DEBUG] Number of " << color << "vertices: "
        << n_color << std::endl
        << "[DEBUG] Number of " << color << "vertices in "
        << Ld << " distance to green: "
        << n_color_Ld << std::endl;
#endif

        return std::pair<double, double>(
                                         (double)wn_color_Ld/n_color,
                                         (double)n_color_Ld/n_color
                                         );
    }

      /// Function to compute the descriptors related to exciton dissociation
      ///
      /// @cond
      /// @fn std::pair<double,double> wf_diss( graph_t* G, const dim_g_t& d_g, const vertex_colors_t& C, const edge_weights_t& W, const vertex_ccs_t& vCC, const ccs_t& CC, double Ld, COLOR color = BLACK, COLOR green = GREEN )
      /// This function computes two descriptors related to exciton dissociation
      /// @param G is the input graph
      /// @param d_g is the structure storing basic informations about the graph dimensionality
      /// @param C is the vector storing the labels/colors of vertices in the graph G
      /// @param W is the map storing the weights of the edges
      /// @param vCC is the vector storing indices of the connected components (CC) of each vector in the graph
      /// @param CC is the vector of CC,
      /// @param Ld is the exciton diffusion length
      /// @param color is the color code for vertices that will be analyzed (default value is BLACK)
      /// @param green is the color code for targeted meta-vertex (default value is GREEN)
      /// @return the pair of fractions: the weighted fraction of vertices and the fraction of vertices within Ld distance to the targeted meta-vertex
    inline std::pair<double,double> wf_diss(
            graph_t* G, const dim_g_t& d_g, const vertex_colors_t& C,
            const edge_weights_t& W, const vertex_ccs_t& vCC,
            const ccs_t& CC,
            double Ld,
            COLOR color = BLACK,
            COLOR green = GREEN
            ){
        int n_color = 0;
        int n_color_Ld = 0;
        double wn_color_Ld = 0;

        connect_color_green pred(*G,C,color,green);
        unsigned int n = boost::num_vertices(*G);
        vertex_t int_id = d_g.id(green);
        std::vector<float> d(n);

        determine_shortest_distances( G, W, int_id, pred, d);

        foo_w_diss wfoo;
        for (unsigned int i = 0; i < d.size(); i++) {
            unsigned int c = C[i];
            if (c == color) n_color++;
        }

        std::pair<int,double> pLd
        = identify_n_weighted_vertices_within_distance(d,Ld);
        n_color_Ld = pLd.first;
        wn_color_Ld = pLd.second;

        return std::pair<double, double>(
                                         (double)wn_color_Ld/n_color,
                                         (double)n_color_Ld/n_color
                                         );
    }
    /// @endcond

}
#endif


// ====================================================================
// This file is part of FlexibleSUSY.
//
// FlexibleSUSY is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// FlexibleSUSY is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with FlexibleSUSY.  If not, see
// <http://www.gnu.org/licenses/>.
// ====================================================================

#ifndef FIXED_POINT_ITERATOR_H
#define FIXED_POINT_ITERATOR_H

#include <iostream>
#include <limits>
#include <string>
#include <utility>
#include <Eigen/Core>
#include <gsl/gsl_sys.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_multiroots.h>

#include "logger.hpp"
#include "wrappers.hpp"
#include "error.hpp"
#include "ewsb_solver.hpp"
#include "gsl_utils.hpp"
#include "gsl_vector.hpp"

namespace flexiblesusy {

namespace fixed_point_iterator {

class Convergence_tester_absolute {
public:
   explicit Convergence_tester_absolute(double precision_ = 1.0e-2)
      : precision(precision_)
   {}

   std::string name() const { return "Convergence_tester_absolute"; }

   /**
    * Test whether the absolute value of the residual, defined by
    * \f$|a-b| = \sqrt{\sum_i (a_i - b_i)^2}\f$,
    * is less than the set precision.
    *
    * @param a GSL vector
    * @param b GSL vector
    * @return GSL error code (GSL_SUCCESS or GSL_CONTINUE)
    */
   int operator()(const GSL_vector& a, const GSL_vector& b) const {
      if (a.size() != b.size()) throw SetupError("Error: vectors have different size.");

      const auto dimension = a.size();
      double residual = 0.;

      if (precision < 0.)
         GSL_ERROR("absolute tolerance is negative", GSL_EBADTOL);

      for (std::size_t i = 0; i < dimension; ++i)
         residual += Sqr(a[i] - b[i]);

      residual = Sqrt(residual);

      return (residual < precision ? GSL_SUCCESS : GSL_CONTINUE);
   }

private:
   double precision;                 ///< precision goal
};

class Convergence_tester_relative {
public:
   explicit Convergence_tester_relative(double precision_ = 1.0e-2)
      : precision(precision_)
   {}

   std::string name() const { return "Convergence_tester_relative"; }

   /**
    * Test whether the relative difference is less than the set
    * precision. The relative difference test used here is carried out
    * by applying \a MaxRelDiff to each element of the vector.
    *
    * @param a GSL vector
    * @param b GSL vector
    * @return GSL error code (GSL_SUCCESS or GSL_CONTINUE)
    */
   int operator()(const GSL_vector& a, const GSL_vector& b) const {
      if (a.size() != b.size()) throw SetupError("Error: vectors have different size.");

      const auto dimension = a.size();
      double rel_diff = 0.;

      if (precision < 0.)
         GSL_ERROR("relative tolerance is negative", GSL_EBADTOL);

      for (std::size_t i = 0; i < dimension; ++i) {
         rel_diff = MaxRelDiff(a[i], b[i]);

         if (rel_diff > precision)
            return GSL_CONTINUE;
      }

      return GSL_SUCCESS;
   }

private:
   double precision;                 ///< precision goal
};

template <std::size_t dimension>
class Convergence_tester_tadpole {
public:
   using Vector_t = Eigen::Matrix<double,dimension,1>;
   using Function_t = std::function<Vector_t(const Vector_t&)>;

   Convergence_tester_tadpole(double precision_,
                              const Function_t& tadpole_function_)
      : precision(precision_)
      , tadpole_function(tadpole_function_)
   {}

   std::string name() const { return "Convergence_tester_tadpole"; }

   /**
    * Test whether the relative difference is less than the set
    * precision. The relative difference test used here is carried out
    * by applying \a MaxRelDiff to each element of the vector. If the
    * relative difference is below the precision, it is tested whether
    * the tadpoles are below the precision. If the tadpoles are larger
    * than the precision, GSL_CONTINUE is returned.
    *
    * @param a GSL vector
    * @param b GSL vector
    * @return GSL error code (GSL_SUCCESS or GSL_CONTINUE)
    */
   int operator()(const GSL_vector& a, const GSL_vector& b) const {
      if (a.size() != b.size()) throw SetupError("Error: vectors have different size.");

      if (precision < 0.)
         GSL_ERROR("relative tolerance is negative", GSL_EBADTOL);

      const double max_rel_diff =
         MaxRelDiff(to_eigen_vector(a), to_eigen_vector(b));

      if (max_rel_diff > precision)
         return GSL_CONTINUE;

      static const double eps = 10*std::pow(10., -std::numeric_limits<double>::digits10);

      if (max_rel_diff < eps)
         return GSL_SUCCESS;

      return check_tadpoles(a);
   }

private:
   double precision;                 ///< precision goal
   const Function_t tadpole_function; ///< function to calculate tadpole

   int check_tadpoles(const GSL_vector& x) const {
      const GSL_vector t(to_GSL_vector(tadpole_function(to_eigen_vector(x))));
      return gsl_multiroot_test_residual(t.raw(), precision);
   }
};

} // namespace fixed_point_iterator

/**
 * @class Fixed_point_iterator
 * @brief Does fixed point iteration
 * @author Dylan Harries, Alexander Voigt
 * @tparam dimension dimension of function
 * @tparam Convergence_tester function for relative comparison
 *    of subsequent iteration steps
 *
 * The user has to provide the function (of which a fixed point should
 * be found) of the type \a Function_t. This function gets as
 * arguments a Eigen vector of length \a dimension and returns a
 * vector with the next point.
 *
 * @note The standard relative convergence criterion
 * \f$\text{MaxRelDiff}(x_{n+1}, x_{n}) < \text{precision}\f$ is not
 * very good: The iteration might converge slowly.  This means, that
 * subsequent steps are very close to each other, but \f$x_n\f$ might
 * not be close to the true fixed point.
 *
 * @todo implement check for no progress towards solution
 */
template <std::size_t dimension, class Convergence_tester = fixed_point_iterator::Convergence_tester_relative>
class Fixed_point_iterator : public EWSB_solver {
public:
   using Vector_t = Eigen::Matrix<double,dimension,1>;
   using Function_t = std::function<Vector_t(const Vector_t&)>;

   Fixed_point_iterator() = default;
   template <typename F>
   Fixed_point_iterator(F&&, std::size_t, const Convergence_tester&);
   virtual ~Fixed_point_iterator() = default;

   template <typename F>
   void set_function(F&& f) { function = std::forward<F>(f); }
   void set_max_iterations(std::size_t n) { max_iterations = n; }
   int find_fixed_point(const Eigen::VectorXd&);

   // EWSB_solver interface methods
   virtual std::string name() const override { return "Fixed_point_iterator<" + convergence_tester.name() + ">"; }
   virtual int solve(const Eigen::VectorXd&) override;
   virtual Eigen::VectorXd get_solution() const override;

private:
   std::size_t max_iterations{100};         ///< maximum number of iterations
   GSL_vector xn{dimension};                ///< current iteration point
   GSL_vector fixed_point{dimension};       ///< vector of fixed point estimate
   Function_t function{nullptr};            ///< function defining fixed point
   Convergence_tester convergence_tester{}; ///< convergence tester

   int fixed_point_iterator_iterate();
   void print_state(std::size_t) const;
   static int gsl_function(const gsl_vector*, void*, gsl_vector*);
};

/**
 * Constructor
 *
 * @param function_ pointer to the function to find fixed point for
 * @param max_iterations_ maximum number of iterations
 * @param convergence_tester_ convergence tester
 */
template <std::size_t dimension, class Convergence_tester>
template <typename F>
Fixed_point_iterator<dimension,Convergence_tester>::Fixed_point_iterator(
   F&& function_,
   std::size_t max_iterations_,
   const Convergence_tester& convergence_tester_
)
   : max_iterations(max_iterations_)
   , function(std::forward<F>(function_))
   , convergence_tester(convergence_tester_)
{
}

/**
 * Start the iteration
 *
 * @param start starting point
 *
 * @return GSL error code (GSL_SUCCESS if fixed point found)
 */
template <std::size_t dimension, class Convergence_tester>
int Fixed_point_iterator<dimension,Convergence_tester>::find_fixed_point(
   const Eigen::VectorXd& start
)
{
   if (!function)
      throw SetupError("Fixed_point_iterator: function not callable");

   int status;
   std::size_t iter = 0;

#ifndef ENABLE_DEBUG
   gsl_set_error_handler_off();
#endif

   fixed_point = xn = to_GSL_vector(start);

#ifdef ENABLE_VERBOSE
   print_state(iter);
#endif

   do {
      iter++;
      status = fixed_point_iterator_iterate();

#ifdef ENABLE_VERBOSE
      print_state(iter);
#endif

      if (status)   // check if iterator has problems
         break;

      status = convergence_tester(fixed_point, xn);

   } while (status == GSL_CONTINUE && iter < max_iterations);

   VERBOSE_MSG("\t\t\tFixed_point_iterator status = "
               << gsl_strerror(status));

   return status;
}

/**
 * Perform a single step of the fixed point iteration
 *
 * @return GSL error code
 */
template <std::size_t dimension, class Convergence_tester>
int Fixed_point_iterator<dimension,Convergence_tester>::fixed_point_iterator_iterate()
{
   xn = fixed_point;

   void* parameters = &function;

   int status = gsl_function(xn.raw(), parameters, fixed_point.raw());

   if (status != GSL_SUCCESS)
      return GSL_EBADFUNC;

   // For safety, include a check for nans or infs here (which
   // should be sufficient for now)
   if (!is_finite(fixed_point))
      GSL_ERROR("update point is not finite", GSL_EBADFUNC);

   return GSL_SUCCESS;
}

/**
 * Print state of the fixed point iterator
 *
 * @param iteration iteration number
 */
template <std::size_t dimension, class Convergence_tester>
void Fixed_point_iterator<dimension,Convergence_tester>::print_state(std::size_t iteration) const
{
   VERBOSE_MSG("\t\t\tIteration n = " << iteration
               << ": x_{n} = " << xn
               << ", x_{n+1} = " << fixed_point);
}

template <std::size_t dimension, class Convergence_tester>
int Fixed_point_iterator<dimension,Convergence_tester>::gsl_function(const gsl_vector* x, void* params, gsl_vector* f)
{
   if (!is_finite(x)) {
      gsl_vector_set_all(f, std::numeric_limits<double>::max());
      return GSL_EDOM;
   }

   Function_t* fun = static_cast<Function_t*>(params);
   int status = GSL_SUCCESS;
   const Vector_t arg(to_eigen_vector(x));
   auto result = arg;

   try {
      result = (*fun)(arg);
      status = GSL_SUCCESS;
   } catch (const flexiblesusy::Error&) {
      status = GSL_EDOM;
   }

   copy(result, f);

   return status;
}

template <std::size_t dimension, class Convergence_tester>
int Fixed_point_iterator<dimension,Convergence_tester>::solve(const Eigen::VectorXd& start)
{
   return (find_fixed_point(start) == GSL_SUCCESS ?
           EWSB_solver::SUCCESS : EWSB_solver::FAIL);
}

template <std::size_t dimension, class Convergence_tester>
Eigen::VectorXd Fixed_point_iterator<dimension,Convergence_tester>::get_solution() const
{
   return to_eigen_vector(fixed_point);
}

} // namespace flexiblesusy

#endif