/*!@file
 * @copyright This code is licensed under the 3-clause BSD license.
 *   Copyright ETH Zurich, Laboratory of Physical Chemistry, Reiher Group.
 *   See LICENSE.txt for details.
 */

#include <boost/test/unit_test.hpp>

#include "Molassembler/Temple/Optimization/TrustRegion.h"
using namespace Scine::Molassembler;

struct Himmelblau {
  double operator() (const Eigen::VectorXd& parameters) {
    assert(parameters.size() == 2);

    const double x = parameters(0);
    const double y = parameters(1);

    const double firstBracket = (x * x + y - 11);
    const double secondBracket = (x + y * y - 7);

    return firstBracket * firstBracket + secondBracket * secondBracket;
  }

  void operator() (
    const Eigen::VectorXd& parameters,
    double& value,
    Eigen::Ref<Eigen::VectorXd> gradient,
    Eigen::Ref<Eigen::MatrixXd> hessian
  ) {
    assert(parameters.size() == 2);
    assert(gradient.size() == 2);
    assert(hessian.cols() == 2 && hessian.rows() == 2);

    const double x = parameters(0);
    const double y = parameters(1);

    const double firstBracket = (x * x + y - 11);
    const double secondBracket = (x + y * y - 7);

    value = firstBracket * firstBracket + secondBracket * secondBracket;

    gradient(0) = 4 * x * firstBracket + 2 * secondBracket;
    gradient(1) = 4 * y * secondBracket + 2 * firstBracket;

    hessian(0, 0) = 8 * x * x + 4 * firstBracket + 2;
    hessian(1, 1) = 8 * y * y + 4 * secondBracket + 2;
    hessian(0, 1) = 4 * x + 4 * y;
    hessian(1, 0) = hessian(0, 1);
  }

  bool shouldContinue(
    const unsigned iteration,
    const double /* value */,
    const Eigen::VectorXd& gradient
  ) {
    return (
      iteration <= 1000
      && gradient.squaredNorm() > 1e-3
    );
  }
};

BOOST_AUTO_TEST_CASE(TrustRegionNewton, *boost::unit_test::label("Temple")) {
  Eigen::VectorXd parameters = Eigen::VectorXd::Random(2);
  Eigen::VectorXd passParameters = parameters;

  auto optimizationResult = Temple::TrustRegionOptimizer<>::minimize(
    parameters,
    Himmelblau {},
    Himmelblau {}
  );

  BOOST_CHECK_MESSAGE(
    std::fabs(optimizationResult.value) <= 1e-5,
    "Newton-Raphson trust region does not find minimization of Himmelblau function, value is "
    << optimizationResult.value << " after " << optimizationResult.iterations
    << " iterations at " << parameters.transpose() << ". Gradient norm is " << optimizationResult.gradient.norm()
  );
}


// Copyright (c) 2020 Chris Richardson & Matthew Scroggs
// FEniCS Project
// SPDX-License-Identifier:    MIT

#include "nedelec.h"
#include "dof-permutations.h"
#include "lagrange.h"
#include "moments.h"
#include "polyset.h"
#include "quadrature.h"
#include "raviart-thomas.h"
#include <Eigen/Dense>
#include <numeric>
#include <vector>

using namespace libtab;

namespace
{
//-----------------------------------------------------------------------------
Eigen::MatrixXd create_nedelec_2d_space(int degree)
{
  // Number of order (degree) vector polynomials
  const int nv = degree * (degree + 1) / 2;

  // Number of order (degree-1) vector polynomials
  const int ns0 = (degree - 1) * degree / 2;

  // Number of additional polynomials in Nedelec set
  const int ns = degree;

  // Tabulate polynomial set at quadrature points
  auto [Qpts, Qwts]
      = quadrature::make_quadrature(cell::type::triangle, 2 * degree);
  Eigen::ArrayXXd Pkp1_at_Qpts
      = polyset::tabulate(cell::type::triangle, degree, 0, Qpts)[0];

  const int psize = Pkp1_at_Qpts.cols();

  // Create coefficients for order (degree-1) vector polynomials
  Eigen::MatrixXd wcoeffs = Eigen::MatrixXd::Zero(nv * 2 + ns, psize * 2);
  wcoeffs.block(0, 0, nv, nv) = Eigen::MatrixXd::Identity(nv, nv);
  wcoeffs.block(nv, psize, nv, nv) = Eigen::MatrixXd::Identity(nv, nv);

  // Create coefficients for the additional Nedelec polynomials
  for (int i = 0; i < ns; ++i)
  {
    for (int k = 0; k < psize; ++k)
    {
      wcoeffs(2 * nv + i, k) = (Qwts * Pkp1_at_Qpts.col(ns0 + i) * Qpts.col(1)
                                * Pkp1_at_Qpts.col(k))
                                   .sum();
      wcoeffs(2 * nv + i, k + psize) = (-Qwts * Pkp1_at_Qpts.col(ns0 + i)
                                        * Qpts.col(0) * Pkp1_at_Qpts.col(k))
                                           .sum();
    }
  }

  return wcoeffs;
}
//-----------------------------------------------------------------------------
Eigen::MatrixXd create_nedelec_2d_dual(int degree)
{
  // Number of dofs and size of polynomial set P(k+1)
  const int ndofs = 3 * degree + degree * (degree - 1);
  const int psize = (degree + 1) * (degree + 2) / 2;

  // Dual space
  Eigen::MatrixXd dual = Eigen::MatrixXd::Zero(ndofs, psize * 2);

  // dof counter
  const int quad_deg = 5 * degree;

  // Integral representation for the boundary (edge) dofs
  dual.block(0, 0, 3 * degree, psize * 2)
      = moments::make_tangent_integral_moments(
          create_dlagrange(cell::type::interval, degree - 1),
          cell::type::triangle, 2, degree, quad_deg);

  if (degree > 1)
  {
    // Interior integral moment
    dual.block(3 * degree, 0, degree * (degree - 1), psize * 2)
        = moments::make_integral_moments(
            create_dlagrange(cell::type::triangle, degree - 2),
            cell::type::triangle, 2, degree, quad_deg);
  }

  return dual;
}
//-----------------------------------------------------------------------------
std::vector<Eigen::MatrixXd> create_nedelec_2d_base_perms(int degree)
{
  const int ndofs = degree * (degree + 2);
  std::vector<Eigen::MatrixXd> base_permutations(
      3, Eigen::MatrixXd::Identity(ndofs, ndofs));

  Eigen::ArrayXi edge_ref = dofperms::interval_reflection(degree);
  for (int edge = 0; edge < 3; ++edge)
  {
    const int start = edge_ref.size() * edge;
    for (int i = 0; i < edge_ref.size(); ++i)
    {
      base_permutations[edge](start + i, start + i) = 0;
      base_permutations[edge](start + i, start + edge_ref[i]) = 1;
    }
  }

  Eigen::ArrayXXd edge_dir
      = dofperms::interval_reflection_tangent_directions(degree);
  for (int edge = 0; edge < 3; ++edge)
  {
    Eigen::MatrixXd directions = Eigen::MatrixXd::Identity(ndofs, ndofs);
    directions.block(edge_dir.rows() * edge, edge_dir.cols() * edge,
                     edge_dir.rows(), edge_dir.cols())
        = edge_dir;
    base_permutations[edge] *= directions;
  }

  return base_permutations;
}
//-----------------------------------------------------------------------------
Eigen::MatrixXd create_nedelec_3d_space(int degree)
{
  // Reference tetrahedron
  const int tdim = 3;

  // Number of order (degree) vector polynomials
  const int nv = degree * (degree + 1) * (degree + 2) / 6;

  // Number of order (degree-1) vector polynomials
  const int ns0 = (degree - 1) * degree * (degree + 1) / 6;
  // Number of additional Nedelec polynomials that could be added
  const int ns = degree * (degree + 1) / 2;
  // Number of polynomials that would be included that are not independent so
  // are removed
  const int ns_remove = degree * (degree - 1) / 2;

  // Number of dofs in the space, ie size of polynomial set
  const int ndofs = 6 * degree + 4 * degree * (degree - 1)
                    + (degree - 2) * (degree - 1) * degree / 2;

  // Tabulate polynomial basis at quadrature points
  auto [Qpts, Qwts]
      = quadrature::make_quadrature(cell::type::tetrahedron, 2 * degree);
  Eigen::ArrayXXd Pkp1_at_Qpts
      = polyset::tabulate(cell::type::tetrahedron, degree, 0, Qpts)[0];
  const int psize = Pkp1_at_Qpts.cols();

  // Create coefficients for order (degree-1) polynomials
  Eigen::MatrixXd wcoeffs = Eigen::MatrixXd::Zero(ndofs, psize * tdim);
  for (int i = 0; i < tdim; ++i)
  {
    wcoeffs.block(nv * i, psize * i, nv, nv)
        = Eigen::MatrixXd::Identity(nv, nv);
  }

  // Create coefficients for additional Nedelec polynomials
  for (int i = 0; i < ns; ++i)
  {
    for (int k = 0; k < psize; ++k)
    {
      const double w = (Qwts * Pkp1_at_Qpts.col(ns0 + i) * Qpts.col(2)
                        * Pkp1_at_Qpts.col(k))
                           .sum();
      // Don't include polynomials (*, *, 0) that are dependant
      if (i >= ns_remove)
        wcoeffs(tdim * nv + i - ns_remove, psize + k) = -w;
      wcoeffs(tdim * nv + i + ns - ns_remove, k) = w;
    }
  }

  for (int i = 0; i < ns; ++i)
  {
    for (int k = 0; k < psize; ++k)
    {
      const double w = (Qwts * Pkp1_at_Qpts.col(ns0 + i) * Qpts.col(1)
                        * Pkp1_at_Qpts.col(k))
                           .sum();
      wcoeffs(tdim * nv + i + ns * 2 - ns_remove, k) = -w;
      // Don't include polynomials (*, *, 0) that are dependant
      if (i >= ns_remove)
        wcoeffs(tdim * nv + i - ns_remove, psize * 2 + k) = w;
    }
  }

  for (int i = 0; i < ns; ++i)
  {
    for (int k = 0; k < psize; ++k)
    {
      const double w = (Qwts * Pkp1_at_Qpts.col(ns0 + i) * Qpts.col(0)
                        * Pkp1_at_Qpts.col(k))
                           .sum();
      wcoeffs(tdim * nv + i + ns - ns_remove, psize * 2 + k) = -w;
      wcoeffs(tdim * nv + i + ns * 2 - ns_remove, psize + k) = w;
    }
  }

  return wcoeffs;
}
//-----------------------------------------------------------------------------
Eigen::MatrixXd create_nedelec_3d_dual(int degree)
{
  const int tdim = 3;

  // Size of polynomial set P(k+1)
  const int psize = (degree + 1) * (degree + 2) * (degree + 3) / 6;

  // Work out number of dofs
  const int ndofs = 6 * degree + 4 * degree * (degree - 1)
                    + (degree - 2) * (degree - 1) * degree / 2;
  Eigen::MatrixXd dual = Eigen::MatrixXd::Zero(ndofs, psize * tdim);

  // Create quadrature scheme on the edge
  const int quad_deg = 5 * degree;

  // Integral representation for the boundary (edge) dofs
  dual.block(0, 0, 6 * degree, psize * 3)
      = moments::make_tangent_integral_moments(
          create_dlagrange(cell::type::interval, degree - 1),
          cell::type::tetrahedron, 3, degree, quad_deg);

  if (degree > 1)
  {
    // Integral moments on faces
    dual.block(6 * degree, 0, 4 * (degree - 1) * degree, psize * 3)
        = moments::make_integral_moments(
            create_dlagrange(cell::type::triangle, degree - 2),
            cell::type::tetrahedron, 3, degree, quad_deg);
  }

  if (degree > 2)
  {
    // Interior integral moment
    dual.block(6 * degree + 4 * degree * (degree - 1), 0,
               (degree - 2) * (degree - 1) * degree / 2, psize * 3)
        = moments::make_integral_moments(
            create_dlagrange(cell::type::tetrahedron, degree - 3),
            cell::type::tetrahedron, 3, degree, quad_deg);
  }

  return dual;
}
//-----------------------------------------------------------------------------
std::vector<Eigen::MatrixXd> create_nedelec_3d_base_perms(int degree)
{
  const int ndofs = 6 * degree + 4 * degree * (degree - 1)
                    + (degree - 2) * (degree - 1) * degree / 2;
  std::vector<Eigen::MatrixXd> base_permutations(
      14, Eigen::MatrixXd::Identity(ndofs, ndofs));

  Eigen::ArrayXi edge_ref = dofperms::interval_reflection(degree);
  for (int edge = 0; edge < 6; ++edge)
  {
    const int start = edge_ref.size() * edge;
    for (int i = 0; i < edge_ref.size(); ++i)
    {
      base_permutations[edge](start + i, start + i) = 0;
      base_permutations[edge](start + i, start + edge_ref[i]) = 1;
    }
  }
  Eigen::ArrayXi face_rot = dofperms::triangle_rotation(degree - 1);
  Eigen::ArrayXi face_ref = dofperms::triangle_reflection(degree - 1);
  for (int face = 0; face < 4; ++face)
  {
    const int start = edge_ref.size() * 6 + face_ref.size() * 2 * face;
    for (int i = 0; i < face_rot.size(); ++i)
    {
      for (int b = 0; b < 2; ++b)
      {
        const int p = 6 + 2 * face;
        const int p1 = start + 2 * i + b;
        base_permutations[p](p1, start + i * 2 + b) = 0;
        base_permutations[p](p1, start + face_rot[i] * 2 + b) = 1;
        base_permutations[p + 1](p1, start + i * 2 + b) = 0;
        base_permutations[p + 1](p1, start + face_ref[i] * 2 + b) = 1;
      }
    }
  }

  Eigen::ArrayXXd edge_dir
      = dofperms::interval_reflection_tangent_directions(degree);
  for (int edge = 0; edge < 6; ++edge)
  {
    Eigen::MatrixXd directions = Eigen::MatrixXd::Identity(ndofs, ndofs);
    directions.block(edge_dir.rows() * edge, edge_dir.cols() * edge,
                     edge_dir.rows(), edge_dir.cols())
        = edge_dir;
    base_permutations[edge] *= directions;
  }

  // Faces
  Eigen::ArrayXXd face_dir_ref
      = dofperms::triangle_reflection_tangent_directions(degree - 1);
  Eigen::ArrayXXd face_dir_rot
      = dofperms::triangle_rotation_tangent_directions(degree - 1);
  for (int face = 0; face < 4; ++face)
  {
    // Rotate face
    Eigen::MatrixXd rotation = Eigen::MatrixXd::Identity(ndofs, ndofs);
    rotation.block(edge_dir.rows() * 6 + face_dir_rot.rows() * face,
                   edge_dir.cols() * 6 + face_dir_rot.rows() * face,
                   face_dir_rot.rows(), face_dir_rot.cols())
        = face_dir_rot;
    base_permutations[6 + 2 * face] *= rotation;

    // Reflect face
    Eigen::MatrixXd reflection = Eigen::MatrixXd::Identity(ndofs, ndofs);
    reflection.block(edge_dir.rows() * 6 + face_dir_ref.rows() * face,
                     edge_dir.cols() * 6 + face_dir_ref.rows() * face,
                     face_dir_ref.rows(), face_dir_ref.cols())
        = face_dir_ref;
    base_permutations[6 + 2 * face + 1] *= reflection;
  }

  return base_permutations;
}

//-----------------------------------------------------------------------------
Eigen::MatrixXd create_nedelec2_2d_dual(int degree)
{
  // Number of dofs and size of polynomial set P(k+1)
  const int ndofs = (degree + 1) * (degree + 2);
  const int psize = (degree + 1) * (degree + 2) / 2;

  // Dual space
  Eigen::MatrixXd dual = Eigen::MatrixXd::Zero(ndofs, psize * 2);

  // dof counter
  int quad_deg = 5 * degree;

  // Integral representation for the boundary (edge) dofs
  dual.block(0, 0, 3 * (degree + 1), psize * 2)
      = moments::make_tangent_integral_moments(
          create_dlagrange(cell::type::interval, degree), cell::type::triangle,
          2, degree, quad_deg);

  if (degree > 1)
  {
    // Interior integral moment
    dual.block(3 * (degree + 1), 0, (degree - 1) * (degree + 1), psize * 2)
        = moments::make_dot_integral_moments(
            create_rt(cell::type::triangle, degree - 1), cell::type::triangle,
            2, degree, quad_deg);
  }

  return dual;
}
//-----------------------------------------------------------------------------
Eigen::MatrixXd create_nedelec2_3d_dual(int degree)
{
  const int tdim = 3;

  // Size of polynomial set P(k+1)
  const int psize = (degree + 1) * (degree + 2) * (degree + 3) / 6;

  // Work out number of dofs
  const int ndofs = (degree + 1) * (degree + 2) * (degree + 3) / 2;

  Eigen::MatrixXd dual = Eigen::MatrixXd::Zero(ndofs, psize * tdim);

  // Create quadrature scheme on the edge
  int quad_deg = 5 * degree;

  // Integral representation for the boundary (edge) dofs
  dual.block(0, 0, 6 * (degree + 1), psize * 3)
      = moments::make_tangent_integral_moments(
          create_dlagrange(cell::type::interval, degree),
          cell::type::tetrahedron, 3, degree, quad_deg);

  if (degree > 1)
  {
    // Integral moments on faces
    dual.block(6 * (degree + 1), 0, 4 * (degree - 1) * (degree + 1), psize * 3)
        = moments::make_dot_integral_moments(
            create_rt(cell::type::triangle, degree - 1),
            cell::type::tetrahedron, 3, degree, quad_deg);
  }

  if (degree > 2)
  {
    // Interior integral moment
    dual.block((6 + 4 * (degree - 1)) * (degree + 1), 0,
               (degree - 1) * (degree - 2) * (degree + 1) / 2, psize * 3)
        = moments::make_integral_moments(
            create_dlagrange(cell::type::tetrahedron, degree - 2),
            cell::type::tetrahedron, 3, degree, quad_deg);
  }

  return dual;
}

} // namespace

//-----------------------------------------------------------------------------
FiniteElement libtab::create_nedelec(cell::type celltype, int degree,
                                     const std::string& name)
{
  Eigen::MatrixXd wcoeffs;
  Eigen::MatrixXd dual;
  std::vector<Eigen::MatrixXd> perms;
  std::vector<Eigen::MatrixXd> directions;
  if (celltype == cell::type::triangle)
  {
    wcoeffs = create_nedelec_2d_space(degree);
    dual = create_nedelec_2d_dual(degree);
    perms = create_nedelec_2d_base_perms(degree);
  }
  else if (celltype == cell::type::tetrahedron)
  {
    wcoeffs = create_nedelec_3d_space(degree);
    dual = create_nedelec_3d_dual(degree);
    perms = create_nedelec_3d_base_perms(degree);
  }
  else
    throw std::runtime_error("Invalid celltype in Nedelec");

  // Nedelec has d dofs on each edge, d(d-1) on each face
  // and d(d-1)(d-2)/2 on the interior in 3D
  const std::vector<std::vector<std::vector<int>>> topology
      = cell::topology(celltype);
  std::vector<std::vector<int>> entity_dofs(topology.size());
  entity_dofs[0].resize(topology[0].size(), 0);
  entity_dofs[1].resize(topology[1].size(), degree);
  entity_dofs[2].resize(topology[2].size(), degree * (degree - 1));
  const int tdim = cell::topological_dimension(celltype);
  if (tdim > 2)
    entity_dofs[3] = {degree * (degree - 1) * (degree - 2) / 2};

  const Eigen::MatrixXd coeffs = compute_expansion_coefficients(wcoeffs, dual);
  return FiniteElement(name, celltype, degree, {tdim}, coeffs, entity_dofs,
                       perms);
}
//-----------------------------------------------------------------------------
FiniteElement libtab::create_nedelec2(cell::type celltype, int degree,
                                      const std::string& name)
{
  const int tdim = cell::topological_dimension(celltype);
  const int psize = polyset::dim(celltype, degree);
  Eigen::MatrixXd wcoeffs
      = Eigen::MatrixXd::Identity(tdim * psize, tdim * psize);

  Eigen::MatrixXd dual;
  if (celltype == cell::type::triangle)
    dual = create_nedelec2_2d_dual(degree);
  else if (celltype == cell::type::tetrahedron)
    dual = create_nedelec2_3d_dual(degree);
  else
    throw std::runtime_error("Invalid celltype in Nedelec");

  // TODO: Implement base permutations
  const std::vector<std::vector<std::vector<int>>> topology
      = cell::topology(celltype);
  const int ndofs = dual.rows();
  int perm_count = 0;
  for (int i = 1; i < tdim; ++i)
    perm_count += topology[i].size() * i;
  std::vector<Eigen::MatrixXd> base_permutations(
      perm_count, Eigen::MatrixXd::Identity(ndofs, ndofs));

  const Eigen::MatrixXd coeffs = compute_expansion_coefficients(wcoeffs, dual);

  // Nedelec(2nd kind) has (d+1) dofs on each edge, (d+1)(d-1) on each face
  // and (d-2)(d-1)(d+1)/2 on the interior in 3D
  std::vector<std::vector<int>> entity_dofs(topology.size());
  entity_dofs[0].resize(topology[0].size(), 0);
  entity_dofs[1].resize(topology[1].size(), degree + 1);
  entity_dofs[2].resize(topology[2].size(), (degree + 1) * (degree - 1));
  if (tdim > 2)
    entity_dofs[3] = {(degree - 2) * (degree - 1) * (degree + 1) / 2};

  return FiniteElement(name, celltype, degree, {tdim}, coeffs, entity_dofs,
                       base_permutations);
}
//-----------------------------------------------------------------------------


#pragma once

#include <boost/multiprecision/gmp.hpp>
#include <math.h>
#include <time.h>

#include "FiniteFields.hpp"
#include "FastFourierTransform.hpp"

namespace ligero {
template <typename FieldT>
class 		SecretSharing {
	public:
		/* Constructors allowing some flexibility in the choice of domains */
		SecretSharing(size_t lSecretLength, size_t kDegree, size_t nShares) : lSecretLength_(lSecretLength), kDegree_(kDegree), nNumberShares_(nShares) {
			// In this implementation, we use an evaluation domain that is twice the size of the number of shares, and we discard
			this->dCompositeDomainSize_ = nShares;
		};

		int share(FieldT *secret);
		int reconstruct(FieldT *eval, bool expanded = false);

		int padMany(FieldT *secrets, size_t lrows);
		int padIntrablocRandomnessMany(FieldT *secret, size_t lrows);
		int shareMany(FieldT *secrets, size_t lrows);
		int reconstructMany(FieldT *secrets, size_t lrows);

		bool degreeTest(FieldT *eval);
		bool zeroTest(FieldT *eval, bool expanded = false);
		bool zeroSumTest(FieldT *eval);

	protected:
		int padSecret(FieldT *secret);
		int padPolynominal(FieldT *secret);
		int padShares(FieldT *secret);
		int padIntrablocRandomness(FieldT *secret);

		size_t	lSecretLength_;
		size_t	kDegree_;
		size_t	nNumberShares_;
		size_t	dCompositeDomainSize_;
		std::vector<FieldT> rootsOfUnity_;
};

template <typename FieldT>
int SecretSharing<FieldT>::padSecret(FieldT *secret) {
	FieldT::randomVector(secret + this->lSecretLength_, this->kDegree_ - this->lSecretLength_, true);
	return 0;
}

template <typename FieldT>
int SecretSharing<FieldT>::padIntrablocRandomness(FieldT *secret) {
	for (size_t i = this->lSecretLength_; i < kDegree_ ;i++) {
		secret[i] = FieldT(0);
	}

	return 0;
}

template <typename FieldT>
int SecretSharing<FieldT>::padPolynominal(FieldT *secret) {
	for (size_t i = this->kDegree_; i < nNumberShares_;i++) {
		secret[i] = FieldT(0);
	}

	return 0;
}

template <typename FieldT>
int SecretSharing<FieldT>::padShares(FieldT *secret) {
	for (size_t i = this->nNumberShares_; i < dCompositeDomainSize_ ;i++) {
		secret[i] = FieldT(0);
	}

	return 0;
}

template <typename FieldT>
int SecretSharing<FieldT>::padMany(FieldT *secret, size_t lrows) {

	// Add randomness for padding
	for (size_t i = 0; i < lrows; i++) {
		this->padSecret(secret + i * this->nNumberShares_);
	}

	return 0;
}

template <typename FieldT>
int SecretSharing<FieldT>::padIntrablocRandomnessMany(FieldT *secret, size_t lrows) {

	// Add randomness for padding
	for (size_t i = 0; i < lrows; i++) {
		this->padIntrablocRandomness(secret + i * this->nNumberShares_);
	}

	return 0;
}

template <typename FieldT>
int SecretSharing<FieldT>::share(FieldT *secret) {

	// Interpolate a polynomial of degree k for the secret
    iFFT<FieldT>(secret, kDegree_, rootsOfUnity_);

	// Evaluate the polynomial on a coset within the composite domain (size numberShares)
	this->padPolynominal(secret);
	FFT<FieldT>(secret, nNumberShares_, FieldT(FieldT::getOmega(nNumberShares_)), rootsOfUnity_);

	return 0;
}

template <typename FieldT>
inline int SecretSharing<FieldT>::reconstruct(FieldT *eval, bool expandedDegree) {
	size_t degree = kDegree_;
	iFFT<FieldT>(eval, nNumberShares_, FieldT(FieldT::getOmega(nNumberShares_)), rootsOfUnity_);

	if (expandedDegree) {
		for (size_t i = 0; i < kDegree_; i++) {eval[i] = eval[i] + eval[i + kDegree_];}
	}

    FFT<FieldT>(eval, degree, rootsOfUnity_);

	return 0;
}

template <typename FieldT>
bool SecretSharing<FieldT>::degreeTest(FieldT *eval) {

	std::vector<FieldT> localCopy(eval, eval + this->nNumberShares_);
	iFFT<FieldT>(&localCopy[0], nNumberShares_, FieldT::getOmega(nNumberShares_), rootsOfUnity_);

	for (size_t i = kDegree_; i < nNumberShares_; i++) {
		if (!(localCopy[i] == FieldT(0))) {
			DBG("val:"<< localCopy[i].getValue() << ",degree>" << i);
			return false;
		}
	}

	return true;
}

template <typename FieldT>
bool SecretSharing<FieldT>::zeroTest(FieldT *eval, bool largerDegree) {

	std::vector<FieldT> localCopy(eval, eval + this->nNumberShares_);
	reconstruct(&localCopy[0], largerDegree);

	for (size_t i = 0; i < this->lSecretLength_; i++) {
		if (!(localCopy[i] == FieldT(0))) return false;
	}

	return true;
}

template <typename FieldT>
bool SecretSharing<FieldT>::zeroSumTest(FieldT *eval) {

	std::vector<FieldT> localCopy(eval, eval+ this->nNumberShares_);
	reconstruct(&localCopy[0], true);

	FieldT sum = FieldT(0);
	for (size_t i = 0; i < this->lSecretLength_; i++) {
		sum += localCopy[i];
	}

	if (sum == FieldT(0)) return true;
	else return false;
}

template <typename FieldT>
int SecretSharing<FieldT>::shareMany(FieldT *secret, size_t lrows) {

	// std::cout << "Performing FFTs: " << lrows << std::endl;
	for (size_t i = 0; i < lrows; i++) {
		share(secret + i * this->nNumberShares_);
	}

	return 0;
}

template <typename FieldT>
int SecretSharing<FieldT>::reconstructMany(FieldT *secret, size_t lrows) {

	for (size_t i = 0; i < lrows; i++) {
		reconstruct(secret + i * this->nNumberShares_);
	}

	return 0;
}

}


// error_policies_example.cpp

// Copyright Paul A. Bristow 2007.
// Copyright John Maddock 2007.

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

#include <boost/math/distributions/normal.hpp>
  using boost::math::normal_distribution;

#include <boost/math/distributions/students_t.hpp>
   using boost::math::students_t;  // Probability of students_t(df, t).
   using boost::math::students_t_distribution;

//  using namespace boost::math;
//.\error_policy_normal.cpp(30) : error C2872: 'policy' : ambiguous symbol
//        could be 'I:\Boost-sandbox\math_toolkit\boost/math/policies/policy.hpp(392) : boost::math::policies::policy'
//        or 'boost::math::policies'

  // So can't use this using namespace command.
// Suppose we want a statistical distribution to return infinities,
// rather than throw exceptions (the default policy), then we can use:

// std
#include <iostream>
   using std::cout;
   using std::endl;

using boost::math::policies::policy;
// Possible errors
using boost::math::policies::overflow_error;
using boost::math::policies::underflow_error;
using boost::math::policies::domain_error;
using boost::math::policies::pole_error;
using boost::math::policies::denorm_error;
using boost::math::policies::evaluation_error;

using boost::math::policies::ignore_error;

// Define a custom policy to ignore just overflow:
typedef policy<
overflow_error<ignore_error>
      > my_policy;

// Define another custom policy (perhaps ill-advised?)
// to ignore all errors: domain, pole, overflow, underflow, denorm & evaluation:
typedef policy<
domain_error<ignore_error>,
pole_error<ignore_error>,
overflow_error<ignore_error>,
underflow_error<ignore_error>,
denorm_error<ignore_error>,
evaluation_error<ignore_error>
      > my_ignoreall_policy;

// Define a new distribution with a custom policy to ignore_error
// (& thus perhaps return infinity for some arguments):
typedef boost::math::normal_distribution<double, my_policy> my_normal;
// Note: uses default parameters zero mean and unit standard deviation.

// We could also do the same for another distribution, for example:
using boost::math::students_t_distribution;
typedef students_t_distribution<double, my_ignoreall_policy> my_students_t;

int main()
{
  cout << "quantile(my_normal(), 0.05); = " << quantile(my_normal(), 0.05) << endl; // 0.05 is argument within normal range.
  cout << "quantile(my_normal(), 0.); = " << quantile(my_normal(), 0.) << endl; // argument zero, so expect infinity.
  cout << "quantile(my_normal(), 0.); = " << quantile(my_normal(), 0.F) << endl; // argument zero, so expect infinity.

  cout << "quantile(my_students_t(), 0.); = " << quantile(my_students_t(-1), 0.F) << endl; // 'bad' argument negative, so expect NaN.

#ifndef BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS
  // Construct a (0, 1) normal distribution that ignores all errors,
  // returning NaN, infinity, zero, or best guess,
  // and NOT setting errno.
  normal_distribution<long double, my_ignoreall_policy> my_normal2(0.L, 1.L); // explicit parameters for distribution.
  cout << "quantile(my_normal2(), 0.); = " << quantile(my_normal2, 0.01) << endl; // argument 0.01, so result finite.
  cout << "quantile(my_normal2(), 0.); = " << quantile(my_normal2, 0.) << endl; // argument zero, so expect infinity.
#endif

  return 0;
}

/*

Output:

quantile(my_normal(), 0.05); = -1.64485
quantile(my_normal(), 0.); = -1.#INF
quantile(my_normal(), 0.); = -1.#INF
quantile(my_students_t(), 0.); = 1.#QNAN
quantile(my_normal2(), 0.); = -2.32635
quantile(my_normal2(), 0.); = -1.#INF

*/


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

// Test case for weighted_extended_p_square.hpp

#include <iostream>
#include <boost/random.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>
#include <boost/accumulators/numeric/functional/vector.hpp>
#include <boost/accumulators/numeric/functional/complex.hpp>
#include <boost/accumulators/numeric/functional/valarray.hpp>
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/weighted_extended_p_square.hpp>

using namespace boost;
using namespace unit_test;
using namespace boost::accumulators;

///////////////////////////////////////////////////////////////////////////////
// test_stat
//
void test_stat()
{
    typedef accumulator_set<double, stats<tag::weighted_extended_p_square>, double> accumulator_t;

    // problem with small results: epsilon is relative (in percent), not absolute

    // tolerance in %
    double epsilon = 1;

    // some random number generators
    double mu1 = -1.0;
    double mu2 =  1.0;
    boost::lagged_fibonacci607 rng;
    boost::normal_distribution<> mean_sigma1(mu1, 1);
    boost::normal_distribution<> mean_sigma2(mu2, 1);
    boost::variate_generator<boost::lagged_fibonacci607&, boost::normal_distribution<> > normal1(rng, mean_sigma1);
    boost::variate_generator<boost::lagged_fibonacci607&, boost::normal_distribution<> > normal2(rng, mean_sigma2);

    std::vector<double> probs_uniform, probs_normal1, probs_normal2, probs_normal_exact1, probs_normal_exact2;

    double p1[] = {/*0.001,*/ 0.01, 0.1, 0.5, 0.9, 0.99, 0.999};
    probs_uniform.assign(p1, p1 + sizeof(p1) / sizeof(double));

    double p2[] = {0.001, 0.025};
    double p3[] = {0.975, 0.999};
    probs_normal1.assign(p2, p2 + sizeof(p2) / sizeof(double));
    probs_normal2.assign(p3, p3 + sizeof(p3) / sizeof(double));

    double p4[] = {-3.090232, -1.959963};
    double p5[] = {1.959963, 3.090232};
    probs_normal_exact1.assign(p4, p4 + sizeof(p4) / sizeof(double));
    probs_normal_exact2.assign(p5, p5 + sizeof(p5) / sizeof(double));

    accumulator_t acc_uniform(extended_p_square_probabilities = probs_uniform);
    accumulator_t acc_normal1(extended_p_square_probabilities = probs_normal1);
    accumulator_t acc_normal2(extended_p_square_probabilities = probs_normal2);

    for (std::size_t i = 0; i < 100000; ++i)
    {
        acc_uniform(rng(), weight = 1.);

        double sample1 = normal1();
        double sample2 = normal2();
        acc_normal1(sample1, weight = std::exp(-mu1 * (sample1 - 0.5 * mu1)));
        acc_normal2(sample2, weight = std::exp(-mu2 * (sample2 - 0.5 * mu2)));
    }

    // check for uniform distribution
    BOOST_CHECK_CLOSE(weighted_extended_p_square(acc_uniform)[0], probs_uniform[0], 6*epsilon);
    BOOST_CHECK_CLOSE(weighted_extended_p_square(acc_uniform)[1], probs_uniform[1], 3*epsilon);
    BOOST_CHECK_CLOSE(weighted_extended_p_square(acc_uniform)[2], probs_uniform[2], epsilon);
    BOOST_CHECK_CLOSE(weighted_extended_p_square(acc_uniform)[3], probs_uniform[3], epsilon);
    BOOST_CHECK_CLOSE(weighted_extended_p_square(acc_uniform)[4], probs_uniform[4], epsilon);
    BOOST_CHECK_CLOSE(weighted_extended_p_square(acc_uniform)[5], probs_uniform[5], epsilon);

    // check for standard normal distribution
    for (std::size_t i = 0; i < probs_normal1.size(); ++i)
    {
        BOOST_CHECK_CLOSE(weighted_extended_p_square(acc_normal1)[i], probs_normal_exact1[i], epsilon);
        BOOST_CHECK_CLOSE(weighted_extended_p_square(acc_normal2)[i], probs_normal_exact2[i], epsilon);
    }
}

///////////////////////////////////////////////////////////////////////////////
// init_unit_test_suite
//
test_suite* init_unit_test_suite( int argc, char* argv[] )
{
    test_suite *test = BOOST_TEST_SUITE("weighted_extended_p_square test");

    test->add(BOOST_TEST_CASE(&test_stat));

    return test;
}


#include "catch.hpp"

#include "FringeGraph.h"
#include "FringeSearch.h"

#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/erdos_renyi_generator.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/random/linear_congruential.hpp>
#include <boost/random/random_device.hpp>

#include <cmath>

// Number of graphs to compare shortest paths on
static const unsigned int NUM_TEST_GRAPHS = 1000;
// Number of nodes per test graph
static const unsigned int NODES_PER_TEST_GRAPH = 1000;
// The Erdos-Renyi input parameter
static const float ER_PARAMETER = 0.005;

typedef boost::adjacency_list< boost::listS, boost::vecS, boost::directedS, boost::no_property, boost::property < boost::edge_weight_t, float > > graph_t;
typedef boost::erdos_renyi_iterator<boost::minstd_rand, graph_t> er_generator_t;
typedef boost::graph_traits < graph_t >::vertex_descriptor vertex_descriptor;

TEST_CASE("Fringe search returns the same paths as Boost's Dijkstra implementation on random graphs") {

    boost::random_device rd;
    boost::minstd_rand gen(rd);
    SECTION("Generate a random graph and compare paths") {
        for (unsigned int i = 0; i < NUM_TEST_GRAPHS; i++) {
            graph_t g(er_generator_t(gen, NODES_PER_TEST_GRAPH, ER_PARAMETER), er_generator_t(), NODES_PER_TEST_GRAPH);

            // Set random weights
            auto unweightedEdges = boost::edges(g);
            for (auto eit = unweightedEdges.first; eit != unweightedEdges.second; eit++) {
                float weight = 10.0f * (gen() - gen.min()) / (gen.max() - gen.min());
                boost::put(boost::edge_weight_t(), g, *eit, weight);
            }

            std::vector<vertex_descriptor> predecessors(num_vertices(g));
            std::vector<float> distances(num_vertices(g));
            vertex_descriptor source(boost::vertex(0, g));
            boost::dijkstra_shortest_paths(g, source,
                                           boost::predecessor_map(&predecessors[0]).distance_map(&distances[0]));

            std::vector<vertex_descriptor> path;
            vertex_descriptor target(boost::vertex(NODES_PER_TEST_GRAPH - 1, g));

            // Check that the target was reachable
            if (predecessors[target] != target) {
                vertex_descriptor current = target;
                while (current != source) {
                    path.push_back(current);
                    current = predecessors[current];
                }
            }

            // Convert boost graph to fringe search graph

            // Create nodes
            std::vector<FringeNode<void>* > fringeNodes;
            for (unsigned int n = 0; n < NODES_PER_TEST_GRAPH; n++) {
                fringeNodes.push_back(new FringeNode<void>(n));
            }

            // Convert edges
            auto edges = boost::edges(g);
            edge_id_t currentId = 0;
            for (auto eit = edges.first; eit != edges.second; eit++) {
                float weight = boost::get(boost::edge_weight_t(), g, *eit);
                FringeNode<void> *edgeSource = fringeNodes[(*eit).m_source];
                FringeNode<void> *edgeTarget = fringeNodes[(*eit).m_target];
                FringeEdge<void> *edge = new FringeEdge<void>(currentId++, edgeSource, edgeTarget, weight);
            }

            FringeSearch search(fringeNodes[0]);
            FringeNode<void> * fringeTarget = fringeNodes[NODES_PER_TEST_GRAPH - 1];
            std::vector<BaseFringeNode *> *fringePath = search.search(fringeTarget);

            if (path.size() == 0) {
                // If the target was not reachable, no path should be output
                REQUIRE(fringePath == nullptr);
            } else {
                REQUIRE(fringePath != nullptr);
                float weightDifference = abs(distances[target] - search.cost(fringeTarget));
                std::cout<< weightDifference << std::endl;
                REQUIRE(weightDifference < 1E-6);
            }
        }
    }
}


/*
 * This is part of the fl library, a C++ Bayesian filtering library
 * (https://github.com/filtering-library)
 *
 * Copyright (c) 2015 Max Planck Society,
 * 				 Autonomous Motion Department,
 * 			     Institute for Intelligent Systems
 *
 * This Source Code Form is subject to the terms of the MIT License (MIT).
 * A copy of the license can be found in the LICENSE file distributed with this
 * source code.
 */

/**
 * \file sigma_point_additive_update_policy.hpp
 * \date July 2015
 * \author Jan Issac (jan.issac@gmail.com)
 */

#pragma once

#include <Eigen/Dense>

#include <fl/util/meta.hpp>
#include <fl/util/traits.hpp>
#include <fl/util/descriptor.hpp>
#include <fl/filter/gaussian/transform/point_set.hpp>
#include <fl/filter/gaussian/quadrature/sigma_point_quadrature.hpp>

namespace fl
{

// Forward declarations
template <typename...> class SigmaPointUpdatePolicy;

template <
    typename SigmaPointQuadrature,
    typename AdditiveSensorFunction
>
class SigmaPointUpdatePolicy<
          SigmaPointQuadrature,
          Additive<AdditiveSensorFunction>>
    : public Descriptor
{
public:
    typedef typename AdditiveSensorFunction::State State;
    typedef typename AdditiveSensorFunction::Obsrv Obsrv;

    enum : signed int
    {
        NumberOfPoints =
            SigmaPointQuadrature::number_of_points(SizeOf<State>::Value)
    };

    typedef PointSet<State, NumberOfPoints> StatePointSet;
    typedef PointSet<Obsrv, NumberOfPoints> ObsrvPointSet;

    template <
        typename Belief
    >
    void operator()(const AdditiveSensorFunction& obsrv_function,
                    const SigmaPointQuadrature& quadrature,
                    const Belief& prior_belief,
                    const Obsrv& obsrv,
                    Belief& posterior_belief)
    {
        auto&& h = [&](const State& x)
        {
           return obsrv_function.expected_observation(x);
        };

        quadrature.propergate_gaussian(h, prior_belief, X, Z);

        auto&& prediction = Z.center();
        auto&& Z_c = Z.points();
        auto&& W = X.covariance_weights_vector();
        auto&& X_c = X.centered_points();

        auto innovation = (obsrv - prediction).eval();
        auto cov_xx = (X_c * W.asDiagonal() * X_c.transpose()).eval();
        auto cov_yy = (Z_c * W.asDiagonal() * Z_c.transpose()
                       + obsrv_function.noise_covariance()).eval();
        auto cov_xy = (X_c * W.asDiagonal() * Z_c.transpose()).eval();
        auto K = (cov_xy * cov_yy.inverse()).eval();

        posterior_belief.dimension(prior_belief.dimension());
        posterior_belief.mean(X.mean() + K * innovation);
        posterior_belief.covariance(cov_xx - K * cov_yy * K.transpose());
    }

    virtual std::string name() const
    {
        return "SigmaPointUpdatePolicy<"
                + this->list_arguments(
                       "SigmaPointQuadrature",
                       "Additive<AdditiveSensorFunction>")
                + ">";
    }

    virtual std::string description() const
    {
        return "Sigma Point based filter update policy for observation model"
               " with additive noise";
    }

protected:
    StatePointSet X;
    ObsrvPointSet Z;
};

}


#include <Eigen/Dense>
#include <map>
#include <algorithm>
#include <vector>

int atom(int ao_index)
{
    int orbitals_per_atom=4;
    return ao_index / orbitals_per_atom;
}

int indexOf(std::vector<std::string> my_list, std::string element)
{
    for(size_t i=0;i<my_list.size();i++)
    {
        if(element==my_list[i])
        {
            return i;
        }
    }
    return -1;
}

bool list_contains(std::vector<std::string>  my_list, std::string element)
{
    bool found = 0;
    for(size_t i=0;i<my_list.size();i++)
    {
        if(element==my_list[i])
        {
            found = 1;
        }
    }
    return found;
}

std::string orb(int ao_index, std::vector<std::string> orbital_types)
{
    int orbitals_per_atom=4;
    int orb_index = ao_index % orbitals_per_atom;
    return orbital_types[orb_index];
}

int ao_index(int atom_p, std::string orb_p, std::vector<std::string>  orbital_types)
{
        int orbitals_per_atom=4;
        int p = atom_p * orbitals_per_atom;
        p += indexOf(orbital_types,orb_p);
        return p;
}

float chi_on_atom(std::string o1, std::string o2, std::string o3, std::vector<std::string> p_orbitals,double dipole)
{
    if(o1 == o2 && o3 == "s")
        return 1.0;
    bool o3_in_p_orbitals = list_contains(p_orbitals,o3);
    if((o1 == o3 && o3_in_p_orbitals) && (o2 == "s"))
        return dipole;
    if ((o2 == o3 && o3_in_p_orbitals) && (o1 == "s"))
        return dipole;
    return 0.0;
}

Eigen::MatrixXd fast_fock_matrix(Eigen::MatrixXd hamiltonian, Eigen::MatrixXd interaction, Eigen::MatrixXd rho,double dipole)
{
    std::vector<std::string> orbital_types ={"s","px", "py", "pz"};
    std::vector<std::string>  p_orbitals = {"px", "py", "pz"};
    size_t ndof = hamiltonian.rows();
    Eigen::MatrixXd fock_mat = hamiltonian;
    //Hartree potential term
    for(size_t p=0;p<ndof;p++)
    {
        for(auto orb_q: orbital_types)
        {
            int q = ao_index(atom(p), orb_q,orbital_types); // p & q on same atom
            for(auto orb_t : orbital_types)
            {
                int t = ao_index(atom(p), orb_t,orbital_types); // p & t on same atom
                float chi_pqt = chi_on_atom(orb(p,orbital_types), orb_q, orb_t,p_orbitals,dipole);
                for(size_t r=0;r<ndof;r++)
                {
                    for(auto orb_s : orbital_types)
                    {
                        int s = ao_index(atom(r), orb_s,orbital_types); // r & s on same atom
                        for(auto orb_u : orbital_types)
                        {
                            int u = ao_index(atom(r), orb_u,orbital_types); // r & u on same atom
                            float chi_rsu = chi_on_atom(orb(r,orbital_types), orb_s, orb_u, p_orbitals,dipole);
                            fock_mat(p,q) += 2.0 * chi_pqt * chi_rsu * interaction(t,u) * rho(r,s);
                        }
                    }
                }
            }
        }
    }
    //Fock exchange term
    for(size_t p=0;p<ndof;p++)
    {
        for(auto orb_s: orbital_types)
        {
            int s = ao_index(atom(p), orb_s,orbital_types); // p & s on same atom
            for(auto orb_u : orbital_types)
            {
                int u = ao_index(atom(p), orb_u,orbital_types); //p & u on same atom
                float chi_psu = chi_on_atom(orb(p,orbital_types), orb_s, orb_u,p_orbitals,dipole);
                for(size_t q=0; q<ndof; q++)
                {
                    for(auto orb_r : orbital_types)
                    {
                        int r = ao_index(atom(q), orb_r,orbital_types); // q & r on same atom
                        for(auto orb_t : orbital_types)
                        {
                            int t = ao_index(atom(q), orb_t,orbital_types); //q & t on same atom
                            float chi_rqt = chi_on_atom(orb_r, orb(q,orbital_types), orb_t, p_orbitals,dipole);
                            fock_mat(p,q) -= chi_rqt * chi_psu * interaction(t,u) * rho(r,s);
                        }
                    }
                }
            }
        }
    }
    return fock_mat;

}

int main(void)
{
    return 0;
}


#include <iostream>
#include <boost/multiprecision/cpp_int.hpp>
using namespace std;
using namespace boost::multiprecision;

cpp_int dp[55][4004];

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, k, x;
    while (cin >> k >> n >> x) {
        cpp_int nom = 0;
        if (x <= k * n && x >= n) {
            for (int i = 0; i <= 50; i++)
                for (int j = 0; j <= 4000; j++)
                    dp[i][j] = 0;

            dp[0][0] = 1;

            for (int i = 1; i <= k; i++)
                dp[1][i] = 1;

            for (int i = 2; i <= n; i++) {
                cpp_int ps = 0;
                for (int j = i; j <= k * i; j++) {
                    ps += dp[i - 1][j - 1];
                    if (j - i >= k)ps -= dp[i - 1][j - k - 1];
                    dp[i][j] = ps;
                }
            }
            nom = dp[n][x];
        }

        cpp_int denom = 1;
        for (int i = 1; i <= n; i++)denom *= k;

        cout << nom << "/" << denom << endl;
    }

    return 0;
}

Mathematics/Probability Theory/Standard/Throw the Dice.cpp


/*
Part of the Fluid Corpus Manipulation Project (http://www.flucoma.org/)
Copyright 2017-2019 University of Huddersfield.
Licensed under the BSD-3 License.
See license.md file in the project root for full license information.
This project has received funding from the European Research Council (ERC)
under the European Union’s Horizon 2020 research and innovation programme
(grant agreement No 725899).
*/

#pragma once

#include "FFT.hpp"
#include "FluidEigenMappings.hpp"
#include "../../data/FluidIndex.hpp"
#include "../../data/TensorTypes.hpp"
#include <Eigen/Eigen>
#include <cmath>

namespace fluid {
namespace algorithm {

class TruePeak
{

  using ArrayXcd = Eigen::ArrayXcd;

public:
  TruePeak(index maxSize) : mFFT(maxSize), mIFFT(maxSize * 4) {}

  void init(index size, double sampleRate)
  {
    using namespace std;
    mSampleRate = sampleRate;
    mFFTSize = static_cast<index>(pow(2, ceil(log(size) / log(2))));
    mFactor = sampleRate < 96000 ? 4 : 2;
    mFFT.resize(mFFTSize);
    mIFFT.resize(mFFTSize * mFactor);
    mBuffer = ArrayXcd::Zero((mFFTSize * mFactor / 2) + 1);
  }

  double processFrame(const RealVectorView& input)
  {
    using namespace Eigen;
    ArrayXd in = _impl::asEigen<Array>(input);
    if (mSampleRate >= 192000) { return in.abs().maxCoeff(); }
    else
    {
      double   peak;
      ArrayXcd transform = mFFT.process(in);
      mBuffer.setZero();
      mBuffer.segment(0, transform.size()) = transform;
      ArrayXd result = mIFFT.process(mBuffer);
      ArrayXd scaled = result / mFFTSize;
      peak = scaled.abs().maxCoeff();
      return peak;
    }
  }

private:
  FFT      mFFT;
  IFFT     mIFFT;
  ArrayXcd mBuffer;
  double   mSampleRate{44100.0};
  index    mFactor{4};
  index    mFFTSize{1024};
};
} // namespace algorithm
} // namespace fluid


#pragma once

#include <boost/multiprecision/cpp_int.hpp>
#include <array>
#include <vector>
#include <stdexcept>
#include <iterator>
#include <random>

using boost::multiprecision::pow;
using boost::multiprecision::cpp_int;

namespace PRNG
{
    class PRNG {
        public:
            // Generates a pseudorandom unsigned bigint with a
            // given number of bytes
            virtual cpp_int random(unsigned int bits) = 0;

            // Generates a 32 bit unsigned pseudorandom int
            virtual unsigned int random32() = 0;

            // Generates a 64 bit unsigned pseudorandom int
            virtual unsigned long long random64() = 0;
    };

    class Xorshift32: public PRNG {
        public:
            void srand(unsigned int seed);
            cpp_int random(unsigned int bits);
            unsigned int random32();
            unsigned long long random64();

        private:
            unsigned int state;
            unsigned int xorshift32();
            cpp_int xorshift4096_32();
    };

    class Xorshift64: public PRNG {
        public:
            void srand(unsigned long long seed);
            cpp_int random(unsigned int bits);
            unsigned int random32();
            unsigned long long random64();

        private:
            unsigned long long state;
            unsigned long long xorshift64();
            cpp_int xorshift4096_64();
    };

    class Xorshift128: public PRNG {
        public:
            void srand(std::array<unsigned int, 4> seed);
            cpp_int random(unsigned int bits);
            unsigned int random32();
            unsigned long long random64();

        private:
            std::array<unsigned int,4> state;
            unsigned int xorshift128();
            cpp_int xorshift4096_128();
    };

    class CMWC: public PRNG {
        public:
            CMWC();
            cpp_int random(unsigned int bits);
            unsigned int random32();
            unsigned long long random64();
        private:
            static const unsigned int CYCLE = 4096;
            static const unsigned int C_MAX = 809430660;

            struct State {
                std::array<unsigned int, CYCLE> Q;
                unsigned int c;
                unsigned int i;
            };

            State state;
            std::random_device rd;
            unsigned int randCMWC();
            cpp_int randCMWC4096();

    };
}


/*******************************************************************************
 * Copyright 2013-2014 Sebastian Niemann <niemann@sra.uni-hannover.de>.
 *
 * Licensed under the MIT License (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://opensource.org/licenses/MIT
 *
 * Developers:
 *   Sebastian Niemann - Lead developer
 *   Daniel Kiechle - Unit testing
 ******************************************************************************/
#include <Expected.hpp>
using armadilloJava::Expected;

#include <iostream>
using std::cout;
using std::endl;

#include <utility>
using std::pair;

#include <armadillo>
using arma::Mat;
using arma::min;
using arma::max;
using arma::prod;
using arma::sum;
using arma::mean;
using arma::median;
using arma::cumsum;
using arma::vectorise;

#include <InputClass.hpp>
using armadilloJava::InputClass;

#include <Input.hpp>
using armadilloJava::Input;

namespace armadilloJava {
  class ExpectedGenMatDim : public Expected {
    public:
      ExpectedGenMatDim() {
        cout << "Compute ExpectedGenMatDim(): " << endl;

        vector<vector<pair<string, void*>>> inputs = Input::getTestParameters({
          InputClass::GenMat,
          InputClass::Dim
        });

        for (vector<pair<string, void*>> input : inputs) {
          _fileSuffix = "";

          int n = 0;
          for (pair<string, void*> value : input) {
            switch (n) {
              case 0:
                _fileSuffix += value.first;
                _genMat = *static_cast<Mat<double>*>(value.second);
                break;
              case 1:
                _fileSuffix += "," + value.first;
                _dim = *static_cast<int*>(value.second);
                break;
            }
            ++n;
          }

          cout << "Using input: " << _fileSuffix << endl;

          expectedArmaMin();
          expectedArmaMax();
          expectedArmaProd();
          expectedArmaSum();
          expectedArmaMean();
          expectedArmaMedian();
          expectedArmaCumsum();
          expectedArmaVectorise();
        }

        cout << "done." << endl;
      }

    protected:
      Mat<double> _genMat;
      int _dim;

      void expectedArmaMin() {
        cout << "- Compute expectedArmaMin() ... ";
        save<double>("Arma.min", min(_genMat, _dim));
        cout << "done." << endl;
      }

      void expectedArmaMax() {
        cout << "- Compute expectedArmaMax() ... ";
        save<double>("Arma.max", max(_genMat, _dim));
        cout << "done." << endl;
      }

      void expectedArmaProd() {
        cout << "- Compute expectedArmaProd() ... ";
        save<double>("Arma.prod", prod(_genMat, _dim));
        cout << "done." << endl;
      }

      void expectedArmaSum() {
        cout << "- Compute expectedArmaSum() ... ";
        save<double>("Arma.sum", sum(_genMat, _dim));
        cout << "done." << endl;
      }

      void expectedArmaMean() {
        cout << "- Compute expectedArmaMean() ... ";
        save<double>("Arma.mean", mean(_genMat, _dim));
        cout << "done." << endl;
      }

      void expectedArmaMedian() {
        cout << "- Compute expectedArmaMedian() ... ";
        save<double>("Arma.median", median(_genMat, _dim));
        cout << "done." << endl;
      }

      void expectedArmaCumsum() {
        cout << "- Compute expectedArmaCumsum() ... ";
        save<double>("Arma.cumsum", cumsum(_genMat, _dim));
        cout << "done." << endl;
      }

      void expectedArmaVectorise() {
        cout << "- Compute expectedArmaVectorise() ... ";
        save<double>("Arma.vectorise", vectorise(_genMat, _dim));
        cout << "done." << endl;
      }

  };
}


/*
The MIT License

Copyright (c) 2015-2017 Albert Murienne

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

#include "autothreshold.h"

#include "CImg.h"

#include <boost/shared_array.hpp>

using namespace cimg_library;

//************** WARNING : FOR NOW ONLY WORKS ON 0-256 DYNAMIC!!! **************//

template<typename T>
int default_isodata( const T* data, const int length );
int isodata( int* data, const int length );

void auto_threshold( float* input, const int sizeX, const int sizeY )
{
    CImg<float> _input( input, sizeX, sizeY, 1, 1, true /*shared*/);

    int threshold = default_isodata<cimg_ulong>( _input.get_histogram(256), 256 );
    _input.threshold( threshold );
}

// One of the many autothreshold IJ implementations:
// https://imagej.nih.gov/ij/developer/source/ij/process/AutoThresholder.java.html

template<typename T>
int default_isodata( const T* data, const int length )
{
    int n = length;
    boost::shared_array<int> data2( new int[n] );
    int mode=0, maxCount=0;
    for (int i=0; i<n; i++) {
        data2[i] = std::round<int>( data[i] );
        if (data2[i]>maxCount) {
            maxCount = data2[i];
            mode = i;
        }
    }
    int maxCount2 = 0;
    for (int i = 0; i<n; i++) {
        if ((data2[i]>maxCount2) && (i!=mode))
            maxCount2 = data2[i];
    }
    int hmax = maxCount;
    if ((hmax>(maxCount2*2)) && (maxCount2!=0)) {
        hmax = (int)(maxCount2 * 1.5);
        data2[mode] = hmax;
    }
    return isodata(data2.get(),n);
}

int isodata( int* data, const int length )
{
    // This is the original ImageJ IsoData implementation, here for backward compatibility.
    int level;
    int maxValue = length - 1;
    double result, sum1, sum2, sum3, sum4;
    int count0 = data[0];
    data[0] = 0; //set to zero so erased areas aren't included
    int countMax = data[maxValue];
    data[maxValue] = 0;
    int min = 0;
    while ((data[min]==0) && (min<maxValue))
        min++;
    int max = maxValue;
    while ((data[max]==0) && (max>0))
        max--;
    if (min>=max) {
        data[0]= count0; data[maxValue]=countMax;
        level = length/2;
        return level;
    }
    int movingIndex = min;
    do {
        sum1=sum2=sum3=sum4=0.0;
        for (int i=min; i<=movingIndex; i++) {
            sum1 += (double)i*data[i];
            sum2 += data[i];
        }
        for (int i=(movingIndex+1); i<=max; i++) {
            sum3 += (double)i*data[i];
            sum4 += data[i];
        }
        result = (sum1/sum2 + sum3/sum4)/2.0;
        movingIndex++;
    } while ((movingIndex+1)<=result && movingIndex<max-1);
    data[0]= count0; data[maxValue]=countMax;
    level = std::round<int>(result);
    return level;
}


#include <fstream>
#define _USE_MATH_DEFINES
#include <cmath>
#include <boost/range/iterator_range.hpp>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>

inline uint8_t scale8u(uint8_t value, uint8_t scale) {
	return (value * scale + value) >> 8;
}

void cos(boost::filesystem::path outputPath) {
	std::ofstream f((outputPath / "cos8u10.h").string());
	f << "#pragma once" << std::endl;

	// unsigned cosine with 8 bit output and 10 bit input, shifted to positive values
	f << "uint16_t FLASH cos8Table[] = {" << std::endl;
	for (int j = 0; j < 64; ++j) {
		f << "\t";
		for (int i = 0; i < 16; ++i) {
			double x = double(i + 16*j) / 512.0 * M_PI;
			//int y = (i | j) == 0 ? 255 : 0;
			int y = int(round((cos(x) + 1.0) * 127.5));
			f << y << ", ";
		}
		f << std::endl;
	}
	f << "};" << std::endl;
	f << "inline uint8_t cos8u10(uint16_t x) {return cos8Table[x];}" << std::endl;
}

void exp(boost::filesystem::path outputPath) {
	{
		// write exp table with 5 bit input and 16 bit output
		std::ofstream f((outputPath / "exp16u5.h").string());
		f << "#pragma once" << std::endl;
		f << "uint16_t FLASH exp16u5Table[] = {" << std::endl;
		f << "\t0";
		for (int i = 1; i < 32; ++i) {
			// x^31 = 65535 -> x = 65535^(1/31)
			int y = int(round(pow(65535.0, i / 31.0)));
			f << ", " << y;
		}
		f << std::endl;
		f << "};" << std::endl;
		f << "inline uint16_t exp16u5(uint8_t x) {return exp16u5Table[x];}" << std::endl;
	}
	{
		// write exp table with 8 bit input and 16 bit output
		std::ofstream f((outputPath / "exp16u8.h").string());
		f << "#pragma once" << std::endl;
		f << "uint16_t FLASH exp16u8Table[] = {" << std::endl;
		f << "\t0";
		for (int i = 1; i < 256; ++i) {
			// x^255 = 65535 -> x = 65535^(1/255)
			int y = int(round(pow(65535.0, i / 255.0)));
			f << ", " << y;
		}
		f << std::endl;
		f << "};" << std::endl;
		f << "inline uint16_t exp16u8(uint8_t x) {return exp16u8Table[x];}" << std::endl;
	}
}

void permute(boost::filesystem::path outputPath) {
	// generate permute table
	srand(1337);
	int permute[256];
	for (int i = 0; i < 256; ++i) {
		permute[i] = i;
	}
	for (int i = 0; i < 10000; ++i) {
		int x = rand() & 0xff;
		int y = rand() & 0xff;
		std::swap(permute[x], permute[y]);
	}

	// write permutation table and function
	std::ofstream f((outputPath / "permute8.h").string());
	f << "#pragma once" << std::endl;
	f << "uint8_t FLASH permute8Table[] = {" << std::endl;
	f << "\t";
	for (int i = 0; i < 256; ++i) {
		if (i != 0)
			f << ", ";
		f << permute[i];
	}
	f << std::endl;
	f << "};" << std::endl;
	f << "inline uint8_t permute8(uint8_t x) {return permute8Table[x];}" << std::endl;
}

void ledSerial(boost::filesystem::path outputPath) {
	// generate pattern to send via serial port to led strip of WS2812b etc.

	// 8 bit are transferred like this (shown inverted) where first and last bit of each block are start and stop bit:
	// 1xx001xx00 1xx001xx00 1xx001xx00 1xx001xx00

	// write lookup table
	std::ofstream f((outputPath / "ledSerial.h").string());
	f << "#pragma once" << std::endl;
	f << "uint32_t FLASH ledSerialTable[] = {" << std::endl;
	f << "\t";
	for (int v = 0; v < 256; ++v) {
		if (v != 0)
			f << ", ";

		// square the value to emulate gamma of 2
		int value = scale8u(v, v);

		// all ones in the middle of each block, omitting start bits (inverted)
		uint32_t pattern = ~0x08080808;
		for (int i = 0; i < 8; ++i) {
			if (value & (1 << i)) {
				// offsets of the "xx" are 1, 6, 9, 14, 17, 22, 25, 30
				int offset = 1 + 4 * i + (i & 1 ? 1 : 0);
				pattern &= ~(3 << offset);
			}
		}

		f << pattern;
	}
	f << std::endl;
	f << "};" << std::endl;
}

struct IndexColor {
	int index;

	int red;
	int green;
	int blue;

	IndexColor() = default;
	IndexColor(int index, uint8_t red, uint8_t green, uint8_t blue)
		: index(index), red(red), green(green), blue(blue) {}
};

inline int toInt(std::string const & s) {
	int base = 10;
	int i = 0;
	if (boost::starts_with(s, "0x")) {
		base = 16;
		i = 2;
	}

	int value = 0;
	for (; i < s.length(); ++i) {
		value *= base;
		char ch = s[i];

		if (ch >= '0' && ch <= '9') {
			value += ch - '0';
		} else if (base == 16) {
			if (ch >= 'A' && ch <= 'F')
				value += ch - 'A' + 10;
			else if (ch >= 'a' && ch <= 'f')
				value += ch - 'a' + 10;
		}
	}
	return value;
}

void interpolate(std::ofstream & f, const std::string &indent, int index1, int index2, int value1, int value2) {
	f << value1;
	if (value2 != value1) {
		f << (value2 > value1 ? " + " : " - ");
		f << '(';
		if ((index1 & 0xff) == 0)
			f << "(uint8_t)x";
		else
			f << "(uint8_t)(x - " << index1 << ")";
		f << " * " << std::abs(value2 - value1);
		int d = index2 - index1;
		int shift = 0;
		while (d > 1) {
			++shift;
			d >>= 1;
		}
		f << " >> " << shift;
		f << ')';
	}
}

void subdivide(std::ofstream & os, const std::string& indent, std::vector<IndexColor> const & palette, int begin, int end) {
	if (end - begin > 1) {
		int mid = (begin + end) / 2;

		os << indent << "if (x < 0x" << std::hex << palette[mid].index << std::dec << ") {" << std::endl;
		subdivide(os, indent + '\t', palette, begin, mid);
		os << indent << "} else {" << std::endl;
		subdivide(os, indent + '\t', palette, mid, end);
		os << indent << "}" << std::endl;
	} else {
		// add index as comment
		os << indent << "// 0x" << std::hex << palette[begin].index << std::dec << std::endl;

		// interpolate colors
		IndexColor color1 = palette[begin];
		IndexColor color2 = palette[begin + 1];
		os << indent << "color.red = "; interpolate(os, indent, color1.index, color2.index, color1.red, color2.red); os << ";" << std::endl;
		os << indent << "color.green = "; interpolate(os, indent, color1.index, color2.index, color1.green, color2.green); os << ";" << std::endl;
		os << indent << "color.blue = "; interpolate(os, indent, color1.index, color2.index, color1.blue, color2.blue); os << ";" << std::endl;
	}
}

void palette(boost::filesystem::path path, boost::filesystem::path outputPath) {
	std::vector<IndexColor> palette;

	// read palette
	std::ifstream is(path.string());
	std::string line;
	while (std::getline(is, line)) {
		// remove comment
		size_t pos = line.find("//");
		if (pos != std::string::npos)
			line = line.substr(0, pos);

		// trim
		boost::algorithm::trim(line);

		// split
		std::vector<std::string> elements;
		boost::split(elements, line, boost::algorithm::is_any_of("\t "), boost::token_compress_on);

		if (elements.size() >= 2) {

			int index = toInt(elements[0]);

			if (elements.size() == 2) {
				if (elements[1] == "loop") {
					IndexColor ic = palette.front();
					ic.index = index;
					palette.push_back(ic);
				}
			} else if (elements.size() == 4) {
				int r = toInt(elements[1]);
				int g = toInt(elements[2]);
				int b = toInt(elements[3]);

				palette.push_back(IndexColor(index, r, g, b));
			}
		}
	}

	std::string name = path.stem().string();
	std::ofstream os((outputPath / (name + ".h")).string());
	os << "#pragma once" << std::endl;
	os << "inline RGB " << name << "(uint16_t x) {" << std::endl;

	//os << "\tuint8_t r, g, b;" << std::endl;
	os << "\tRGB color;" << std::endl;

	subdivide(os, "\t", palette, 0, palette.size()-1);

	//os << "\tRGB color;" << std::endl;
	//os << "\tcolor.red = r;" << std::endl;
	//os << "\tcolor.green = g;" << std::endl;
	//os << "\tcolor.blue = b;" << std::endl;
	os << "\treturn color;" << std::endl;

	os << "}" << std::endl;
	os.close();
}

int main(int argc, const char **argv) {
	boost::filesystem::path outputPath = "generated";
	boost::filesystem::create_directory(outputPath);

	// generate tables
	cos(outputPath);
	exp(outputPath);
	permute(outputPath);
	ledSerial(outputPath);

	// convert palettes in current directory
	boost::filesystem::path p = ".";
	for (auto & entry : boost::make_iterator_range(boost::filesystem::directory_iterator(p), {})) {
		boost::filesystem::path path = entry.path();
		std::string ext = path.extension().string();
		if (ext == ".palette") {
			palette(path, outputPath);
		}
	}

	return 0;
}


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

#include <boost/math/bindings/rr.hpp>
#include <boost/test/included/test_exec_monitor.hpp>
#include <boost/math/special_functions/zeta.hpp>
#include <boost/math/constants/constants.hpp>
#include <boost/math/tools/test.hpp>
#include <fstream>

#include <boost/math/tools/test_data.hpp>

using namespace boost::math::tools;
using namespace std;

struct zeta_data_generator
{
   boost::math::ntl::RR operator()(boost::math::ntl::RR z)
   {
      std::cout << z << " ";
      boost::math::ntl::RR result = boost::math::zeta(z);
      std::cout << result << std::endl;
      return result;
   }
};

struct zeta_data_generator2
{
   std::tr1::tuple<boost::math::ntl::RR, boost::math::ntl::RR> operator()(boost::math::ntl::RR z)
   {
      std::cout << -z << " ";
      boost::math::ntl::RR result = boost::math::zeta(-z);
      std::cout << result << std::endl;
      return std::tr1::make_tuple(-z, result);
   }
};

int test_main(int argc, char*argv [])
{
   boost::math::ntl::RR::SetPrecision(500);
   boost::math::ntl::RR::SetOutputPrecision(40);

   parameter_info<boost::math::ntl::RR> arg1;
   test_data<boost::math::ntl::RR> data;

   bool cont;
   std::string line;

   std::cout << "Welcome.\n"
      "This program will generate spot tests for the zeta function:\n";

   do{
      if(0 == get_user_parameter_info(arg1, "z"))
         return 1;
      arg1.type |= dummy_param;
      data.insert(zeta_data_generator2(), arg1);

      std::cout << "Any more data [y/n]?";
      std::getline(std::cin, line);
      boost::algorithm::trim(line);
      cont = (line == "y");
   }while(cont);

   std::cout << "Enter name of test data file [default=zeta_data.ipp]";
   std::getline(std::cin, line);
   boost::algorithm::trim(line);
   if(line == "")
      line = "zeta_data.ipp";
   std::ofstream ofs(line.c_str());
   write_code(ofs, data, "zeta_data");

   return 0;
}


#if defined __GNUC__ \
            && ( __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7) ) \
            && !defined __clang__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#endif

#include <boost/type_traits.hpp>
#include <boost/timer/timer.hpp>
#include <boost/math/special_functions/digamma.hpp>
#include <boost/math/special_functions/fpclassify.hpp>
#include <boost/math/special_functions/hypot.hpp>
#include <boost/math/special_functions/pow.hpp>
#include <boost/math/tools/promotion.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>

#if defined __GNUC__ \
            && ( __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7) ) \
            && !defined __clang__
#pragma GCC diagnostic pop
#endif

#include <cmath>
#include <string>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <limits>
#include <valarray>

#if __cplusplus >= 201103L
  #include <type_traits>
  #include <initializer_list>
#endif


/**
 * @file mtgp32-calc-poly.cpp
 *
 * @brief calculate characteristic polynomial for 32bit mtgp.
 *
 * @author Mutsuo Saito (Hiroshima University)
 * @author Makoto Matsumoto (Hiroshima University)
 *
 * Copyright (c) 2012 Mutsuo Saito, Makoto Matsumoto, Hiroshima
 * University and University of Tokyo. All rights reserved.
 *
 * The new BSD License is applied to this software, see LICENSE.txt
 */
#include <stdint.h>
#include <inttypes.h>
#include <time.h>
#include <string.h>
#include <string>
#include <errno.h>
#include <NTL/GF2X.h>
#include <NTL/vec_GF2.h>
#include <NTL/GF2XFactoring.h>
#include "mtgp32-calc-poly.hpp"
#include "mtgp-calc-jump.hpp"
#include "mtgp32-fast.h"

using namespace std;
using namespace NTL;

/**
 * calculate the characteristic polynomial for given 32-bit MTGP.
 * MinPolySeq is defined in NTL.
 * @param[out] poly calculated characteristic polynomial.
 * @param[in] mtgp64 generator
 */
void calc_characteristic(GF2X& poly, mtgp32_fast_t * mtgp32)
{
    vec_GF2 seq;
    int mexp = mtgp32->params.mexp;
    seq.SetLength(2 * mexp);
    for (int i = 0; i < 2 * mexp; i++) {
	seq[i] = mtgp32_genrand_uint32(mtgp32) & 1;
    }
    MinPolySeq(poly, seq, mexp);
}

/**
 * calculate the characteristic polynomial for given 32-bit MTGP.
 * MinPolySeq is defined in NTL.
 * @param[out] str calculated characteristic polynomial in string format.
 * @param[in] mtgp64 generator
 */
void calc_characteristic(string& str, mtgp32_fast_t * mtgp32)
{
    GF2X poly;
    calc_characteristic(poly, mtgp32);
    polytostring(str, poly);
}

void calc_characteristic(uint32_t array[], int size, mtgp32_fast_t * mtgp32)
{
    GF2X poly;
    calc_characteristic(poly, mtgp32);
    polytoarray(array, size, poly);
}

#if defined(MAIN)
/**
 * main function for executable.
 * @param[in] argc number of arguments.
 * @param[in] argv an array of arguments.
 * @return 0 if normal, other abnormal.
 */
int main(int argc, char *argv[]) {
    int mexp;
    int no;
    uint32_t seed = 1;
    mtgp32_params_fast_t *params;
    mtgp32_fast_t mtgp32;
    int rc;

    if (argc <= 2) {
	printf("%s: mexp no.\n", argv[0]);
	return 1;
    }
    mexp = strtol(argv[1], NULL, 10);
    if (errno) {
	printf("%s: mexp no.\n", argv[0]);
	return 2;
    }
    no = strtol(argv[2], NULL, 10);
    if (errno) {
	printf("%s: mexp no.\n", argv[0]);
	return 3;
    }
    switch (mexp) {
    case 11213:
	params = mtgp32_params_fast_11213;
	break;
    case 23209:
	params = mtgp32_params_fast_23209;
	break;
    case 44497:
	params = mtgp32_params_fast_44497;
	break;
    default:
	printf("%s: mexp no.\n", argv[0]);
	printf("mexp shuould be 11213, 23209 or 44497\n");
	return 4;
    }
    if (no >= 128 || no < 0) {
	printf("%s: mexp no.\n", argv[0]);
	printf("no must be between 0 and 127\n");
	return 5;
    }
    params += no;
    rc = mtgp32_init(&mtgp32, params, seed);
    if (rc) {
	printf("failure in mtgp32_init\n");
	return -1;
    }
    mtgp32_print_idstring(&mtgp32, stdout);
#if 0
    string s;
    calc_characteristic(s, &mtgp32);
    printf("%s\n", s.c_str());
#else
#endif
    mtgp32_free(&mtgp32);
    return 0;
}
#endif


//
// Created by eliezer on 07.11.16.
//

#include <cmath>
#include "BatchPoissonPure.h"
#include <math.h>
#include <limits>
#include <algorithm>
#include <cmath>
#include <sstream>
#include <stdexcept>
#include <boost/math/special_functions/digamma.hpp>
#include <chrono>
#include <boost/math/special_functions/gamma.hpp>
#ifndef M_PIl
/** The constant Pi in high precision */
#define M_PIl 3.1415926535897932384626433832795029L
#endif
#ifndef M_GAMMAl
/** Euler's constant in high precision */
#define M_GAMMAl 0.5772156649015328606065120900824024L
#endif
#ifndef M_LN2l
/** the natural logarithm of 2 in high precision */
#define M_LN2l 0.6931471805599453094172321214581766L
#endif

double gamma_term(double a, double b, double a_latent, double b_latent, double e_latent, double elog_latent) {
    return lgamma(a_latent)-lgamma(a)+a*log(b)+a_latent*(1-log(b_latent))-b*e_latent+(a-a_latent)*elog_latent;
}

void compute_gama_expected(Arrayf& a_x, Arrayf& b_x, Arrayf& e_x,
                           Arrayf& elog_x) {
    for ( long i=0; i< a_x.rows();i++ ){
        for( long k=0; k< a_x.cols();k++){
            e_x(i,k) = a_x(i,k)/b_x(k);
            //elog_x(i,k) = (double)(digammal(a_x(i,k))-log(b_x(k))); // using standalone implementation
            elog_x(i, k) = boost::math::digamma(a_x(i, k)) - log(b_x(k)); // using boost implementation

        }
    }
}

size_t sizes_var(vars v,size_t n_ratings, size_t n_wd_entries, size_t n_users, size_t n_items,size_t k_feat, size_t n_words, size_t n_max_neighbors){
    size_t result=0;
    switch(v){
        case vars::a_beta:
        case vars::e_beta:
        case vars::elog_beta:
            result= k_feat*n_words;
            break;

        case vars::a_epsilon:
        case vars::e_epsilon:
        case vars::elog_epsilon:
        case vars::a_theta:
        case vars::e_theta:
        case vars::elog_theta:
            result= k_feat*n_items;
            break;

        case vars::e_eta:
        case vars::elog_eta:
        case vars::a_eta:
            result= k_feat*n_users;
            break;

        case vars::e_tau:
        case vars::elog_tau:
        case vars::a_tau:
            result= n_max_neighbors*n_users;
            break;

        case vars::phi:
            result=k_feat*n_wd_entries;
            break;
        case vars::xi_M:
        case vars::xi_N:
            result= k_feat*n_ratings;
            break;
        case vars::xi_S:
            result= n_max_neighbors*n_ratings;
            break;

        case vars::b_theta:
        case vars::b_eta:
        case vars::b_epsilon:
        case vars::b_beta:
            result= k_feat;
            break;

        case vars::b_tau:
            result= n_max_neighbors;
            break;
    }
    //std::cout << EnumStrings[(int)v]<<"= "<<result<<'\n';
    return result;
}

size_t total_memory(size_t n_ratings, size_t n_wd_entries, size_t n_users, size_t n_items,size_t k_feat, size_t n_words, size_t n_max_neighbors){
    size_t sum=0;

    for(vars i : vec_vars){
        sum+=sizes_var(i,n_ratings,n_wd_entries, n_users,  n_items,k_feat, n_words,n_max_neighbors);
    }
    return sum;
}

BatchPoissonNewArray::BatchPoissonNewArray(size_t n_ratings, size_t n_wd_entries, size_t n_users, size_t n_items,
                                           size_t k_feat, size_t n_words, size_t n_max_neighbors, double a, double b,
                                           double c, double d, double e, double f, double g, double h, double k, double l) :
        arrman(new ArrayManager<double>(2*total_memory(n_ratings,n_wd_entries, n_users,  n_items,k_feat, n_words,n_max_neighbors))),
        _n_users(n_users), _n_items(n_items), _k_feat(k_feat),
        _n_words(n_words), _n_ratings(n_ratings),
        _n_wd_entries(n_wd_entries), _n_max_neighbors(n_max_neighbors),
        a(a), b(b), c(c), d(d), e(e), f(f), g(g), h(h), k(k), l(l),
        beta(arrman,n_words,k_feat,a,b),
        theta(arrman,n_items,k_feat,c,d),
        epsilon(arrman,n_items,k_feat,g,h),
        eta(arrman,n_users,k_feat,e,f),
        tau(arrman,n_users,n_max_neighbors,k,l),
        phi(arrman->makeArray(n_wd_entries,k_feat)),
        xi_M(arrman->makeArray(n_ratings,k_feat)),
        xi_N(arrman->makeArray(n_ratings,k_feat)),
        xi_S(arrman->makeArray(n_ratings,n_max_neighbors)),
        user_items_map(pairmap(n_ratings)),
        user_items_index(vector< pair<size_t,size_t>>(n_users)),
        user_items_neighboors(vector< list <  pair<size_t, size_t > > >(n_ratings))
{

    mem_use= total_memory(n_ratings,n_wd_entries, n_users,  n_items,k_feat, n_words,n_max_neighbors);
    std::cout << "total = " << mem_use <<"\n";
    std::cout << "n_users = " << n_users <<"\n";
    std::cout << "n_items = " << n_items <<"\n";
    std::cout << "n_words = " << n_words <<"\n";
    std::cout << "n_max_neighbors = " << n_max_neighbors <<"\n";
    std::cout << "n_ratings = " << n_ratings <<"\n";
    std::cout << "n_wd_entries = " << n_wd_entries <<"\n";
    std::cout << "k_feat = " << k_feat <<"\n";

}

void BatchPoissonNewArray::train(size_t n_iter, double tol) {
    try {
        std::cout << "n_iter = " << n_iter <<"\n";
        std::cout << "tol = " << tol <<"\n";
        init_aux_latent();
        double old_elbo=-std::numeric_limits<double>::infinity();
        double elbo=0;

        for(auto i=0;i<n_iter;i++){

                std::cout << "############ITERATION "<<i<<" of "<<n_iter<<endl;
                std::cout << "Begin update latent variables"<<endl;
                update_latent();
                std::cout << "Begin update auxiliary variables"<<endl;
                update_aux_latent();
                elbo = compute_elbo();

                elbo_lst.push_back(elbo);
                std::cout << "Old ELBO="<<old_elbo<<"  ---- new ELBO="<< elbo<< " improvement = " << abs((elbo-old_elbo)/old_elbo) << endl;

                if(abs((elbo-old_elbo)/old_elbo) < tol)
                    break;
                else
                    old_elbo=elbo;

        }
        std::cout << "List os ELBO values";
        std::copy(elbo_lst.begin(),
                  elbo_lst.end(),
                  std::ostream_iterator<double>(std::cout, " , "));
    } catch (const std::bad_alloc& e) {
        std::cout << "Allocation failed: " << e.what() << '\n';
        exit(-1);
    }
}

 void BatchPoissonNewArray::init_train(vector<tuple<size_t, size_t, size_t>> r_entries,
                                      vector<tuple<size_t, size_t, size_t>> w_entries,
                                      vector< vector<size_t> > user_neighboors) {
    this->r_entries=r_entries;
    this->w_entries=w_entries;
    this->user_neighboors=user_neighboors;
    cout << "init_train " << endl;
    std::cout << "r_entries.size = " << r_entries.size() <<"\n";
    std::cout << "w_entries.size = " << w_entries.size() <<"\n";
    std::cout << "user_neighboors.size = " << user_neighboors.size() <<"\n";

    pair<size_t,size_t> temp=make_pair(0,0);

    size_t ud;
    for(ud=0; ud< _n_ratings;ud++) {
        auto user_u = std::get<0>(r_entries[ud]);
        auto item_i = std::get<1>(r_entries[ud]);
        temp.first=user_u;
        temp.second=item_i;
        user_items_map[temp]=  ud;

    }
    temp=make_pair(0,0);
    size_t current_user=0;
    for(ud = 0; ud < _n_ratings; ud++) {
        auto user_u = std::get<0>(r_entries[ud]);
        auto item_i = std::get<1>(r_entries[ud]);
        tau.b_latent(user_u)+=std::get<2>(r_entries[ud]); // b_tau_user_i = l + \sum_d r_{user_i,d}

        if(current_user!=user_u){
            // generate a index with beginning index and end index for item rated by user in the user_item_rating matrix
            //
            temp.second=ud;
            user_items_index[current_user]=temp;
            temp=make_pair(ud,0);
            current_user=user_u;
            //cout << "("<<user_u << ", " << item_i << "," << std::get<2>(r_entries[ud])<<"),";

        }
        size_t neigh_ord=0;
        for(auto user_i : this->user_neighboors[user_u]){
            // test which neighboor of user_u has item_i rated and point to the index on the rating matrix
            pairmap::iterator ifind = user_items_map.find(make_pair(user_i,item_i));
            if ( ifind != user_items_map.end() ){
                // fill vector< list <  pair<size_t, size_t > > > user_items_neighboors;
                // list<<pair<user_neighbor_i,index_in_r_entries>>
                user_items_neighboors[ud].push_back(make_pair(user_i,ifind->second));

            }
            neigh_ord++;
        }
    }
    temp.second=_n_ratings;
    user_items_index[current_user]=temp;
    //cout << "###########" <<endl;
    //for(size_t i=0;i<user_items_index.size();i++)
    //{
    //    cout << "("<<i<<","<<user_items_index[i].first <<","<<user_items_index[i].second  << "),";
    //}
    beta.init_b_latent();
    theta.init_b_latent();
    epsilon.init_b_latent();
    eta.init_b_latent();
    cout << "tau_b="<< tau.b_latent << endl;
}

void BatchPoissonNewArray::init_aux_latent() {
    phi.init_gamma_row_normalized();
    xi_M.init_gamma_row_normalized();
    xi_N.init_gamma_row_normalized();
    xi_S.init_gamma_row_normalized();
    //update_latent();
    //update_expected();
    //xi_S=0;
    //xi_M=0;
    //xi_N=0;
    //xi_S=0;
    //update_aux_latent();

}

void BatchPoissonNewArray::update_latent() {
    beta.init_a_latent();
    theta.init_a_latent();
    epsilon.init_a_latent();
    eta.init_a_latent();
    tau.init_a_latent();
    cout << "INIT#theta" << theta <<endl;
    //cout << "INIT#eta" << eta <<endl;
    //cout << "INIT#tau" << tau <<endl;
    //cout << "INIT#epsilon" << epsilon <<endl;
    //cout << "INIT#beta" << epsilon <<endl;

    for(size_t ud=0; ud< _n_ratings;ud++){
        auto user_u = std::get<0>(r_entries[ud]);
        auto item_i = std::get<1>(r_entries[ud]);
        auto r_ud= std::get<2>(r_entries[ud]);

        for(size_t k=0; k< _k_feat;k++){
            auto rudk_M=r_ud*xi_M(ud,k);
            auto rudk_N=r_ud*xi_N(ud,k);
            epsilon.a_latent(item_i,k)+=rudk_N;
            theta.a_latent(item_i,k)+=rudk_M;
            eta.a_latent(user_u,k)+=rudk_M+rudk_N;
        }

        for(auto neighb : user_items_neighboors[ud]) {
            tau.a_latent(user_u,neighb.first)+=r_ud*xi_S(ud,neighb.first);
        }
    }
    double temp_w;
    for(size_t dv=0; dv< _n_wd_entries;dv++){

        auto word_w = std::get<1>(w_entries[dv]);
        auto item_i = std::get<0>(w_entries[dv]);
        auto wdv= std::get<2>(w_entries[dv]);
        for(size_t k=0; k< _k_feat;k++) {
            temp_w=wdv*phi(dv,k);
            beta.a_latent(word_w,k)+=temp_w;
            theta.a_latent(item_i,k)+=temp_w;

        }
    }

    beta.init_b_latent();
    eta.init_b_latent();
    for(size_t k=0; k< _k_feat;k++) {
        double sum_d_epsilon = epsilon.e_expected.col_sum(k);
        double sum_d_theta = theta.e_expected.col_sum(k);
        beta.b_latent(k)+= sum_d_theta;
        eta.b_latent(k) += sum_d_theta + sum_d_epsilon;
    }
    beta.update_expected();
    eta.update_expected();

    theta.init_b_latent();
    epsilon.init_b_latent();
    for(size_t k=0; k< _k_feat;k++) {
        double sum_u_eta = eta.e_expected.col_sum(k);
        double sum_v_beta = beta.e_expected.col_sum(k);
        epsilon.b_latent(k) += sum_u_eta;
        theta.b_latent(k) += sum_u_eta + sum_v_beta;
    }
    theta.update_expected();
    epsilon.update_expected();
    tau.update_expected();

    //cout << "END#theta" << theta <<endl;
    //cout << "AFTERTHETA#xi_M" << xi_M << endl;
    //cout << "END#eta" << eta <<endl;
    //cout << "END#tau" << tau <<endl;
    //cout << "END#epsilon" << epsilon <<endl;
    //cout << "END#beta" << epsilon <<endl;
}

void BatchPoissonNewArray::update_aux_latent() {
    double sum_k=0;
    for(auto ud=0; ud< _n_ratings;ud++) {
        // TODO: implement LOG-SUM
        sum_k=0;
        auto user_u = std::get<0>(r_entries[ud]);
        auto item_i = std::get<1>(r_entries[ud]);
        for (auto k = 0; k < _k_feat; k++) {
            // self.xi_M = np.exp(self.Elogeta[:, np.newaxis, :] + self.Elogtheta[:, :, np.newaxis])
            xi_M(ud, k) = exp(eta.elog_expected(user_u, k) + theta.elog_expected(item_i, k));

            // self.xi_N = np.exp(self.Elogeta[:, np.newaxis, :] + self.Elogepsilon[:, :, np.newaxis])
            xi_N(ud, k) = exp(eta.elog_expected(user_u, k) + epsilon.elog_expected(item_i, k));
            sum_k += xi_M(ud,k) + xi_N(ud,k);
        }
        xi_S.row(ud) = 0;
        for (auto neighb : user_items_neighboors[ud]) {
            // user_items_neighboors[ud].push_back(make_pair(user_i,ifind->second));
            xi_S(ud,neighb.first) = std::get<2>(r_entries[neighb.second])
                                     * exp(tau.elog_expected(user_u,neighb.first));
            sum_k += xi_S(ud,neighb.first);

        }
        {
            xi_M.row(ud) /= sum_k;
        }
        {
            xi_N.row(ud) /= sum_k;
        }
        {
            xi_S.row(ud) /= sum_k;
        }
    }
    cout << endl;

    for(auto dv=0; dv< _n_wd_entries;dv++){
        sum_k=0;
        auto word_w = std::get<1>(w_entries[dv]);
        auto item_i = std::get<0>(w_entries[dv]);
        for(auto k=0; k< _k_feat;k++){
            // self.phi = np.exp(self.Elogbeta[:, np.newaxis, :] + self.Elogtheta[:, :, np.newaxis])
            phi(dv,k)=exp(beta.elog_expected(word_w,k)+theta.elog_expected(item_i,k));
            sum_k += phi(dv,k);
        }
        {
            phi.row(dv)/=sum_k;
        }

    }
    cout << "#END UPDATE_AUX" << endl;
    cout << "#xi_M" << xi_M.row(11685) << endl;
    cout << "#xi_N" << xi_N.row(11685) << endl;
    //cout << "#xi_S" << xi_S.row(11685) << endl;
    //cout << "#xi_N" << xi_N << endl;
    //cout << "#xi_S" << xi_S << endl;
    cout << "#phi" << phi.row(0) << endl;

}

double BatchPoissonNewArray::compute_elbo() {
    double total_sum;
    total_sum = 0.0;
    double log_sum=0;
    // poisson termo of the ELBO for user-document ratings
    // sum_u,d,k{ Eq[log p(r_ud|*) ] }
    cout << r_entries.size() << " nrat " << _n_ratings;
    for(size_t  ud=0; ud< _n_ratings;ud++) {
        size_t  user_u = std::get<0>(r_entries[ud]);
        size_t  item_i = std::get<1>(r_entries[ud]);
        size_t  r_ud = std::get<2>(r_entries[ud]);
        log_sum=0;
        for (size_t  k = 0; k < _k_feat; k++) {
            if(xi_M(ud,k) > 0)
                log_sum += xi_M(ud,k)*(eta.elog_expected(user_u,k)+theta.elog_expected(item_i,k)-log(xi_M(ud,k)));
            if(xi_N(ud,k) > 0)
                log_sum += xi_N(ud,k)*(eta.elog_expected(user_u,k)+epsilon.elog_expected(item_i,k)-log(xi_N(ud,k)));
            if(log_sum!=log_sum)
            {
                cout << "(NAN-logsum: ud="<<ud<<", k="<<k<<" xi_M(ud,k)="<<xi_M(ud,k)<<" xi_N(ud,k)="<<xi_N(ud,k)
                     <<" E_q[log eta_uk]="<<eta.elog_expected(user_u,k)<<" E_q[log the_dk]="<<theta.elog_expected(item_i,k)
                     <<" E_q[log eta_uk]="<<epsilon.elog_expected(user_u,k);
            }
        }
        if(ud==0)
            cout << "logsum = " << log_sum << " ";
        for (pair<size_t,size_t> neighb : user_items_neighboors[ud]) {
            //neighb is user_i in N(user_u), neighb.first is its index in the trust tau variable
            // user_items_neighboors[ud].push_back(make_pair(user_i,ifind->second));
            size_t  r_id = std::get<2>(r_entries[neighb.second]);
            if(xi_S(ud,neighb.first) > 0)
                log_sum += xi_S(ud,neighb.first)*(tau.elog_expected(user_u,neighb.first)+log(r_id )
                                                       -log(xi_S(ud,neighb.first)));
        }
        if(ud==0)
            cout << "logsum = " << log_sum << " ";
        total_sum+=r_ud*log_sum-boost::math::lgamma(r_ud+1);
        if(boost::math::isnan( total_sum))
            cout << "##LOG_SUM ud="<<ud<<", user_u="<<user_u<<"item_i="<<item_i<<" r_ud="<<r_ud<<"##";
        /** TODO:
         * - sum_u,d,k over Eq[latent variables] (Eq without log probability)
         */
    }
    // poisson termo of the ELBO for word-document count
    // sum_v,d,k{ Eq[log p(w_dv|*) ] }
    //cout << xi_M;
    //cout << theta;
   // cout << endl;
    //cout << "r entries "<< std::get<0>(r_entries[0]) << " " << std::get<1>(r_entries[0]) << " " << boost::math::lgamma(std::get<2>(r_entries[0])+1) << " " <<endl;
    cout << "partial elbo 1 "<<total_sum;
    cout << endl;
    for(size_t dv=0; dv< _n_wd_entries;dv++){
        size_t  word_w = std::get<1>(w_entries[dv]);
        size_t item_i = std::get<0>(w_entries[dv]);
        size_t  w_dv = std::get<2>(w_entries[dv]);
        log_sum=0;
        for(size_t k=0; k< _k_feat;k++){
            log_sum += phi(dv,k)*(beta.elog_expected(word_w,k)+theta.elog_expected(item_i,k)- log(phi(dv,k)));
            if(boost::math::isnan( log_sum))
                cout << "##LOG_SUM dv="<<dv<<", k="<<k<<", word_w="<<word_w<<"item_i="<<item_i<<" phi(dv,k)="<<phi(dv,k)<<", beta.elog_expected(word_w,k)="
                     <<beta.elog_expected(word_w,k)
                     <<", beta.elog_expected(word_w,k)="<<beta.elog_expected(word_w,k)
                     <<",theta.elog_expected(item_i,k)="<<theta.elog_expected(item_i,k)
                     <<",log(phi(dv,k)))="<<log(phi(dv,k))
                        <<"##";

        }

        total_sum+=(((double)w_dv)*log_sum)-boost::math::lgamma(w_dv+1);
        if(boost::math::isnan( total_sum))
            cout << "##LOG_SUM dv="<<dv<<", word_w="<<word_w<<"item_i="<<item_i<<" w_dv="<<w_dv<<"##";
        /* if(w_dv >= 1){
            try {
                double x=boost::math::lgamma(w_dv+1);
                //cout << "boot lgamma =" << x << " wdv=" << w_dv << endl ;
                if(boost::math::isnan( log_sum))
                    cout << "##LOG_SUM dv="<<dv<<", word_w="<<word_w<<"item_i="<<item_i<<" w_dv="<<w_dv<<"log-fact="<<x<<"##";
                total_sum-=x;
                //cout << "total_sum =" << total_sum << endl ;
            } catch (const std::bad_alloc& e) {
                std::cout << "Allocation failed: " << e.what() << '\n';
                exit(-1);
            }

        }*/

        /** TODO:
         * - sum_v,d,k over Eq[latent variables] (Eq without log probability)
         */

    }

    //term with sum of multiplication of expected-value of latent variables
    // -sum_k,d,v E[theta_dk]*E[beta_vk]
    total_sum+=theta.elbo_term_prod_linear_expectations(vector<gamma_latent*>({&beta}));
    if(boost::math::isnan( total_sum ))
        cout << "##TOTAL_SUM theta*beta";
    // -sum_k,d,u E[theta_dk]*E[eta_uk]+E[epsilon_dk]*E[eta_uk]
    total_sum+=eta.elbo_term_prod_linear_expectations(vector<gamma_latent*>({&theta,&epsilon}));
    if(boost::math::isnan( total_sum ))
        cout << "##TOTAL_SUM theta*eta+epsilon*eta";
    total_sum+=tau_elbo_expected_linear_term();
    if(boost::math::isnan( total_sum ))
        cout << "##TOTAL_SUM tau";

    // Gamma terms for the latent variables
    total_sum+=beta.elbo_term();
    if(boost::math::isnan( total_sum ))
        cout << "##TOTAL_SUM gamma beta";
    total_sum+=theta.elbo_term();
    if(boost::math::isnan( total_sum ))
        cout << "##TOTAL_SUM gamma theta";
    total_sum+=epsilon.elbo_term();
    if(boost::math::isnan( total_sum ))
        cout << "##TOTAL_SUM gamma epsilon";
    total_sum+=eta.elbo_term();
    if(boost::math::isnan( total_sum ))
        cout << "##TOTAL_SUM  gamma eta";
    total_sum+=tau.elbo_term();
    if(boost::math::isnan( total_sum ))
        cout << "##TOTAL_SUM  gamma tau";
    return total_sum;
}
/*
 * TODO:
 * - Change everything about tau variable. There is two options:
 * 1) kdim of tau variable is = n_users;
 * 2) kdim of tau variable is = n_users_that_are_neighbors.
 * In beginning to think that option 1) is better, but it will need some changing in other places
 *
 * UPDATE: implemented option 1
 */

double BatchPoissonNewArray::tau_elbo_expected_linear_term() {
    double total_sum=0;
    for(size_t u=0;u < _n_users ;u++) {
        for(size_t i : user_neighboors[u]) {
            for(size_t d=user_items_index[i].first; d<user_items_index[i].second;d++) {
                size_t r_id = std::get<2>(r_entries[d]);
                total_sum+=tau.e_expected(u,i)*r_id;
            }
        }
    }
    return -total_sum;
}

vector<vector<double>>  BatchPoissonNewArray::estimate() {
    cout << "begin estimate" <<endl;
    vector<vector<double>> ret(_n_users);
    for(size_t user_u=0;user_u < _n_users; user_u++){
        for(size_t item_i=0;item_i < _n_items ; item_i++){
            double r_ui = 0;
            for(size_t k=0;k<_k_feat;k++){
                r_ui += eta.e_expected(user_u,k)*(epsilon.e_expected(item_i,k)+theta.e_expected(item_i,k));
            }
            for(size_t user_i : user_neighboors[user_u]){
                pairmap::iterator ifind = user_items_map.find(make_pair(user_i,item_i));
                if ( ifind != user_items_map.end() )
                    r_ui += tau.e_expected(user_u,user_i)*ifind->second;
            }
            ret[user_u].push_back(r_ui);
        }
    }
    cout << "end estimate" <<endl;
    return ret;
}

struct predicate
{
    bool operator()(const std::pair<double,size_t> &left, const std::pair<double,size_t> &right)
    {
        return left.first < right.first;
    }
};

vector<vector<size_t>> BatchPoissonNewArray::recommend(size_t m) {
    cout << "begin recommend" <<endl;

    vector< vector<double>> ret= estimate();
    vector<vector<size_t>> rec;
    ;

    for(size_t user_u=0;user_u < _n_users; user_u++)
    {
        vector<pair<double,size_t>> scores;
        for(size_t item_i=0;item_i < _n_items ; item_i++){
            // recommend only items that are not already rated by the user
            if(user_items_map.count(make_pair(user_u,item_i))<=0)
                scores.push_back(make_pair(ret[user_u][item_i],item_i));
        }
        std::sort(scores.begin(),scores.end());
        std::reverse(scores.begin(),scores.end());
        vector<size_t> temp;
        for(size_t i=0; i < m ; i++)
        {
            //cout << "(" <<scores[i].first <<","<<scores[i].second<<")";
            temp.push_back(scores[i].second);
        }
        //cout << endl;
        rec.push_back(temp);
    }
    return rec;
}

void BatchPoissonNewArray::recommend(std::ostream &output, size_t m) {
    cout << "begin recommend save" <<endl;

    vector<vector<double>> ret= estimate();

    for(size_t user_u=0;user_u < _n_users; user_u++)
    {
        vector<pair<double,size_t>> scores;
        for(size_t item_i=0;item_i < _n_items ; item_i++){
            // recommend only items that are not already rated by the user
            if(user_items_map.count(make_pair(user_u,item_i))<=0)
                scores.push_back(make_pair(ret[user_u][item_i],item_i));
        }
        std::sort(scores.begin(),scores.end());
        std::reverse(scores.begin(),scores.end());
        vector<size_t> temp;
        for(size_t i=0; i < m ; i++)
        {
            //cout << "(" <<scores[i].first <<","<<scores[i].second<<")";
            temp.push_back(scores[i].second);
        }
        std::copy (temp.begin(), temp.end(), std::ostream_iterator<size_t>(output, "\t"));
        output << endl;
    }
}

BatchPoissonNewArray::~BatchPoissonNewArray() {
    arrman->~ArrayManager();
}

vars operator++(vars &x) { return x = (vars)(((int)(x) + 1)); }

void gamma_latent::update_expected() {
    for ( size_t i=0; i< a_latent.rows();i++ ){
        for( size_t k=0; k< a_latent.cols();k++){
            e_expected(i,k) = a_latent(i,k)/b_latent(k);
            if(i==k && i==0)
            {
                if(boost::math::isnan( e_expected(i,k)))
                    cout << "NAN-EXP "<<i<<" "<<k<<" a_latent="<<a_latent(i,k)<<" b_latent="<<b_latent(k);
            }

            //elog_x(i,k) = (double)(digammal(a_x(i,k))-log(b_x(k))); // using standalone implementation
            elog_expected(i, k) = boost::math::digamma(a_latent(i, k)) - log(b_latent(k)); // using boost implementation
            if(i==k && i==0) {
                if (boost::math::isnan(elog_expected(i, k)))
                    cout << "NAN-LOGEXP " << i << " " << k << " digama a_latent="
                         << boost::math::digamma(a_latent(i, k)) << " b_latent=" << b_latent(k) << " log b"
                         << log(b_latent(k));
            }
        }
    }
}

double gamma_latent::elbo_term() {
    double total_sum=0;
    for( size_t  d=0;d<a_latent.nrow;d++){
        for( size_t  k=0; k<a_latent.ncol;k++){
            total_sum+=gamma_term(a,b,a_latent(d,k),b_latent(k),e_expected(d,k),elog_expected(d,k));
        }
    }
    return total_sum;
}

double gamma_latent::elbo_term(vector<gamma_latent*> vars) {
    double total_sum=0;
    for( size_t d=0;d<a_latent.nrow;d++){
        for( size_t  k=0; k<a_latent.ncol;k++){
            total_sum+=gamma_term(a,b,a_latent(d,k),b_latent(k),e_expected(d,k),elog_expected(d,k));
            for(gamma_latent* var : vars){
                if(var)
                    total_sum+=gamma_term(var->a,var->b,var->a_latent(d,k),var->b_latent(k),var->e_expected(d,k),var->elog_expected(d,k));
            }
        }
    }
    return total_sum;
}

gamma_latent::gamma_latent(const Arrayf &a_latent, const Arrayf &b_latent, const Arrayf &e_expected,
                           const Arrayf &elog_expected, double a, double b) :
        a_latent(a_latent), b_latent(b_latent),
        e_expected(e_expected),
        elog_expected(elog_expected), a(a), b(b),
        nvars(a_latent.nrow),kdim(a_latent.ncol){}

void gamma_latent::init_b_latent() {
    b_latent=b;
}

void gamma_latent::init_a_latent() {
    a_latent=a;

}

double gamma_latent::elbo_term_prod_linear_expectations(vector<gamma_latent *> vars) {
    double total_sum=0;

    for(gamma_latent* var : vars)
    {
        if(var)
        {

            for( size_t  j=0;j<(var->nvars);j++)
            {
                for (int k = 0; k < kdim; ++k)
                {
                    auto expjk=var->e_expected(j,k);
                    for( size_t  i=0;i<nvars;i++)
                        total_sum+=e_expected(i,k)*expjk;
                }
            }
        }
    }

    return (-total_sum);
}

gamma_latent::gamma_latent( ArrayManager<double>* arrman, size_t nrows, size_t ncols, double a, double b):
        a(a), b(b), a_latent(arrman->makeArray(nrows,ncols,a)),b_latent(arrman->makeArray(ncols,b)),
        e_expected(arrman->makeArray(nrows,ncols)),elog_expected(arrman->makeArray(nrows,ncols)),
        nvars(nrows),kdim(ncols)
{

}