#include "static_ic.h"
#include "../../cosmo_includes.h"
#include "../../cosmo_types.h"
#include "../../cosmo_globals.h"
#include "../../ICs/ICs.h"
#include "../../utils/math.h"

#include <boost/math/special_functions/spherical_harmonic.hpp>

#include <utility>
#include <random>

namespace cosmo
{

typedef std::pair<real_t, real_t> complex_t;
#define m_idx(l, m) ((l)+(m))

/**
 * @brief Gaussian random field ICs
 *
 * General steps:
 *  1) Generate the Newtonian gauge variables:
 *     phi = psi, \dot{phi} = 0, \delta_u
 *  2) Compute the synchronous gauge variables
 *  3) Fix \phi, \bar{gamma}_{ij}, K
 *  4) Solve for V, U in CTT decomposition -> obtain \bar{A}_{ij}^{\rm NL}
 *  5) Use the Hamiltonian constraint residual to solve for the density field
 */
void static_ic_set_random(BSSN * bssn, Static * stat, Lambda * lambda,
  Fourier * fourier, IOData * iodata)
{
  idx_t i, j, k;

  // Background cosmology, a_FRW = 1
  real_t rho_FRW = 3.0/PI/8.0;
  real_t Omega_L = std::stod(_config("Omega_L", "1.0e-6"));
  real_t p0 = std::stod(_config("p0", "7.0"));
  real_t P = H_LEN_FRAC*H_LEN_FRAC*1.0e-15*std::stod(_config("P", "1.0"));
  real_t p_cut = std::stod(_config("p_cut", "1.0"));
  real_t rho_L = Omega_L * rho_FRW;
  lambda->setLambda(rho_L);

  // final metric fields
  arr_t & DIFFphi_p = *bssn->fields["DIFFphi_p"];
  arr_t & DIFFK_p = *bssn->fields["DIFFK_p"];
  arr_t & A11_p = *bssn->fields["A11_p"];
  arr_t & A12_p = *bssn->fields["A12_p"];
  arr_t & A13_p = *bssn->fields["A13_p"];
  arr_t & A22_p = *bssn->fields["A22_p"];
  arr_t & A23_p = *bssn->fields["A23_p"];
  arr_t & A33_p = *bssn->fields["A33_p"];
  arr_t & DIFFD_a = *stat->fields["DIFFD_a"];

  // Extra / auxiliary fields for intermediate computations
  // (re-use _c register, it gets overwritten later anyways)
  arr_t & phi_N = *bssn->fields["DIFFphi_c"];
  arr_t & lap_phi_N = *bssn->fields["DIFFK_c"];
  arr_t & invlape6pd1K = *bssn->fields["DIFFgamma11_c"];
  arr_t & invlape6pd2K = *bssn->fields["DIFFgamma12_c"];
  arr_t & invlape6pd3K = *bssn->fields["DIFFgamma13_c"];
  arr_t & W1 = *bssn->fields["A22_c"];
  arr_t & W2 = *bssn->fields["A23_c"];
  arr_t & W3 = *bssn->fields["A33_c"];

  // 1.a) Synchronous-gauge Newtonian potential:
  set_gaussian_random_Phi_N(phi_N, fourier, P, p0, p_cut);

  // 1.b) Preliminary metric variables: phi, K
# pragma omp parallel for default(shared) private(i,j,k)
  LOOP3(i,j,k)
  {
    idx_t idx = NP_INDEX(i,j,k);

    lap_phi_N[idx] = laplacian(i, j, k, phi_N);

    DIFFphi_p[idx] = log1p(-10.0/3.0*phi_N[idx])/4.0;
    DIFFK_p[idx] = -3.0*(1.0 + 2.0*phi_N[idx]) + 2.0/3.0*lap_phi_N[idx];
  }

  // 1.c) Vector contribution to the extrinsic curvature, W^i
  // compute invlapK
# pragma omp parallel for default(shared) private(i,j,k)
  LOOP3(i,j,k) {
    idx_t idx = NP_INDEX(i,j,k);
    real_t e6p = exp(6.0*DIFFphi_p[idx]);
    invlape6pd1K[idx] = e6p*derivative(i,j,k,1,DIFFK_p);
    invlape6pd2K[idx] = e6p*derivative(i,j,k,2,DIFFK_p);
    invlape6pd3K[idx] = e6p*derivative(i,j,k,3,DIFFK_p);
  }
  fourier->inverseLaplacian <idx_t, real_t> (invlape6pd1K._array);
  fourier->inverseLaplacian <idx_t, real_t> (invlape6pd2K._array);
  fourier->inverseLaplacian <idx_t, real_t> (invlape6pd3K._array);
  // compute W^i = W_i
# pragma omp parallel for default(shared) private(i,j,k)
  LOOP3(i,j,k)
  {
    idx_t idx = NP_INDEX(i,j,k);
    W1[idx] = -derivative(i,j,k,1,phi_N)/3.0 + invlape6pd1K[idx]/2.0;
    W2[idx] = -derivative(i,j,k,2,phi_N)/3.0 + invlape6pd2K[idx]/2.0;
    W3[idx] = -derivative(i,j,k,3,phi_N)/3.0 + invlape6pd3K[idx]/2.0;
  }

  // 1.d) Aij components
# pragma omp parallel for default(shared) private(i,j,k)
  LOOP3(i,j,k)
  {
    idx_t idx = NP_INDEX(i,j,k);
    // these are the CTT conformal Aij
    real_t CTT2BSSNAij = exp(-6.0*DIFFphi_p[idx]);
    real_t DkWk = derivative(i,j,k,1,W1) + derivative(i,j,k,2,W2) + derivative(i,j,k,3,W3);
    A11_p[idx] = CTT2BSSNAij * ( derivative(i,j,k,1,W1) + derivative(i,j,k,1,W1) + 2.0/3.0*double_derivative(i,j,k,1,1,phi_N)
      - 2.0/3.0*DkWk - 2.0/9.0*lap_phi_N[idx] );
    A12_p[idx] = CTT2BSSNAij * ( derivative(i,j,k,1,W2) + derivative(i,j,k,2,W1) + 2.0/3.0*double_derivative(i,j,k,1,2,phi_N) );
    A13_p[idx] = CTT2BSSNAij * ( derivative(i,j,k,1,W3) + derivative(i,j,k,3,W1) + 2.0/3.0*double_derivative(i,j,k,1,3,phi_N) );
    A22_p[idx] = CTT2BSSNAij * ( derivative(i,j,k,2,W2) + derivative(i,j,k,2,W2) + 2.0/3.0*double_derivative(i,j,k,2,2,phi_N)
      - 2.0/3.0*DkWk - 2.0/9.0*lap_phi_N[idx] );
    A23_p[idx] = CTT2BSSNAij * ( derivative(i,j,k,2,W3) + derivative(i,j,k,3,W2) + 2.0/3.0*double_derivative(i,j,k,2,3,phi_N) );
    A33_p[idx] = CTT2BSSNAij * ( derivative(i,j,k,3,W3) + derivative(i,j,k,3,W3) + 2.0/3.0*double_derivative(i,j,k,3,3,phi_N)
      - 2.0/3.0*DkWk - 2.0/9.0*lap_phi_N[idx] );
  }

  // 1.e) Density field
# pragma omp parallel for default(shared) private(i,j,k)
  LOOP3(i,j,k)
  {
    idx_t idx = NP_INDEX(i,j,k);

    real_t AijAij = A11_p[idx]*A11_p[idx] + A22_p[idx]*A22_p[idx] + A33_p[idx]*A33_p[idx]
      + 2.0*(A12_p[idx]*A12_p[idx] + A13_p[idx]*A13_p[idx] + A23_p[idx]*A23_p[idx]);
    real_t lap_e_p = exp(DIFFphi_p[idx]) * ( laplacian(i,j,k,DIFFphi_p)
      + pw2(derivative(i,j,k,1,DIFFphi_p)) + pw2(derivative(i,j,k,2,DIFFphi_p)) + pw2(derivative(i,j,k,3,DIFFphi_p)) );
    real_t rho_ADM = 1.0/2.0/PI * ( DIFFK_p[idx]*DIFFK_p[idx]/12.0 - AijAij/8.0 - exp(-5.0*DIFFphi_p[idx])*lap_e_p );
    real_t rho_0 = rho_ADM - rho_L;

    if(rho_0 < 0.0)
    {
      iodata->log("Error: negative density in some regions.");
      throw -1;
    }

    DIFFD_a[idx] = exp(6.0*DIFFphi_p[idx])*rho_0;
  }

  iodata->log( "Average fluctuation density: " + stringify(average(DIFFD_a)) );
  iodata->log( "Std.dev fluctuation density: " + stringify(standard_deviation(DIFFD_a)) );
}

void static_ic_set_sinusoid_3d(BSSN * bssn, Static * stat, Lambda * lambda, Fourier * fourier,
  IOData * iodata)
{

  idx_t i, j, k;

  arr_t & DIFFr_a = *bssn->fields["DIFFr_a"];
  arr_t & DIFFphi_p = *bssn->fields["DIFFphi_p"];
  arr_t & DIFFD_a = *stat->fields["DIFFD_a"];

  real_t rho_FRW = 3.0/PI/8.0;

  real_t Omega_L = std::stod(_config("Omega_L", "0.0"));
  real_t rho_m = (1.0 - Omega_L) * rho_FRW;
  real_t rho_L = Omega_L * rho_FRW;
  lambda->setLambda(rho_L);

  real_t A = H_LEN_FRAC*H_LEN_FRAC*std::stod(_config("peak_amplitude_frac", "0.001"));

  // the conformal factor in front of metric is the solution to
  // d^2 exp(\phi) = -2*pi exp(5\phi) * \delta_rho
  // generate random mode in \phi
  // delta_rho = -(lap e^\phi)/e^(4\phi)/2pi
  real_t phix = std::stod(_config("phix", "0.0"));
  real_t phiy = phix, phiz = phix;

  // grid values
  LOOP3(i,j,k)
  {
    idx_t idx = NP_INDEX(i,j,k);

    real_t x_frac = ((real_t) i / (real_t) NX),
      y_frac = ((real_t) j / (real_t) NY), z_frac = ((real_t) k / (real_t) NZ);

    real_t phi = A*
      (sin(2.0*PI*x_frac + phix) + sin(2.0*PI*y_frac + phiy) + sin(2.0*PI*z_frac + phiz));

    real_t DIFFrho = -exp(-4.0*phi)/PI/2.0*(
      pw2(A*2.0*PI / H_LEN_FRAC * cos(2.0*PI*x_frac + phix))
      + pw2(A*2.0*PI / H_LEN_FRAC * cos(2.0*PI*y_frac + phiy))
      + pw2(A*2.0*PI / H_LEN_FRAC * cos(2.0*PI*z_frac + phiz))
      -  ( A * pw2(2.0*PI / H_LEN_FRAC) * sin(2.0*PI*x_frac + phix)
           + A * pw2(2.0*PI / H_LEN_FRAC) * sin(2.0*PI*y_frac + phiy)
           + A * pw2(2.0*PI / H_LEN_FRAC) * sin(2.0*PI*z_frac + phiz))
    );

    // These aren't difference vars
    DIFFphi_p[NP_INDEX(i,j,k)] = phi;
    DIFFr_a[idx] = DIFFrho;

// // debugging: throw away field
// DIFFD_a[idx] = phi;
  }
// std::cout << std::setprecision(17);
// std::cout << "field[0] = " << DIFFD_a[0] << "; ";
// fourier->inverseLaplacian <idx_t, real_t> (DIFFD_a._array);
// std::cout << "lap/lap field = " << laplacian(0,0,0,DIFFD_a) << "\n";

  // Make sure min density value > 0
  // Set conserved density variable field
  real_t min = rho_m;
  real_t max = min;
  LOOP3(i,j,k)
  {
    idx_t idx = NP_INDEX(i,j,k);
    real_t DIFFr = DIFFr_a[idx];
    real_t rho = rho_m + DIFFr;
    // phi_FRW = 0
    real_t DIFFphi = DIFFphi_p[idx];
    // phi = DIFFphi
    // DIFFK = 0

    DIFFD_a[idx] =
      rho_m*expm1(6.0*DIFFphi) + exp(6.0*DIFFphi)*DIFFr;

    if(rho < min)
    {
      min = rho;
    }
    if(rho > max)
    {
      max = rho;
    }
    if(rho != rho)
    {
      iodata->log("Error: NaN energy density.");
      throw -1;
    }
  }

  iodata->log( "Minimum fluid density: " + stringify(min) );
  iodata->log( "Maximum fluid density: " + stringify(max) );
  iodata->log( "Average fluctuation density: " + stringify(average(DIFFD_a)) );
  iodata->log( "Std.dev fluctuation density: " + stringify(standard_deviation(DIFFD_a)) );
  if(min < 0.0)
  {
    iodata->log("Error: negative density in some regions.");
    throw -1;
  }

# if USE_REFERENCE_FRW
  // Set values in reference FRW integrator
  auto & frw = bssn->frw;
  real_t K_FRW = -sqrt(24.0*PI*rho_FRW);
  frw->set_phi(0.0);
  frw->set_K(K_FRW);
  frw->addFluid(rho_m, 0.0 /* w=0 */);
# else
  arr_t & DIFFK_p = *bssn->fields["DIFFK_p"];
  arr_t & DIFFK_a = *bssn->fields["DIFFK_a"];
  // add in FRW pieces to ICs
  // phi is unchanged
  // rho (D) and K get contribs
  // w=0 fluid only
# pragma omp parallel for default(shared) private(i,j,k)
  LOOP3(i,j,k)
  {
    idx_t idx = NP_INDEX(i,j,k);

    real_t D_FRW = rho_m; // on initial slice

    DIFFr_a[idx] += rho_m;

    DIFFK_a[idx] = -sqrt(24.0*PI*rho_FRW);
    DIFFK_p[idx] = -sqrt(24.0*PI*rho_FRW);

    DIFFD_a[idx] += D_FRW;
  }
# endif

}

/**
 * @brief Sinusoidal mode ICs
 */
void static_ic_set_sinusoid(BSSN * bssn, Static * stat, Lambda * lambda, Fourier * fourier,
  IOData * iodata)
{
  idx_t i, j, k;

  arr_t & DIFFr_a = *bssn->fields["DIFFr_a"];
  arr_t & DIFFphi_p = *bssn->fields["DIFFphi_p"];
  arr_t & DIFFD_a = *stat->fields["DIFFD_a"];

  real_t rho_FRW = 3.0/PI/8.0;

  real_t Omega_L = std::stod(_config("Omega_L", "0.0"));
  real_t rho_m = (1.0 - Omega_L) * rho_FRW;
  real_t rho_L = Omega_L * rho_FRW;
  lambda->setLambda(rho_L);

  real_t A = H_LEN_FRAC*H_LEN_FRAC*std::stod(_config("peak_amplitude_frac", "0.001"));

  // the conformal factor in front of metric is the solution to
  // d^2 exp(\phi) = -2*pi exp(5\phi) * \delta_rho
  // generate random mode in \phi
  // delta_rho = -(lap e^\phi)/e^(4\phi)/2pi
  real_t phix = std::stod(_config("phix", "0.0"));
  real_t twopi_L = 2.0*PI/H_LEN_FRAC;
  real_t pw2_twopi_L = twopi_L*twopi_L;
  // grid values
  LOOP3(i,j,k)
  {
    idx_t idx = NP_INDEX(i,j,k);

    real_t x = ((real_t) i / (real_t) NX);
    real_t phi = A*sin(2.0*PI*x + phix);
    real_t DIFFrho = -exp(-4.0*phi)/PI/2.0*(
        pw2(twopi_L*A*cos(2.0*PI*x + phix))
        - pw2_twopi_L*A*sin(2.0*PI*x + phix)
      );

    // These aren't difference vars
    DIFFphi_p[NP_INDEX(i,j,k)] = phi;
    DIFFr_a[idx] = DIFFrho;

  }

  // Make sure min density value > 0
  // Set conserved density variable field
  real_t min = rho_m;
  real_t max = min;
  LOOP3(i,j,k)
  {
    idx_t idx = NP_INDEX(i,j,k);
    real_t DIFFr = DIFFr_a[idx];
    real_t rho = rho_m + DIFFr;
    // phi_FRW = 0
    real_t DIFFphi = DIFFphi_p[idx];
    // phi = DIFFphi
    // DIFFK = 0

    DIFFD_a[idx] =
      rho_m*expm1(6.0*DIFFphi) + exp(6.0*DIFFphi)*DIFFr;

    if(rho < min)
    {
      min = rho;
    }
    if(rho > max)
    {
      max = rho;
    }
    if(rho != rho)
    {
      iodata->log("Error: NaN energy density.");
      throw -1;
    }
  }

  iodata->log( "Minimum fluid density: " + stringify(min) );
  iodata->log( "Maximum fluid density: " + stringify(max) );
  iodata->log( "Average fluctuation density: " + stringify(average(DIFFD_a)) );
  iodata->log( "Std.dev fluctuation density: " + stringify(standard_deviation(DIFFD_a)) );
  if(min < 0.0)
  {
    iodata->log("Error: negative density in some regions.");
    throw -1;
  }

# if USE_REFERENCE_FRW
  // Set values in reference FRW integrator
  auto & frw = bssn->frw;
  real_t K_FRW = -sqrt(24.0*PI*rho_FRW);
  frw->set_phi(0.0);
  frw->set_K(K_FRW);
  frw->addFluid(rho_m, 0.0 /* w=0 */);
# else
  arr_t & DIFFK_p = *bssn->fields["DIFFK_p"];
  arr_t & DIFFK_a = *bssn->fields["DIFFK_a"];
  // add in FRW pieces to ICs
  // phi is unchanged
  // rho (D) and K get contribs
  // w=0 fluid only
# pragma omp parallel for default(shared) private(i,j,k)
  LOOP3(i,j,k)
  {
    idx_t idx = NP_INDEX(i,j,k);

    real_t D_FRW = rho_m; // on initial slice

    DIFFr_a[idx] += rho_m;

    DIFFK_a[idx] = -sqrt(24.0*PI*rho_FRW);
    DIFFK_p[idx] = -sqrt(24.0*PI*rho_FRW);

    DIFFD_a[idx] += D_FRW;
  }
# endif
}

void static_ic_set_semianalytic(
  BSSN * bssn, Static * stat, Lambda * lambda, Fourier * fourier,
  IOData * iodata)
{
  idx_t i, j, k;

  arr_t & DIFFr_a = *bssn->fields["DIFFr_a"];
  arr_t & DIFFphi_p = *bssn->fields["DIFFphi_p"];
  arr_t & DIFFD_a = *stat->fields["DIFFD_a"];
  arr_t & DIFFK_p = *bssn->fields["DIFFK_p"];

  real_t rho_FRW = 3.0/PI/8.0;
  real_t K_FRW = -3.0;

  real_t Omega_L = std::stod(_config("Omega_L", "0.0"));
  real_t rho_m = (1.0 - Omega_L) * rho_FRW;
  real_t rho_L = Omega_L * rho_FRW;
  lambda->setLambda(rho_L);
  real_t L = H_LEN_FRAC;
  real_t A = std::stod(_config("peak_amplitude", "0.001"))*0.026699*L*L;
  // grid values
  LOOP3(i,j,k)
  {
    idx_t idx = NP_INDEX(i,j,k);
    real_t x = ((real_t) i) * dx;
    DIFFphi_p[idx] = std::log1p( A*std::sin(2.0*PI*x/L) );
    DIFFr_a[idx] = rho_m + 2.0*A*PI*std::sin((2*PI*x)/L)
      / ( L*L*std::pow(1 + A*std::sin((2*PI*x)/L), 5) );
    DIFFK_p[idx] = K_FRW;
    DIFFD_a[idx] = exp(6.0*DIFFphi_p[idx])*DIFFr_a[idx];
  }

  // Make sure min density value > 0
  // Set conserved density variable field
  real_t min = rho_m;
  real_t max = min;
  LOOP3(i,j,k)
  {
    real_t rho = DIFFr_a[NP_INDEX(i,j,k)];
    if(rho < min)
    {
      min = rho;
    }
    if(rho > max)
    {
      max = rho;
    }
    if(rho != rho)
    {
      iodata->log("Error: NaN energy density.");
      throw -1;
    }
  }

  iodata->log( "Minimum fluid density: " + stringify(min) );
  iodata->log( "Maximum fluid density: " + stringify(max) );
  iodata->log( "Average fluctuation density: " + stringify(average(DIFFD_a)) );
  iodata->log( "Std.dev fluctuation density: " + stringify(standard_deviation(DIFFD_a)) );
  if(min < 0.0)
  {
    iodata->log("Error: negative density in some regions.");
    throw -1;
  }
}

/**
 * @brief Spherical "shell" of perturbations around an observer
 */
void static_ic_set_sphere(BSSN * bssn, Static * stat, IOData * iodata)
{
  idx_t i, j, k;

  arr_t & DIFFr_a = *bssn->fields["DIFFr_a"];
  arr_t & DIFFphi_p = *bssn->fields["DIFFphi_p"];
  arr_t & DIFFphi_a = *bssn->fields["DIFFphi_a"];
  arr_t & DIFFphi_f = *bssn->fields["DIFFphi_f"];

  arr_t & DIFFD_a = *stat->fields["DIFFD_a"];

  // shell amplitude
  const real_t A = stod(_config("shell_amplitude", "1e-5"));
  // Shell described by only one fixed l:
  const idx_t l = stoi(_config("shell_angular_scale_l", "1"));
  iodata->log( "Generating ICs with shell angular scale of l = " + stringify(l) );
  iodata->log( "Generating ICs with peak amp. = " + stringify(A) );

  // spherical shell of perturbations in phi0field

  // place shell around center of box
  real_t x0 = (NX-0.001)*dx/2.0;
  real_t y0 = (NY-0.001)*dx/2.0;
  real_t z0 = (NZ-0.001)*dx/2.0;

  // place spherical "shell" of fluctuations at r = NX/4, 1/2-way between observer and boundary
  real_t r_shell = NX*dx / 4.0;
  // shell width
  real_t shell_width = NX*dx / 40.0;

  // Angular fluctuations in shell described by spherical harmonic coeffs, a_lm's,
  complex_t * alms = new complex_t[m_idx(l,l)+1];
  const real_t seed = stod(_config("mt19937_seed", "7"));
  std::mt19937 gen(seed);
  std::normal_distribution<> normal_dist(0.0, 1.0);
  std::uniform_real_distribution<> uniform_dist(0.0, 2.0*PI);

  std::cout << "normal_dist(gen) = " << normal_dist(gen) << ", uniform_dist(gen) = " << uniform_dist(gen) << "\n";

  // zero mode:
  alms[m_idx(l, 0)].first = normal_dist(gen);
  alms[m_idx(l, 0)].second = 0;
  // positive modes:
  for(int m = 1; m <= l; m++)
  {
    real_t phase = uniform_dist(gen);
    real_t amp = normal_dist(gen);
    alms[m_idx(l,m)].first = amp*std::cos(phase);
    alms[m_idx(l,m)].second = amp*std::sin(phase);
  }
  // negative modes:
  for(int m = -l; m <= -1; m++)
  {
    real_t Condon_Shortley_phase = std::abs(m) % 2 ? -1.0 : 1.0; // 0 (false) if m even, 1 (true) if odd
    alms[m_idx(l,m)].first = Condon_Shortley_phase*alms[m_idx(l,std::abs(m))].first;
    alms[m_idx(l,m)].second = -Condon_Shortley_phase*alms[m_idx(l,std::abs(m))].second;
  }

  for(int m = -l; m <= l; m++)
  {
    std::cout << "Amp. of a_{" << l << "," << m << "} = " << alms[m_idx(l,m)].first
      << " + " << alms[m_idx(l,m)].second << "i\n";
  }

  LOOP3(i,j,k) {
    idx_t idx = NP_INDEX(i,j,k);

    real_t x = i*dx;
    real_t y = j*dx;
    real_t z = k*dx;

    real_t r = sqrt( pw2(x - x0) + pw2(y - y0) + pw2(z - z0) );
    real_t theta = acos((z - z0) / r); // "theta" (polar angle)
    real_t phi = atan2(y - y0, x - x0); // "phi" (azimuthal angle)

    real_t DIFFphi_r = 0.0;
    real_t DIFFphi_i = 0.0;
    for(int m=-l; m<=l; m++)
    {
      real_t Y_r = boost::math::spherical_harmonic_r(l, m, theta, phi);
      real_t Y_i = boost::math::spherical_harmonic_i(l, m, theta, phi);

      DIFFphi_r += alms[m_idx(l,m)].first*Y_r - alms[m_idx(l,m)].second*Y_i;
      DIFFphi_i += alms[m_idx(l,m)].first*Y_i + alms[m_idx(l,m)].second*Y_r;
    }
    if(std::abs(DIFFphi_i) > 1e-6)
    {
      iodata->log("Significant non-zero imaginary component of solution exists!");
      throw -1;
    }

    // gaussian profile shell of fluctuations
    real_t U_r = A*std::exp( -pw2((r - r_shell)/2.0/shell_width) );
    // cosine profile
    // real_t U_r = (r < r_shell-shell_width || r > r_shell+shell_width ) ? 0 : A*(1+std::cos(PI*(r-r_shell)/shell_width));

    DIFFphi_p[idx] = U_r*DIFFphi_r;

    if(i==NX/2 && j==NY/2 && k==NZ/2)
      std::cout << "At a grid point close to the middle, r = " << r
                << ", r_shell = " << r_shell
                << ", shell_width = " << shell_width
                << ", U_r = " << U_r
                << ", DIFFphi_p = " << DIFFphi_p[idx] << "\n";
  }
  // cleanup
  delete [] alms;

  // delta_rho = -lap(phi)/(1+xi)^5/2pi
# pragma omp parallel for default(shared) private(i,j,k)
  LOOP3(i,j,k) {
    DIFFr_a[NP_INDEX(i,j,k)] = -0.5/PI/(
      pow(1.0 + DIFFphi_p[NP_INDEX(i,j,k)], 5.0)
    )*(
      double_derivative(i, j, k, 1, 1, DIFFphi_p)
      + double_derivative(i, j, k, 2, 2, DIFFphi_p)
      + double_derivative(i, j, k, 3, 3, DIFFphi_p)
    );
  }

  // phi = ln(xi)
# pragma omp parallel for default(shared) private(i,j,k)
  LOOP3(i,j,k) {
    idx_t idx = NP_INDEX(i,j,k);
    DIFFphi_a[idx] = log1p(DIFFphi_p[idx]);
    DIFFphi_f[idx] = log1p(DIFFphi_p[idx]);
    DIFFphi_p[idx] = log1p(DIFFphi_p[idx]);
  }

  // Make sure min density value > 0
  // Set conserved density variable field
  real_t min = 3.0/PI/8.0;
  real_t max = min;
  LOOP3(i,j,k)
  {
    idx_t idx = NP_INDEX(i,j,k);
    real_t rho_FRW = 3.0/PI/8.0;
    real_t DIFFr = DIFFr_a[idx];
    real_t rho = rho_FRW + DIFFr;
    // phi_FRW = 0
    real_t DIFFphi = DIFFphi_a[idx];
    // phi = DIFFphi
    // DIFFK = 0

    DIFFD_a[idx] =
      rho_FRW*expm1(6.0*DIFFphi) + exp(6.0*DIFFphi)*DIFFr;

    if(rho < min)
    {
      min = rho;
    }
    if(rho > max)
    {
      max = rho;
    }
    if(rho != rho)
    {
      iodata->log("Error: NaN energy density.");
      throw -1;
    }
  }

  iodata->log( "Minimum fluid density: " + stringify(min) );
  iodata->log( "Maximum fluid density: " + stringify(max) );
  iodata->log( "Average fluctuation density: " + stringify(average(DIFFD_a)) );
  iodata->log( "Std.dev fluctuation density: " + stringify(standard_deviation(DIFFD_a)) );
  if(min < 0.0)
  {
    iodata->log("Error: negative density in some regions.");
    throw -1;
  }

# if USE_REFERENCE_FRW
  // Set values in reference FRW integrator
  real_t rho_FRW = 3.0/PI/8.0;
  real_t K_frw = -sqrt(24.0*PI*rho_FRW);

  auto & frw = bssn->frw;
  frw->set_phi(0.0);
  frw->set_K(K_frw);
  frw->addFluid(rho_FRW, 0.0 /* w=0 */);
# else
  arr_t & DIFFK_p = *bssn->fields["DIFFK_p"];
  arr_t & DIFFK_a = *bssn->fields["DIFFK_a"];
  // add in FRW pieces to ICs
  // phi is unchanged
  // rho (D) and K get contribs
  // w=0 fluid only
# pragma omp parallel for default(shared) private(i,j,k)
  LOOP3(i,j,k)
  {
    idx_t idx = NP_INDEX(i,j,k);
    real_t rho_FRW = 3.0/PI/8.0;
    real_t D_FRW = rho_FRW; // on initial slice

    DIFFr_a[idx] += rho_FRW;

    DIFFK_a[idx] = -sqrt(24.0*PI*rho_FRW);
    DIFFK_p[idx] = -sqrt(24.0*PI*rho_FRW);

    DIFFD_a[idx] += D_FRW;
  }
# endif
}

}


#include <iostream>
//#include "snake-ai.h"

#include <math.h>
#include <vector>
#include <deque>

#include <dlib/matrix.h>
#include <dlib/dnn.h>
#include <thread>
#include <chrono>
#include <sstream>

using namespace dlib;
using namespace std;

using input_matrix_type = matrix<double, 3, 8>;

#if 0
using net_type = loss_multiclass_log<
                                fc<4,
                                relu<fc<18,
                                relu<fc<18,
                                input<  input_matrix_type
                                >>>>>>>;

#else
using net_type = loss_multiclass_log<
                                tag1<fc<4,relu<
                                tag2<fc<18,relu<
                                tag3<relu<fc<18,
                                input<  input_matrix_type
                                >>>>>>>>>>>;
#endif

int main()
{

	char membuf[10000] = {0};
/*
	FILE* fp = fopen( "snake.net", "r+" );

	for( int i; feof( fp ) == 0; i++ )
		::fread( &membuf[i], 1, 1, fp );

	cout.precision(9);

	for( int j=0; j < 24; j++ )
	{
		for( int i=0; i < 18; i++ )
		{
			cout << ((float*)(&membuf[0x49]))[18*j+i] << ' ';
		}
		cout << endl;
	}
	cout << endl;

	for( int j=0; j < 18; j++ )
	{
		for( int i=0; i < 18; i++ )
		{
			cout << ((float*)(&membuf[0x8fc]))[18*j+i] << ' ';
		}
		cout << endl;
	}
	cout << endl;

	for( int j=0; j < 4; j++ )
	{
		for( int i=0; i < 18; i++ )
		{
			cout << ((float*)(&membuf[0xffd]))[18*j+i] << ' ';
		}
		cout << endl;
	}
	cout << endl;
*/

	//Snake s;
	net_type net;

	//deserialize( "/tmp/snake.male.net" ) >> net;

	cout << net << endl << endl;

	cout << layer< tag2 >(net).subnet().layer_details()        << endl;
	cout << layer< tag3 >(net).subnet().layer_details()        << endl;

	cout << layer< tag2 >(net).subnet().get_output().nc()   << endl;
	cout << layer< tag2 >(net).subnet().get_output().host()   << endl;

	//cout << net.subnet().subnet().subnet().details() <<  endl;
	//cout << layer<3>(net).get_parameter_gradient().host()[0]        <<  endl;
	//const tensor &t = layer<3>(net).get_output();
	//cout << t.host() << endl << endl;

}


#include <iostream>
#include <iomanip>
#include <NTL/GF2X.h>
#include <stdint.h>
#include "xsadd.h"
#include "xsadd.c" // to check static functions

using namespace NTL;
using namespace std;

static void uztostring16(char * str, uz * x)
{
    int p = 0;
    int first = 1;
    for (int i = UZ_ARRAY_SIZE - 1; i >= 0; i--) {
	if (first) {
	    if (x->ar[i] != 0) {
		first = 0;
		sprintf(str, "%x", x->ar[i]);
		p = strlen(str);
	    }
	} else {
	    sprintf(str + p, "%04x", x->ar[i]);
	    p = p + 4;
	}
    }
    if (first) {
	strcpy(str, "0");
    }
}

//static const char * const characteristic_polynomial
//= "100000000008101840085118000000001";

void xsadd_calculate_jump_polynomial_debug(char *jump_str,
					   uint32_t mul_step,
					   const char * base_step)
{
    f2_polynomial jump_poly;
    f2_polynomial charcteristic;
    f2_polynomial tee;
    uz base;
    uz mul;
    uz step;
    char buff[200];

    strtopolynomial(&charcteristic, characteristic_polynomial);
    clear(&tee);
    tee.ar[0] = 2;
    string16touz(&base, base_step);
    uint32touz(&mul, mul_step);

    uztostring16(buff, &mul);
    cout << "mul:" << buff << endl;
    uztostring16(buff, &base);
    cout << "base:" << buff << endl;
    uz_mul(&step, &mul, &base);
    uztostring16(buff, &step);
    cout << "step:" << buff << endl;
    polynomial_power_mod(&jump_poly, &tee, &step, &charcteristic);
    polynomialtostr(jump_str, &jump_poly);
}

void xsadd_jump_by_polynomial_debug(xsadd_t *xsadd, const char * jump_str)
{
    f2_polynomial jump_poly;
    xsadd_t work_z;
    xsadd_t * work = &work_z;
    cout << "xsadd_jump_by_polynomial_debug: step1" << endl;
    *work = *xsadd;
    for (int i = 0; i < 4; i++) {
        work->state[i] = 0;
    }
    strtopolynomial(&jump_poly, jump_str);
    cout << "xsadd_jump_by_polynomial_debug: step2" << endl;
    char buff[200];
    polynomialtostr(buff, &jump_poly);
    cout << "jump_poly:" << buff << endl;
    for (int i = 0; i < POLYNOMIAL_ARRAY_SIZE; i++) {
	for (int j = 0; j < 32; j++) {
	    //cout << "(i,j) = (" << dec << i << "," << j << ")" << endl;
	    uint32_t mask = 1 << j;
	    if ((jump_poly.ar[i] & mask) != 0) {
		xsadd_add(work, xsadd);
	    }
	    xsadd_uint32(xsadd);
	}
    }
    cout << "xsadd_jump_by_polynomial_debug: step3" << endl;
    *xsadd = *work;
}

void xsadd_jump_debug(xsadd_t *xsadd,
		      uint32_t mul_step,
		      const char * base_step)
{
    char jump_str[200];
    cout << "step1" << endl;
    xsadd_calculate_jump_polynomial_debug(jump_str, mul_step, base_step);
    cout << "jump_str:" << jump_str << endl;
    cout << "step2" << endl;
    xsadd_jump_by_polynomial_debug(xsadd, jump_str);
    cout << "step3" << endl;
}

void test_xsadd_jump_debug()
{
    const char * base_step = "0";
    uint32_t step = 139;
    uint32_t seed = 1791095845;
    xsadd_t xsadd;
    xsadd_init(&xsadd, seed);
    xsadd_jump_debug(&xsadd, step, base_step);
    cout << "debug end" << endl;
}

int main() {
    test_xsadd_jump_debug();
}


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#include <iostream>
#include <boost/timer.hpp>
#include <boost/numeric/mtl/mtl.hpp>

using namespace std;

template <typename Vector>
void test(const char* name)
{
    using boost::timer;

    for (size_t n= 4096; n < 6000000; n*= 2) {

	Vector                    x(3*n, 10.0), y(3*n, 4.0), x2(3*n, 10.0), y2(3*n, 0.0);
	mtl::multi_vector<Vector> A(n, 3), B(n, 3), C(n, 3), D(n, 3);
	A= 0.0; B= 10.0; C= 4.0;

	boost::timer timer;
	int reps= 0;
	for (; timer.elapsed() < 3.0; reps++)
	    for (int i= 0; i < 10000; i++)
		A+= 2.0 * B + 3 * C;
	double t1= timer.elapsed();
	std::cout << n << " " << timer.elapsed() / reps; // << "\n";

	boost::timer timer2;
	for (int r= 0; r < reps ; r++)
	    for (int i= 0; i < 10000; i++)
		y2+= 2.0 * x + 3 * y;
	double t2= timer2.elapsed();
	std::cout << " " << timer2.elapsed() / reps << " == " << (t1 - t2) / t2 * 100 << "%\n";
    }
}

int main(int, char**)
{
    test<mtl::dense_vector<double> >("dense_vector<double>");

    return 0;
}


/**
 * \ file PowerFilter.cpp
 */

#include <ATK/Dynamic/PowerFilter.h>

#include <ATK/Core/Utilities.h>

#include <ATK/Mock/FFTCheckerFilter.h>
#include <ATK/Mock/SimpleSinusGeneratorFilter.h>

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>

constexpr gsl::index PROCESSSIZE = 1024*64;

BOOST_AUTO_TEST_CASE( PowerFilter_RMS_memory_test )
{
  ATK::PowerFilter<double> filter;
  filter.set_memory(0.5);
  BOOST_CHECK_EQUAL(filter.get_memory(), 0.5);
}

BOOST_AUTO_TEST_CASE( PowerFilter_RMS_throw_memory_0_test )
{
  ATK::PowerFilter<double> filter;
  BOOST_CHECK_THROW(filter.set_memory(-1), ATK::RuntimeError);
}

BOOST_AUTO_TEST_CASE( PowerFilter_RMS_throw_memory_1_test )
{
  ATK::PowerFilter<double> filter;
  BOOST_CHECK_THROW(filter.set_memory(1), ATK::RuntimeError);
}

BOOST_AUTO_TEST_CASE( PowerFilter_RMS_0_1k_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(1000);

  ATK::PowerFilter<double> filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_memory(0);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(0, 1));
  frequency_checks.push_back(std::make_pair(100, 0));
  frequency_checks.push_back(std::make_pair(1000, 0));
  frequency_checks.push_back(std::make_pair(2000, 0.7071067811865475244)); // Power will multiple frequency by 2
  frequency_checks.push_back(std::make_pair(4000, 0));
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}

BOOST_AUTO_TEST_CASE( PowerFilter_RMS_05_1k_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(1000);

  ATK::PowerFilter<double> filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_memory(0.5);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(0, 1));
  frequency_checks.push_back(std::make_pair(100, 0));
  frequency_checks.push_back(std::make_pair(1000, 0));
  frequency_checks.push_back(std::make_pair(2000, 0.6947103730615206));
  frequency_checks.push_back(std::make_pair(4000, 0));
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}

BOOST_AUTO_TEST_CASE( PowerFilter_RMS_09_1k_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(1000);

  ATK::PowerFilter<double> filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_memory(0.9);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(0, 1));
  frequency_checks.push_back(std::make_pair(100, 0));
  frequency_checks.push_back(std::make_pair(1000, 0));
  frequency_checks.push_back(std::make_pair(2000, 0.49107208814492116)); // cos power=1/(1+memory**2-2*memory*cos(2*pi*f/fs))
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}

BOOST_AUTO_TEST_CASE( PowerFilter_RMS_0_100_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(100);

  ATK::PowerFilter<double> filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_memory(0);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(0, 1));
  frequency_checks.push_back(std::make_pair(100, 0));
  frequency_checks.push_back(std::make_pair(200, 0.7071067811865475244)); // Power will multiple frequency by 2
  frequency_checks.push_back(std::make_pair(400, 0));
  frequency_checks.push_back(std::make_pair(1000, 0));
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}

BOOST_AUTO_TEST_CASE( PowerFilter_RMS_05_100_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(100);

  ATK::PowerFilter<double> filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_memory(0.5);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(0, 1));
  frequency_checks.push_back(std::make_pair(100, 0));
  frequency_checks.push_back(std::make_pair(200, 0.7069768533999591));
  frequency_checks.push_back(std::make_pair(400, 0));
  frequency_checks.push_back(std::make_pair(1000, 0));
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}

BOOST_AUTO_TEST_CASE( PowerFilter_RMS_09_100_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(100);

  ATK::PowerFilter<double> filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_memory(0.9);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(0, 1));
  frequency_checks.push_back(std::make_pair(1000, 0));
  frequency_checks.push_back(std::make_pair(100, 0));
  frequency_checks.push_back(std::make_pair(200, 0.7013753920393636));
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}

BOOST_AUTO_TEST_CASE( PowerFilter_RMS_0_10k_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(10000);

  ATK::PowerFilter<double> filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_memory(0);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(0, 1));
  frequency_checks.push_back(std::make_pair(100, 0));
  frequency_checks.push_back(std::make_pair(10000, 0));
  frequency_checks.push_back(std::make_pair(20000, 0.7071067811865475244)); // Power will multiple frequency by 2
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}

BOOST_AUTO_TEST_CASE( PowerFilter_RMS_05_10k_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(10000);

  ATK::PowerFilter<double> filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_memory(0.5);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(0, 1));
  frequency_checks.push_back(std::make_pair(1000, 0));
  frequency_checks.push_back(std::make_pair(10000, 0));
  frequency_checks.push_back(std::make_pair(20000, 0.4452828409472382));
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}

BOOST_AUTO_TEST_CASE( PowerFilter_RMS_09_10k_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(10000);

  ATK::PowerFilter<double> filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_memory(0.9);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(0, 1));
  frequency_checks.push_back(std::make_pair(1000, 0));
  frequency_checks.push_back(std::make_pair(10000, 0));
  frequency_checks.push_back(std::make_pair(20000, 0.1792501270985228));
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}


#pragma once
#ifndef KERNEL_H
#define KERNEL_H

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

namespace Kernel
{
    double dot(const std::vector<Eigen::Vector3d> & v1, const std::vector<Eigen::Vector3d> & v2);
}

#endif


#pragma once

#include <boost/utility.hpp>
#include <MicropolygonMesh.hpp>
#include <vector>
#include <VertexType.hpp>

template <class SurfaceType>
struct SurfaceSubdivider : boost::noncopyable {
	typedef typename VertexType<SurfaceType>::type VertexType;
	typedef typename ScalarType<VertexType>::type ScalarType;

	private:
		struct NodeType {
			VertexType vertex_;

			ScalarType u_, v_;
			Vec4 rhw_;
			Vec2 projected_position_;

			NodeType(SurfaceType const & surface, ScalarType const & u, ScalarType const & v)
				: vertex_(surface(u, v)), u_(u), v_(v),
					rhw_(rhw_position(vertex_)), projected_position_(rhw_.x(), rhw_.y())
			{
				projected_position_ /= (rhw_.z() * rhw_.w());
			}
		};

		typedef std::vector<NodeType> NodeBuffer;

	public:
		SurfaceSubdivider(SurfaceType const & surface)
			: surface_(surface), u_length_(2), v_length_(2)
		{
			ScalarType const zero = static_cast<ScalarType>(0.0),
				one = static_cast<ScalarType>(1.0);

			ping_.push_back(NodeType(surface_, zero, zero));
			ping_.push_back(NodeType(surface_, one, zero));
			ping_.push_back(NodeType(surface_, zero, one));
			ping_.push_back(NodeType(surface_, one, one));

			max_u_tol_ = (std::max)(
				tolerance_metric(ping_[0], ping_[1]),
				tolerance_metric(ping_[2], ping_[3])
			);

			max_v_tol_ = (std::max)(
				tolerance_metric(ping_[0], ping_[2]),
				tolerance_metric(ping_[1], ping_[3])
			);

			subdivide();
		}

		MicropolygonMesh<VertexType> * get_mesh() const {
			MicropolygonMesh<VertexType> * mesh = new MicropolygonMesh<VertexType>(u_length_, v_length_);

			for (int j = 0; j < v_length_; ++j) {
				for (int i = 0; i < u_length_; ++i) {
					(*mesh)(i, j) = ping_[j * u_length_ + i].vertex_;
				}
			}

			return mesh;
		}

	private:
		NodeType split(
			NodeType const & left, NodeType const & right
		) const
		{
			ScalarType u = .5 * left.u_ + .5 * right.u_,
				v = .5 * left.v_ + .5 * right.v_;

			return NodeType(surface_, u, v);
		}

		float tolerance_metric(NodeType const & left, NodeType const & right) const {
			return dist_sq(left.projected_position_, right.projected_position_);
		}

		void split_u() {
			for (int j = 0; j < v_length_; ++j) {
				auto left = ping_.begin() + (j * u_length_);
				pong_.push_back(*left);

				for (int i = 1; i < u_length_; ++i) {
					auto right = ping_.begin() + (j * u_length_ + i);

					pong_.push_back(split(*left, *right));
					pong_.push_back(*right);

					left = right;
				}
			}

			u_length_ += (u_length_ - 1);
		}

		void split_v() {
			int last_row = 0;

			for (int i = 0; i < u_length_; ++i)
				{ pong_.push_back(ping_[i]); }

			for (int j = 1; j < v_length_; ++j) {
				int next_row = j * u_length_;

				for (int i = 0; i < u_length_; ++i) {
					pong_.push_back(split(ping_[last_row + i], ping_[next_row + i]));
				}

				for (int i = 0; i < u_length_; ++i) {
					pong_.push_back(ping_[next_row + i]);
				}

				last_row = next_row;
			}

			v_length_ += (v_length_ - 1);
		}

		void calc_max_u_tol() {
			max_u_tol_ = 0;

			for (int j = 0; j < v_length_; ++j) {
				auto left = pong_.begin() + j * u_length_;
				for (int i = 1; i < u_length_; ++i) {
					auto right = pong_.begin() + j * u_length_ + i;
					max_u_tol_ = (std::max)(max_u_tol_, tolerance_metric(*left, *right));
					left = right;
				}
			}
		}

		void calc_max_v_tol() {
			max_v_tol_ = 0;

			auto last = pong_.begin();

			for (int j = 1; j < v_length_; ++j) {
				auto next = pong_.begin() + j * u_length_;
				auto b = last, t = next;

				for (; b < next; ++b, ++t) {
					max_v_tol_ = (std::max)(max_v_tol_, tolerance_metric(*b, *t));
				}

				last = next;
			}
		}

		void subdivide() {
			const float tolerance = 1.0f;

			while (max_u_tol_ >= tolerance || max_v_tol_ >= tolerance) {
				if (max_u_tol_ >= max_v_tol_) { split_u(); calc_max_u_tol(); }
				else { split_v(); calc_max_v_tol(); }

				ping_.swap(pong_);
				pong_.clear();
			}
		}

		SurfaceType const & surface_;
		NodeBuffer ping_, pong_;

		int u_length_, v_length_;
		float max_u_tol_, max_v_tol_;
};


// included by rem_factorial_custom.tpp (in case we want to change the file location)

#include <cassert>

#include <NTL/ZZ_pX.h>
#include <NTL/matrix.h>

#include "../elements/element.hpp"
#include "factorial_engine.hpp"

using NTL::ZZ;
using NTL::ZZ_p;
using NTL::ZZ_pX;
using NTL::Mat;

Elt<ZZ> calculate_factorial(long n, const Elt<ZZ>& m, const std::function<vector<Elt<ZZ>> (long, long)>& get_A, const PolyMatrix& formula){
    if (n == 0) {
        return Elt<ZZ>(1);
    }

    assert(("No formula given!" && not formula.empty()));
    assert(("Element given is not a square matrix." && (formula.size() == formula[0].size())));

    // Matrix-type elements
    assert(("ZZ's must have 1x1 matrix formulas." && ((long)formula.size() <= 1)));

    // Do naive_factorial if n is small enough
    if(n < 30000 || formula.size() == 0){
        return naive_factorial<Elt<ZZ>, Elt<ZZ>>(n, m, get_A); // TODO: write naive_factorial
    }
    long maxdeg = 0;
    for(const auto& term : formula[0][0]){
        if(term.first > maxdeg){
            maxdeg = term.first;
        }
    }
    // Linear and Polynomial-type elements
    if(n < 1000000000 || maxdeg >= 2){
        // TODO: convert polynomial coefficients into ZZ_p and call poly_factorial()
        // Otherwise, do poly_factorial
        ZZ_pX poly;
        for(const auto& term : formula[0][0]){
            ZZ_p coeff;
            coeff.init(m.t);
            coeff = term.second;
            SetCoeff(poly, term.first, coeff);
        }
        ZZ_p output;
        output.init(m.t);
        output = poly_factorial(n, m.t, poly); // TODO: write poly_factorial

        Elt<ZZ> output_elt(rep(output));

        return output_elt;
    }
    // Large Linear-type elements
    else{
        // TODO: convert polynomial coefficients into Mat<ZZ_p> and call matrix_factorial()
        Mat<ZZ_pX> matrix;
        matrix.SetDims(1, 1);
        ZZ_pX poly;
        for(const auto& term : formula[0][0]){
            ZZ_p coeff;
            coeff.init(m.t);
            coeff = term.second;
            SetCoeff(poly, term.first, coeff);
        }
        matrix.put(0, 0, poly);
        Mat<ZZ_p> output;
        output.SetDims(1, 1);
        output = matrix_factorial(n, m.t, matrix);
        Elt<ZZ> output_elt(rep(output.get(0, 0)));
        return output_elt;

    }
}


//
// Copyright 2019-2020 Mateusz Loskot <mateusz at loskot dot net>
// Copyright 2021 Pranam Lashkari <plashkari628@gmail.com>
//
// Distributed under 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/gil.hpp>

#include <boost/core/lightweight_test.hpp>

#include <boost/gil/pixel_numeric_operations.hpp>

#include <tuple>
#include <type_traits>

#include "test_utility_output_stream.hpp"
#include "core/test_fixture.hpp" // random_value
#include "core/pixel/test_fixture.hpp"

namespace gil = boost::gil;
namespace fixture = boost::gil::test::fixture;

struct test_plus_integer_same_types
{
    template <typename Pixel>
    void operator()(Pixel const&)
    {
        using pixel_t = Pixel;
        using channel_t = typename gil::channel_type<pixel_t>::type;
        gil::pixel_plus_t<pixel_t, pixel_t, pixel_t> f;
        {
            pixel_t p0;
            gil::static_fill(p0, static_cast<channel_t>(0));
            BOOST_TEST_EQ(f(p0, p0), p0);
        }
        {
            pixel_t p1;
            gil::static_fill(p1, static_cast<channel_t>(1));
            pixel_t r2;
            gil::static_fill(r2, static_cast<channel_t>(2));
            BOOST_TEST_EQ(f(p1, p1), r2);
        }
        {
            // Generates pixels with consecutive channel values: {1} or {1,2,3} or {1,2,3,4} etc.
            fixture::consecutive_value<channel_t> g(1);
            pixel_t p;
            gil::static_generate(p, [&g]() { return g(); });
            auto const r = f(p, p);
            BOOST_TEST_NE(r, p);
            BOOST_TEST_EQ(gil::at_c<0>(r), (gil::at_c<0>(p) + gil::at_c<0>(p)));
        }
    }
    static void run()
    {
        boost::mp11::mp_for_each<fixture::pixel_integer_types>(test_plus_integer_same_types{});
    }
};

struct test_minus_integer_same_types
{
    template <typename Pixel>
    void operator()(Pixel const&)
    {
        using pixel_t = Pixel;
        using channel_t = typename gil::channel_type<pixel_t>::type;
        gil::pixel_minus_t<pixel_t, pixel_t, pixel_t> f;

        pixel_t p0;
        gil::static_fill(p0, static_cast<channel_t>(0));
        BOOST_TEST_EQ(f(p0, p0), p0);
        {
            pixel_t p1, p2;
            gil::static_fill(p1, static_cast<channel_t>(1));
            gil::static_fill(p2, static_cast<channel_t>(2));
            pixel_t r1;
            gil::static_fill(r1, static_cast<channel_t>(1));
            BOOST_TEST_EQ(f(p2, p1), r1);
        }
        {
            // Generates pixels with consecutive channel values: {1} or {1,2,3} or {1,2,3,4} etc.
            fixture::consecutive_value<channel_t> g(1);
            pixel_t p;
            gil::static_generate(p, [&g]() { return g(); });
            BOOST_TEST_EQ(f(p, p), p0);
        }
    }
    static void run()
    {
        boost::mp11::mp_for_each<fixture::pixel_integer_types>(test_minus_integer_same_types{});
    }
};

struct test_pixel_multiplies_scalar_integer_same_types
{
    template <typename Pixel>
    void operator()(Pixel const&)
    {
        using pixel_t = Pixel;
        using channel_t = typename gil::channel_type<pixel_t>::type;
        gil::pixel_multiplies_scalar_t<pixel_t, channel_t, pixel_t> f;

        pixel_t p0;
        gil::static_fill(p0, static_cast<channel_t>(0));
        BOOST_TEST_EQ(f(p0, 0), p0);

        {
            pixel_t p1;
            gil::static_fill(p1, static_cast<channel_t>(1));
            BOOST_TEST_EQ(f(p1, 0), p0);
            BOOST_TEST_EQ(f(p1, 1), p1);
        }
        {
            // Generates pixels with consecutive channel values: {1} or {1,2,3} or {1,2,3,4} etc.
            fixture::consecutive_value<channel_t> g(1);
            pixel_t p;
            gil::static_generate(p, [&g]() { return g(); });

            // check first channel value is doubled
            auto const r = f(p, 2);
            BOOST_TEST_NE(r, p);
            BOOST_TEST_EQ(gil::at_c<0>(r), (gil::at_c<0>(p) * 2));
        }
    }
    static void run()
    {
        boost::mp11::mp_for_each<fixture::pixel_integer_types>(test_pixel_multiplies_scalar_integer_same_types{});
    }
};

struct test_pixel_multiply_integer_same_types
{
    template <typename Pixel>
    void operator()(Pixel const&)
    {
        using pixel_t = Pixel;
        using channel_t = typename gil::channel_type<pixel_t>::type;
        gil::pixel_multiply_t<pixel_t, pixel_t, pixel_t> f;

        pixel_t p0;
        gil::static_fill(p0, static_cast<channel_t>(0));
        BOOST_TEST_EQ(f(p0, p0), p0);

        pixel_t p1;
        gil::static_fill(p1, static_cast<channel_t>(1));
        BOOST_TEST_EQ(f(p1, p1), p1);

        pixel_t p2;
        gil::static_fill(p2, static_cast<channel_t>(2));
        BOOST_TEST_EQ(f(p1, p2), p2);
    }
    static void run()
    {
        boost::mp11::mp_for_each<fixture::pixel_integer_types>(test_pixel_multiply_integer_same_types{});
    }
};

struct test_pixel_divides_scalar_integer_same_types
{
    template <typename Pixel>
    void operator()(Pixel const&)
    {
        using pixel_t = Pixel;
        using channel_t = typename gil::channel_type<pixel_t>::type;
        gil::pixel_divides_scalar_t<pixel_t, channel_t, pixel_t> f;

        pixel_t p0;
        gil::static_fill(p0, static_cast<channel_t>(0));
        BOOST_TEST_EQ(f(p0, 1), p0);

        pixel_t p1;
        gil::static_fill(p1, static_cast<channel_t>(1));
        BOOST_TEST_EQ(f(p1, 1), p1);

        pixel_t p2;
        gil::static_fill(p2, static_cast<channel_t>(2));
        BOOST_TEST_EQ(f(p2, 2), p1);
    }
    static void run()
    {
        boost::mp11::mp_for_each<fixture::pixel_integer_types>(test_pixel_divides_scalar_integer_same_types{});
    }
};

struct test_pixel_divide_integer_same_types
{
    template <typename Pixel>
    void operator()(Pixel const&)
    {
        using pixel_t = Pixel;
        using channel_t = typename gil::channel_type<pixel_t>::type;
        gil::pixel_divide_t<pixel_t, pixel_t, pixel_t> f;

        pixel_t p0;
        gil::static_fill(p0, static_cast<channel_t>(0));
        pixel_t p1;
        gil::static_fill(p1, static_cast<channel_t>(1));
        BOOST_TEST_EQ(f(p0, p1), p0);
        BOOST_TEST_EQ(f(p1, p1), p1);

        pixel_t p2;
        gil::static_fill(p2, static_cast<channel_t>(2));
        BOOST_TEST_EQ(f(p2, p1), p2);
    }
    static void run()
    {
        boost::mp11::mp_for_each<fixture::pixel_integer_types>(test_pixel_divide_integer_same_types{});
    }
};

struct test_pixel_halves_integer_same_types
{
    template <typename Pixel>
    void operator()(Pixel const&)
    {
        using pixel_t = Pixel;
        using channel_t = typename gil::channel_type<pixel_t>::type;
        gil::pixel_halves_t<pixel_t> f;

        pixel_t p0;
        gil::static_fill(p0, static_cast<channel_t>(0));
        pixel_t p1;
        gil::static_fill(p1, static_cast<channel_t>(1));

        {
            auto p = p0;
            BOOST_TEST_EQ(f(p), p0);
        }
        {
            auto p = p1;
            BOOST_TEST_EQ(f(p), p0); // truncates toward Zero
        }
        {
            pixel_t p2;
            gil::static_fill(p2, static_cast<channel_t>(2));
            BOOST_TEST_EQ(f(p2), p1);
        }
    }
    static void run()
    {
        boost::mp11::mp_for_each<fixture::pixel_integer_types>(test_pixel_halves_integer_same_types{});
    }
};

struct test_pixel_zeros_integer_same_types
{
    template <typename Pixel>
    void operator()(Pixel const&)
    {
        using pixel_t = Pixel;
        using channel_t = typename gil::channel_type<pixel_t>::type;
        gil::pixel_zeros_t<pixel_t> f;

        pixel_t p0;
        gil::static_fill(p0, static_cast<channel_t>(0));
        {
            auto p = p0;
            BOOST_TEST_EQ(f(p), p0);
        }
        {
            fixture::consecutive_value<channel_t> g(1);
            pixel_t p;
            gil::static_generate(p, [&g]() { return g(); });
            BOOST_TEST_EQ(f(p), p0);
        }
    }
    static void run()
    {
        boost::mp11::mp_for_each<fixture::pixel_integer_types>(test_pixel_zeros_integer_same_types{});
    }
};

struct test_zero_channels_integer_same_types
{
    template <typename Pixel>
    void operator()(Pixel const&)
    {
        using pixel_t = Pixel;
        using channel_t = typename gil::channel_type<pixel_t>::type;

        pixel_t p0;
        gil::static_fill(p0, static_cast<channel_t>(0));
        {
            auto p = p0;
            gil::zero_channels(p);
            BOOST_TEST_EQ(p, p0);
        }
        {
            fixture::consecutive_value<channel_t> g(1);
            pixel_t p;
            gil::static_generate(p, [&g]() { return g(); });
            gil::zero_channels(p);
            BOOST_TEST_EQ(p, p0);
        }
    }
    static void run()
    {
        boost::mp11::mp_for_each<fixture::pixel_integer_types>(test_zero_channels_integer_same_types{});
    }
};

struct test_pixel_assigns_integer_same_types
{
    template <typename Pixel>
    void operator()(Pixel const&)
    {
        using pixel_t = Pixel;
        using channel_t = typename gil::channel_type<pixel_t>::type;
        gil::pixel_assigns_t<pixel_t, pixel_t> f;

        {
            pixel_t p0, r;
            gil::static_fill(p0, static_cast<channel_t>(0));
            f(p0, r);
            BOOST_TEST_EQ(p0, r);
        }
        {
            fixture::consecutive_value<channel_t> g(1);
            pixel_t p, r;
            gil::static_generate(p, [&g]() { return g(); });
            f(p, r);
            BOOST_TEST_EQ(p, r);
        }
    }
    static void run()
    {
        boost::mp11::mp_for_each<fixture::pixel_integer_types>(test_pixel_assigns_integer_same_types{});
    }
};
int main()
{
    test_plus_integer_same_types::run();
    test_minus_integer_same_types::run();
    test_pixel_multiplies_scalar_integer_same_types::run();
    test_pixel_multiply_integer_same_types::run();
    test_pixel_divides_scalar_integer_same_types::run();
    test_pixel_divide_integer_same_types::run();
    test_pixel_halves_integer_same_types::run();
    test_pixel_zeros_integer_same_types::run();
    test_zero_channels_integer_same_types::run();
    test_pixel_assigns_integer_same_types::run();

    return ::boost::report_errors();
}


#ifndef RBGL_MINCUT_H
#define RBGL_MINCUT_H

#include <boost/graph/edge_connectivity.hpp>

namespace boost
{

template < typename VertexListGraph, typename OutputIterator >
typename graph_traits<VertexListGraph>::edges_size_type
min_cut(VertexListGraph & g, OutputIterator s_set, OutputIterator vs_set)
{
    typedef graph_traits<VertexListGraph> Traits;
    typedef typename Traits::vertex_descriptor vertex_descriptor;
    typedef typename Traits::vertex_iterator   vertex_iterator;
    typedef typename Traits::degree_size_type  degree_size_type;
    typedef typename Traits::edge_iterator     edge_iterator;
    typedef typename Traits::edges_size_type   edges_size_type;

    typedef color_traits<default_color_type> Color;

    typedef adjacency_list_traits<vecS, vecS, directedS> Tr;
    typedef typename Tr::edge_descriptor Tr_edge_desc;
    typedef adjacency_list<vecS, vecS, directedS, no_property,
	          property<edge_capacity_t, degree_size_type,
	          property<edge_residual_capacity_t, degree_size_type,
	          property<edge_reverse_t, Tr_edge_desc> > > >
	    FlowGraph;
    typedef typename graph_traits<FlowGraph>::edge_descriptor edge_descriptor;

    vertex_descriptor u, v, p, k;
    vertex_iterator vi, vi_end;
    edge_descriptor e1, e2;
    edge_iterator ei, ei_end;
    edges_size_type delta, alpha_star, alpha_S_k;
    bool inserted;

    std::set < vertex_descriptor > S, neighbor_S;
    std::vector < vertex_descriptor > S_star, nonneighbor_S;
    std::vector < default_color_type > color(num_vertices(g));
    std::vector < edge_descriptor > pred(num_vertices(g));

    FlowGraph flow_g(num_vertices(g));
    typename property_map < FlowGraph, edge_capacity_t >::type
             cap = get(edge_capacity, flow_g);
    typename property_map < FlowGraph, edge_residual_capacity_t >::type
             res_cap = get(edge_residual_capacity, flow_g);
    typename property_map < FlowGraph, edge_reverse_t >::type
             rev_edge = get(edge_reverse, flow_g);

    // This loop is to convert undirected graph to directed graph,
    // each edge in undirected graph has unit capacity
    // each arc in directed graph also has unit capacity
    for (tie(ei, ei_end) = edges(g); ei != ei_end; ++ei) {
        u = source(*ei, g), v = target(*ei, g);
        tie(e1, inserted) = add_edge(u, v, flow_g);
        cap[e1] = 1;
        tie(e2, inserted) = add_edge(v, u, flow_g);
        cap[e2] = 1;
        rev_edge[e1] = e2;
        rev_edge[e2] = e1;
    }

    tie(p, delta) = detail::min_degree_vertex(g);
    S_star.push_back(p);
    alpha_star = delta;
    S.insert(p);
    neighbor_S.insert(p);
    detail::neighbors(g, S.begin(), S.end(),
              std::inserter(neighbor_S, neighbor_S.begin()));
    std::set_difference(vertices(g).first, vertices(g).second,
                        neighbor_S.begin(), neighbor_S.end(),
                        std::back_inserter(nonneighbor_S));

    while (!nonneighbor_S.empty()) {
        k = nonneighbor_S.front();
        alpha_S_k = edmonds_karp_max_flow
                    (flow_g, p, k, cap, res_cap, rev_edge, &color[0], &pred[0]);
        if (alpha_S_k < alpha_star) {
            alpha_star = alpha_S_k;
            S_star.clear();
            for (tie(vi, vi_end) = vertices(flow_g); vi != vi_end; ++vi)
                if (color[*vi] != Color::white())
                    S_star.push_back(*vi);
        }
        S.insert(k);
        neighbor_S.insert(k);
	detail::neighbors(g, k, std::inserter(neighbor_S, neighbor_S.begin()));
        nonneighbor_S.clear();
        std::set_difference(vertices(g).first, vertices(g).second,
                            neighbor_S.begin(), neighbor_S.end(),
                            std::back_inserter(nonneighbor_S));
    }

    /* To extract mincut sets: S and (V - S) */
    std::vector < bool > in_S_star(num_vertices(g), false);
    typename std::vector < vertex_descriptor >::iterator si;
    for (si = S_star.begin(); si != S_star.end(); ++si)
          in_S_star[*si] = true;

    for (tie(vi, vi_end) = vertices(flow_g); vi != vi_end; ++vi)
	  if ( in_S_star[*vi] ) *s_set++ = *vi;
          else *vs_set++ = *vi;

    return alpha_star;
}

}
#endif  // RBGL_MINCUT_H


/*******************************************************************************
Copyright(c) 2015-2021 Parker Hannifin Corp. All rights reserved.

MIT Licensed. See the included LICENSE.txt for a copy of the full MIT License.
*******************************************************************************/
#include "mscl/TimeSpan.h"

#include <boost/test/unit_test.hpp>

using namespace mscl;

BOOST_AUTO_TEST_SUITE(TimeSpan_Test)

BOOST_AUTO_TEST_CASE(TimeSpan_NanoSeconds)
{
    //create a TimeSpan of 1 hour
    TimeSpan ts = TimeSpan::NanoSeconds(3600000000000);

    //check that calling all the time functions gets 1 hour in each unit
    BOOST_CHECK_EQUAL(ts.getNanoseconds(), 3600000000000);
    BOOST_CHECK_EQUAL(ts.getMilliseconds(), 3600000);
    BOOST_CHECK_EQUAL(ts.getSeconds(), 3600);
}

BOOST_AUTO_TEST_CASE(TimeSpan_MicroSeconds)
{
    //create a TimeSpan of 1 hour
    TimeSpan ts = TimeSpan::MicroSeconds(static_cast<uint64>(3600000000));

    //check that calling all the time functions gets 1 hour in each unit
    BOOST_CHECK_EQUAL(ts.getNanoseconds(), 3600000000000);
    BOOST_CHECK_EQUAL(ts.getMilliseconds(), 3600000);
    BOOST_CHECK_EQUAL(ts.getSeconds(), 3600);
}

BOOST_AUTO_TEST_CASE(TimeSpan_MilliSeconds)
{
    //create a TimeSpan of 1 hour
    TimeSpan ts = TimeSpan::MilliSeconds(3600000);

    //check that calling all the time functions gets 1 hour in each unit
    BOOST_CHECK_EQUAL(ts.getNanoseconds(), 3600000000000);
    BOOST_CHECK_EQUAL(ts.getMilliseconds(), 3600000);
    BOOST_CHECK_EQUAL(ts.getSeconds(), 3600);
}

BOOST_AUTO_TEST_CASE(TimeSpan_Seconds)
{
    //create a TimeSpan of 1 hour
    TimeSpan ts = TimeSpan::Seconds(3600);

    //check that calling all the time functions gets 1 hour in each unit
    BOOST_CHECK_EQUAL(ts.getNanoseconds(), 3600000000000);
    BOOST_CHECK_EQUAL(ts.getMilliseconds(), 3600000);
    BOOST_CHECK_EQUAL(ts.getSeconds(), 3600);
}

BOOST_AUTO_TEST_CASE(TimeSpan_Minutes)
{
    //create a TimeSpan of 1 hour
    TimeSpan ts = TimeSpan::Minutes(60);

    //check that calling all the time functions gets 1 hour in each unit
    BOOST_CHECK_EQUAL(ts.getNanoseconds(), 3600000000000);
    BOOST_CHECK_EQUAL(ts.getMilliseconds(), 3600000);
    BOOST_CHECK_EQUAL(ts.getSeconds(), 3600);
}

BOOST_AUTO_TEST_CASE(TimeSpan_Hours)
{
    //create a TimeSpan of 1 hour
    TimeSpan ts = TimeSpan::Hours(1);

    //check that calling all the time functions gets 1 hour in each unit
    BOOST_CHECK_EQUAL(ts.getNanoseconds(), 3600000000000);
    BOOST_CHECK_EQUAL(ts.getMilliseconds(), 3600000);
    BOOST_CHECK_EQUAL(ts.getSeconds(), 3600);
}

BOOST_AUTO_TEST_CASE(TimeSpan_Days)
{
    //create a TimeSpan of 2 days
    TimeSpan ts = TimeSpan::Days(2);

    //check that calling all the time functions gets 2 days in each unit
    BOOST_CHECK_EQUAL(ts.getNanoseconds(), 172800000000000);
    BOOST_CHECK_EQUAL(ts.getMilliseconds(), 172800000);
    BOOST_CHECK_EQUAL(ts.getSeconds(), 172800);
}

BOOST_AUTO_TEST_CASE(TimeSpan_Operators)
{
    //create TimeSpans to compare
    TimeSpan ts1 = TimeSpan::Days(1);
    TimeSpan ts2 = TimeSpan::Seconds(60);
    TimeSpan ts3 = TimeSpan::Minutes(1);

    BOOST_CHECK_EQUAL(ts1 > ts2, true);
    BOOST_CHECK_EQUAL(ts1 >= ts2, true);
    BOOST_CHECK_EQUAL(ts1 == ts2, false);
    BOOST_CHECK_EQUAL(ts1 != ts2, true);
    BOOST_CHECK_EQUAL(ts1 < ts2, false);
    BOOST_CHECK_EQUAL(ts1 <= ts2, false);

    BOOST_CHECK_EQUAL(ts2 > ts3, false);
    BOOST_CHECK_EQUAL(ts2 >= ts3, true);
    BOOST_CHECK_EQUAL(ts2 == ts3, true);
    BOOST_CHECK_EQUAL(ts2 != ts3, false);
    BOOST_CHECK_EQUAL(ts2 < ts3, false);
    BOOST_CHECK_EQUAL(ts2 <= ts3, true);
}

BOOST_AUTO_TEST_CASE(TimeSpan_Multiplication)
{
    TimeSpan span = TimeSpan::NanoSeconds(500);
    TimeSpan rightFactor1 = span * 4;
    TimeSpan leftFactor1 = 4 * span;
    TimeSpan rightFactor2 = span * 1000000000;
    TimeSpan leftFactor2 = 1000000000 * span;

    BOOST_CHECK_EQUAL(rightFactor1.getNanoseconds(), 2000);
    BOOST_CHECK_EQUAL(leftFactor1.getNanoseconds(), 2000);
    BOOST_CHECK_EQUAL(rightFactor2.getSeconds(), 500);
    BOOST_CHECK_EQUAL(leftFactor2.getSeconds(), 500);
}

BOOST_AUTO_TEST_CASE(TimeSpan_Division)
{
    TimeSpan span = TimeSpan::Seconds(1000);
    TimeSpan rightFactor1 = span / 4;
    TimeSpan leftFactor1 = 4 / span;
    TimeSpan rightFactor2 = span / 1000000000;
    TimeSpan leftFactor2 = 1000000000 / span;

    BOOST_CHECK_EQUAL(rightFactor1.getSeconds(), 250);
    BOOST_CHECK_EQUAL(leftFactor1.getSeconds(), 250);
    BOOST_CHECK_EQUAL(rightFactor2.getNanoseconds(), 1000);
    BOOST_CHECK_EQUAL(leftFactor2.getNanoseconds(), 1000);
}

BOOST_AUTO_TEST_SUITE_END()


#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE "test_gradient"

#include <boost/test/unit_test.hpp>
#include "nanocv/nanocv.h"
#include "nanocv/logger.h"
#include "nanocv/optimizer.h"
#include "nanocv/accumulator.h"
#include "nanocv/thread/pool.h"
#include "nanocv/math/close.hpp"
#include "nanocv/math/random.hpp"
#include "nanocv/math/epsilon.hpp"
#include "nanocv-test/test_models.h"

namespace test
{
        using namespace ncv;

        thread_pool_t::mutex_t mutex;

        size_t n_checks = 0;
        size_t n_failures1 = 0;
        size_t n_failures2 = 0;
        size_t n_failures3 = 0;

        void test_grad_params(const string_t& header, const string_t& loss_id, const model_t& model,
                accumulator_t& acc_params)
        {
                random_t<size_t> rand(8, 16);

                const size_t n_tests = 16;
                const size_t n_samples = rand();

                const rloss_t rloss = ncv::get_losses().get(loss_id);
                const loss_t& loss = *rloss;

                const size_t psize = model.psize();
                const size_t isize = model.isize();
                const size_t osize = model.osize();

                vector_t params(psize);
                vectors_t targets(n_samples, vector_t(osize));
                tensors_t inputs(n_samples, tensor_t(model.idims(), model.irows(), model.icols()));

                // optimization problem (wrt parameters & inputs): size
                auto fn_params_size = [&] ()
                {
                        return psize;
                };

                // optimization problem (wrt parameters & inputs): function value
                auto fn_params_fval = [&] (const vector_t& x)
                {
                        acc_params.set_params(x);
                        acc_params.update(inputs, targets, loss);

                        return acc_params.value();
                };

                // optimization problem (wrt parameters & inputs): function value & gradient
                auto fn_params_grad = [&] (const vector_t& x, vector_t& gx)
                {
                        acc_params.set_params(x);
                        acc_params.update(inputs, targets, loss);

                        gx = acc_params.vgrad();
                        return acc_params.value();
                };

                // construct optimization problem: analytic gradient and finite difference approximation
                const opt_problem_t problem(fn_params_size, fn_params_fval, fn_params_grad);

                for (size_t t = 0; t < n_tests; t ++)
                {
                        random_t<scalar_t> prgen(-0.1, +0.1);
                        random_t<scalar_t> irgen(-0.1, +0.1);
                        random_t<size_t> trgen(0, osize - 1);

                        prgen(params.data(), params.data() + psize);
                        for (vector_t& target : targets)
                        {
                                target = ncv::class_target(trgen(), osize);
                        }
                        for (tensor_t& input : inputs)
                        {
                                irgen(input.data(), input.data() + isize);
                        }

                        const scalar_t delta = problem.grad_accuracy(params);

                        // update statistics
                        const thread_pool_t::lock_t lock(test::mutex);

                        n_checks ++;
                        if (!math::close(delta, scalar_t(0), math::epsilon1<scalar_t>()))
                        {
                                n_failures1 ++;
                        }
                        if (!math::close(delta, scalar_t(0), math::epsilon2<scalar_t>()))
                        {
                                n_failures2 ++;
                        }
                        if (!math::close(delta, scalar_t(0), math::epsilon3<scalar_t>()))
                        {
                                n_failures3 ++;

                                BOOST_CHECK_LE(delta, math::epsilon3<scalar_t>());

                                log_error() << header << ": error = " << delta << "/" << math::epsilon3<scalar_t>() << "!";
                        }
                }
        }

        void test_grad_params(const string_t& header, const string_t& loss_id, const model_t& model)
        {
                // check all criteria
                const strings_t criteria = ncv::get_criteria().ids();
                for (const string_t& criterion : criteria)
                {
                        random_t<size_t> rand(2, 16);
                        const size_t n_threads = 1 + (rand() % 2);

                        accumulator_t acc_params(model, n_threads, criterion, criterion_t::type::vgrad, 0.1);
                        test_grad_params(header + "[criterion = " + criterion + "]", loss_id, model, acc_params);
                }
        }

        void test_grad_inputs(const string_t& header, const string_t& loss_id, const model_t& model)
        {
                const rmodel_t rmodel_inputs = model.clone();

                const size_t n_tests = 16;

                const rloss_t rloss = ncv::get_losses().get(loss_id);
                const loss_t& loss = *rloss;

                const size_t psize = model.psize();
                const size_t isize = model.isize();
                const size_t osize = model.osize();

                vector_t params(psize);
                vector_t target(osize);
                tensor_t input(model.idims(), model.irows(), model.icols());

                // optimization problem (wrt parameters & inputs): size
                auto fn_inputs_size = [&] ()
                {
                        return isize;
                };

                // optimization problem (wrt parameters & inputs): function value
                auto fn_inputs_fval = [&] (const vector_t& x)
                {
                        rmodel_inputs->load_params(params);

                        const vector_t output = rmodel_inputs->output(x).vector();

                        return loss.value(target, output);
                };

                // optimization problem (wrt parameters & inputs): function value & gradient
                auto fn_inputs_grad = [&] (const vector_t& x, vector_t& gx)
                {
                        rmodel_inputs->load_params(params);

                        const vector_t output = rmodel_inputs->output(x).vector();

                        gx = rmodel_inputs->ginput(loss.vgrad(target, output)).vector();
                        return loss.value(target, output);
                };

                // construct optimization problem: analytic gradient and finite difference approximation
                const opt_problem_t problem_analytic_inputs(fn_inputs_size, fn_inputs_fval, fn_inputs_grad);
                const opt_problem_t problem_aproxdif_inputs(fn_inputs_size, fn_inputs_fval);

                for (size_t t = 0; t < n_tests; t ++)
                {
                        random_t<scalar_t> prgen(-1.0, +1.0);
                        random_t<scalar_t> irgen(-0.1, +0.1);
                        random_t<size_t> trgen(0, osize - 1);

                        prgen(params.data(), params.data() + psize);
                        target = ncv::class_target(trgen(), osize);
                        irgen(input.data(), input.data() + isize);

                        vector_t analytic_inputs_grad, aproxdif_inputs_grad;

                        problem_analytic_inputs(input.vector(), analytic_inputs_grad);
                        problem_aproxdif_inputs(input.vector(), aproxdif_inputs_grad);

                        const scalar_t delta = (analytic_inputs_grad - aproxdif_inputs_grad).lpNorm<Eigen::Infinity>();

                        // update statistics
                        const thread_pool_t::lock_t lock(test::mutex);

                        n_checks ++;
                        if (!math::close(delta, scalar_t(0), math::epsilon1<scalar_t>()))
                        {
                                n_failures1 ++;
                        }
                        if (!math::close(delta, scalar_t(0), math::epsilon2<scalar_t>()))
                        {
                                n_failures2 ++;
                        }
                        if (!math::close(delta, scalar_t(0), math::epsilon3<scalar_t>()))
                        {
                                n_failures3 ++;

                                BOOST_CHECK_LE(delta, math::epsilon3<scalar_t>());

                                log_error() << header << ": error = " << delta << "/" << math::epsilon3<scalar_t>() << "!";
                        }
                }
        }
}

BOOST_AUTO_TEST_CASE(test_gradient)
{
        using namespace ncv;

        ncv::init();

        size_t cmd_irows;
        size_t cmd_icols;
        size_t cmd_outputs;
        color_mode cmd_color;

        auto configs = test::make_grad_configs(cmd_irows, cmd_icols, cmd_outputs, cmd_color);

        // test each configuration
        thread_pool_t pool;
        for (auto config : configs)
        {
                pool.enqueue([=] ()
                {
                        const string_t desc = config.first;
                        const string_t loss_id = config.second;

                        {
                                const thread_pool_t::lock_t lock(test::mutex);
                                log_info() << desc;
                        }

                        // create model
                        const rmodel_t model = ncv::get_models().get("forward-network", desc);
                        BOOST_CHECK_EQUAL(model.operator bool(), true);
                        {
                                const thread_pool_t::lock_t lock(test::mutex);

                                model->resize(cmd_irows, cmd_icols, cmd_outputs, cmd_color, false);
                                BOOST_CHECK_EQUAL(model->irows(), cmd_irows);
                                BOOST_CHECK_EQUAL(model->icols(), cmd_icols);
                                BOOST_CHECK_EQUAL(model->osize(), cmd_outputs);
                                BOOST_CHECK_EQUAL(static_cast<int>(model->color()), static_cast<int>(cmd_color));
                        }

                        // check with the given loss
                        test::test_grad_params("param [loss = " + loss_id + "]", loss_id, *model);
                        test::test_grad_inputs("input [loss = " + loss_id + "]", loss_id, *model);
                });
        };

        pool.wait();

        // print statistics
        const scalar_t eps1 = math::epsilon1<scalar_t>();
        const scalar_t eps2 = math::epsilon2<scalar_t>();
        const scalar_t eps3 = math::epsilon3<scalar_t>();

        log_info() << "failures: level1 = " << test::n_failures1 << "/" << test::n_checks << ", epsilon = " << eps1;
        log_info() << "failures: level2 = " << test::n_failures2 << "/" << test::n_checks << ", epsilon = " << eps2;
        log_info() << "failures: level3 = " << test::n_failures3 << "/" << test::n_checks << ", epsilon = " << eps3;

        BOOST_CHECK_LE(test::n_failures1 * 100, 100 * test::n_checks);
        BOOST_CHECK_LE(test::n_failures2 * 100, 5 * test::n_checks);
        BOOST_CHECK_LE(test::n_failures3 * 100, 0 * test::n_checks);
}


// Copyright (c) 2009 INRIA Sophia-Antipolis (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org)
//
// $URL$
// $Id$
// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s)     : Stephane Tayeb
//

#include <string>

#include <CGAL/config.h>

#if defined(BOOST_MSVC)
#  pragma warning(disable:4244) // int to float conversion warning
#endif
#include <CGAL/Exact_rational.h>

// leda_rational, or Gmpq, or Quotient<MP_float>
typedef CGAL::Exact_rational         Rational;
#include <CGAL/Cartesian.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Timer.h>
#include <CGAL/Kernel_traits.h>

#include <iomanip>

#include <boost/math/special_functions/next.hpp> // for nextafter

double random_in(const double a,
                 const double b)
{
  double r = rand() / (double)RAND_MAX;
  return a + (b - a) * r;
}

template <class K>
typename K::Point_3 random_point_in(const CGAL::Bbox_3& bbox)
{
  typedef typename K::FT FT;
  FT x = (FT)random_in(bbox.xmin(),bbox.xmax());
  FT y = (FT)random_in(bbox.ymin(),bbox.ymax());
  FT z = (FT)random_in(bbox.zmin(),bbox.zmax());
  return typename K::Point_3(x,y,z);
}

template <class T, class Box3>
bool test_aux(const T& t,
              const std::string& name,
              const Box3& bbox,
              bool expected, bool /*exact_predicates*/ = false)
{
  bool b = CGAL::do_intersect(t,bbox);

  if ( b != expected )
    std::cerr << "ERROR: do_intersect(" << name
              << ") did not answer the expected result !" << std::endl;

  return (b == expected);
}

template <class K,
          class FT>
bool test_case(const FT& px, const FT& py, const FT& pz,
               const FT& qx, const FT& qy, const FT& qz,
               FT bxmin, FT bymin, FT bzmin,
               FT bxmax, FT bymax, FT bzmax,
               const bool expected,
               const bool exact_k = false,
               const bool exactness_issue = false,
               const bool change_signs = true,
               const bool swap_coords  = true,
               const bool opposite_seg = true,
               const bool translate    = true,
               const bool scale        = true)
{
  bool b = true;
  if(change_signs) {
    b &= test_case<K>( px,     py,     pz,
                       qx,     qy,     qz,
                       bxmin,  bymin,  bzmin,
                       bxmax,  bymax,  bzmax, expected, exact_k, exactness_issue, false);
    b &= test_case<K>(-px,     py,     pz,
                      -qx,     qy,     qz,
                      -bxmin,  bymin,  bzmin,
                      -bxmax,  bymax,  bzmax, expected, exact_k, exactness_issue, false);
    b &= test_case<K>( px,    -py,     pz,
                       qx,    -qy,     qz,
                       bxmin, -bymin,  bzmin,
                       bxmax, -bymax,  bzmax, expected, exact_k, exactness_issue, false);
    b &= test_case<K>( px,     py,    -pz,
                       qx,     qy,    -qz,
                       bxmin,  bymin, -bzmin,
                       bxmax,  bymax, -bzmax, expected, exact_k, exactness_issue, false);
    b &= test_case<K>(-px,    -py,     pz,
                      -qx,    -qy,     qz,
                      -bxmin, -bymin,  bzmin,
                      -bxmax, -bymax,  bzmax, expected, exact_k, exactness_issue, false);
    b &= test_case<K>( px,    -py,    -pz,
                       qx,    -qy,    -qz,
                       bxmin, -bymin, -bzmin,
                       bxmax, -bymax, -bzmax, expected, exact_k, exactness_issue, false);
    b &= test_case<K>(-px,     py,    -pz,
                      -qx,     qy,    -qz,
                      -bxmin,  bymin, -bzmin,
                      -bxmax,  bymax, -bzmax, expected, exact_k, exactness_issue, false);
    b &= test_case<K>(-px,    -py,    -pz,
                      -qx,    -qy,    -qz,
                      -bxmin, -bymin, -bzmin,
                      -bxmax, -bymax, -bzmax, expected, exact_k, exactness_issue, false);
  } else if(swap_coords) {
    // xyz
    b &= test_case<K>( px,     py,     pz,
                       qx,     qy,     qz,
                       bxmin,  bymin,  bzmin,
                       bxmax,  bymax,  bzmax, expected, exact_k, exactness_issue,
                       false, false);
    // xzy
    b &= test_case<K>( px,     pz,     py,
                       qx,     qz,     qy,
                       bxmin,  bzmin,  bymin,
                       bxmax,  bzmax,  bymax, expected, exact_k, exactness_issue,
                       false, false);
    // yxz
    b &= test_case<K>( py,     px,     pz,
                       qy,     qx,     qz,
                       bymin,  bxmin,  bzmin,
                       bymax,  bxmax,  bzmax, expected, exact_k, exactness_issue,
                       false, false);
    // zxy
    b &= test_case<K>( pz,     px,     py,
                       qz,     qx,     qy,
                       bzmin,  bxmin,  bymin,
                       bzmax,  bxmax,  bymax, expected, exact_k, exactness_issue,
                       false, false);

    // yzx
    b &= test_case<K>( py,     pz,     px,
                       qy,     qz,     qx,
                       bymin,  bzmin,  bxmin,
                       bymax,  bzmax,  bxmax, expected, exact_k, exactness_issue,
                       false, false);
    // zyx
    b &= test_case<K>( pz,     py,     px,
                       qz,     qy,     qx,
                       bzmin,  bymin,  bxmin,
                       bzmax,  bymax,  bxmax, expected, exact_k, exactness_issue,
                       false, false);
  } else if(opposite_seg) {
    b &= test_case<K>(px,     py,     pz,
                      qx,     qy,     qz,
                      bxmin,  bymin,  bzmin,
                      bxmax,  bymax,  bzmax, expected, exact_k, exactness_issue,
                      false, false, false);
    b &= test_case<K>(qx,     qy,     qz,
                      px,     py,     pz,
                      bxmin,  bymin,  bzmin,
                      bxmax,  bymax,  bzmax, expected, exact_k, exactness_issue,
                      false, false, false);
  } else if(translate) {
    b &= test_case<K>(px,     py,     pz,
                      qx,     qy,     qz,
                      bxmin,  bymin,  bzmin,
                      bxmax,  bymax,  bzmax, expected, exact_k, exactness_issue,
                      false, false, false, false);
    FT dx = 10, dy = 20, dz = 30;
    b &= test_case<K>(dx + px,     dy + py,     dz + pz,
                      dx + qx,     dy + qy,     dz + qz,
                      dx + bxmin,  dy + bymin,  dz + bzmin,
                      dx + bxmax,  dy + bymax,  dz + bzmax, expected, exact_k, exactness_issue,
                      false, false, false, false);
    dx = (1 >> 10), dy = dx, dz = dx;
    b &= test_case<K>(dx + px,     dy + py,     dz + pz,
                      dx + qx,     dy + qy,     dz + qz,
                      dx + bxmin,  dy + bymin,  dz + bzmin,
                      dx + bxmax,  dy + bymax,  dz + bzmax, expected, exact_k, exactness_issue,
                      false, false, false, false);
    dx = -(1 >> 10), dy = dx, dz = dx;
    b &= test_case<K>(dx + px,     dy + py,     dz + pz,
                      dx + qx,     dy + qy,     dz + qz,
                      dx + bxmin,  dy + bymin,  dz + bzmin,
                      dx + bxmax,  dy + bymax,  dz + bzmax, expected, exact_k, exactness_issue,
                      false, false, false, false);
  } else if(scale) {
    b &= test_case<K>(px,     py,     pz,
                      qx,     qy,     qz,
                      bxmin,  bymin,  bzmin,
                      bxmax,  bymax,  bzmax, expected, exact_k, exactness_issue,
                      false, false, false, false, false);
    FT delta = 9;
    b &= test_case<K>(delta * qx,     delta * qy,     delta * qz,
                      delta * px,     delta * py,     delta * pz,
                      delta * bxmin,  delta * bymin,  delta * bzmin,
                      delta * bxmax,  delta * bymax,  delta * bzmax, expected, exact_k, exactness_issue,
                      false, false, false, false, false);
    delta = (1 << 10);
    b &= test_case<K>(delta * qx,     delta * qy,     delta * qz,
                      delta * px,     delta * py,     delta * pz,
                      delta * bxmin,  delta * bymin,  delta * bzmin,
                      delta * bxmax,  delta * bymax,  delta * bzmax, expected, exact_k, exactness_issue,
                      false, false, false, false, false);
    delta = (1 << 10);
    delta = 1/delta;
    b &= test_case<K>(delta * qx,     delta * qy,     delta * qz,
                      delta * px,     delta * py,     delta * pz,
                      delta * bxmin,  delta * bymin,  delta * bzmin,
                      delta * bxmax,  delta * bymax,  delta * bzmax, expected, exact_k, exactness_issue,
                      false, false, false, false, false);
    delta = 7;
    delta /= 2;
    b &= test_case<K>(delta * qx,     delta * qy,     delta * qz,
                      delta * px,     delta * py,     delta * pz,
                      delta * bxmin,  delta * bymin,  delta * bzmin,
                      delta * bxmax,  delta * bymax,  delta * bzmax, expected, exact_k, exactness_issue,
                      false, false, false, false, false);
    delta = 1;
    delta /= 8;
    b &= test_case<K>(delta * qx,     delta * qy,     delta * qz,
                      delta * px,     delta * py,     delta * pz,
                      delta * bxmin,  delta * bymin,  delta * bzmin,
                      delta * bxmax,  delta * bymax,  delta * bzmax, expected, exact_k, exactness_issue,
                      false, false, false, false, false);
  } else {
    using CGAL::do_intersect;
    using CGAL::Bbox_3;
    typedef typename K::Point_3 Point_3;
    typedef typename K::Segment_3 Segment_3;
    if(bxmin > bxmax) std::swap(bxmin, bxmax);
    if(bymin > bymax) std::swap(bymin, bymax);
    if(bzmin > bzmax) std::swap(bzmin, bzmax);
    if(do_intersect(Bbox_3(bxmin, bymin, bzmin,
                           bxmax, bymax, bzmax),
                    Segment_3(Point_3(px, py, pz),
                              Point_3(qx, qy, qz))) != expected)
    {
      if(!exactness_issue || exact_k) {
        b = false;
        CGAL::IO::set_pretty_mode(std::cerr);
        std::cerr.precision(17);
        std::cerr << "Wrong result for do_intersect("
                  << Bbox_3(bxmin, bymin, bzmin,
                            bxmax, bymax, bzmax)
                  << ",\n"
                  << "                              "
                  << Segment_3(Point_3(px, py, pz),
                               Point_3(qx, qy, qz))
                  << ")\n"
                  << "  it should have been " << std::boolalpha << expected
                  << std::endl;
      }
    }
  }
  return b;
}

template <class T>
void speed(const std::string& name)
{
  // types
  typedef typename CGAL::Kernel_traits<T>::Kernel K;
  typedef typename K::Point_3 Point;

  // speed
  double d1 = 1.44258699;
  CGAL::Bbox_3 bbox_small(-d1, -d1, -d1, d1, d1, d1);
  double d2 = 10;
  CGAL::Bbox_3 bbox_big(-d2,-d2,-d2,d2,d2,d2);

  std::vector<T> segment_vector;
  for ( int i = 0 ; i < 1e4 ; ++i )
  {
    Point source = random_point_in<K>(bbox_big);
    Point target = random_point_in<K>(bbox_big);

    segment_vector.push_back(T(source, target));
  }

  int nb_loops = 0;

  CGAL::Timer timer;
  timer.start();
  std::size_t success = 0;
  while ( timer.time() < 5. )
  {
    for ( typename std::vector<T>::iterator it = segment_vector.begin();
         it != segment_vector.end() ; ++it )
    {
      success += do_intersect(bbox_small, *it);
    }
    ++nb_loops;
  }
  timer.stop();

  std::cout << std::fixed << std::setprecision(1);
  std::cout << "\tDo_intersect(bbox, " << name << "): "
            << (nb_loops*segment_vector.size()) / (timer.time()*1000)
            << " computations / ms  "
            << (success / ((0.+ nb_loops*segment_vector.size()) / 100))
            << "% of intersection" << std::endl;
}

template <class K>
void test_speed()
{
  typedef typename K::Segment_3 Segment;
  typedef typename K::Ray_3 Ray;
  typedef typename K::Line_3 Line;

  speed<Segment>("segment");
  speed<Ray>("ray");
  speed<Line>("line");
}

template <class K>
bool intensive_test(bool exact_predicates = true)
{
  bool b = true;

  // Test vertical segments
  for(double x = 4.; x <= 7.; x+=1.)
    for(double ymin = 0.; ymin <= 7.; ymin+=1.)
      for(double ymax = ymin; ymax <= 7.; ymax+=1.)
      {
        const bool expected =
          x >= -5. && x<= 5. &&
          ymin <= 5. && ymax >= -5;
        b &= test_case<K>(x, ymin, 0.,
                          x, ymax, 0.,
                          -5., -5., -5., 5., 5., 5., expected, exact_predicates);
      }
  // Test slanted segments
  for(double x = -7.; x <= 6.; x+=1.)
    for(double y = -1.; y <= 6.; y+=1.)
    {
      const bool expected =
        x >= -6. && x <= 5. &&
        y >= -6. && y <= 5. &&
        y <= x + 10. && y >= x - 10.;
      b &= test_case<K>(x, y, 0.,
                        x + 1., y + 1., 0.,
                        -5., -5., -5., 5., 5., 5., expected, exact_predicates);
    }
  for(double x = -9.; x <= 6.; x+=1.)
    for(double y = -3.; y <= 6.; y+=1.)
    {
      const bool expected =
        x >= -8. && x <= 5. &&
        y >= -7. && y <= 5. &&
        3 * y <= 2 * x + 25. && 3 * y >= 2 * x - 25.;
      b &= test_case<K>(x, y, 0.,
                        x + 3., y + 2., 0.,
                        -5., -5., -5., 5., 5., 5., expected, exact_predicates);
    }
  return b;
}

template <class K>
bool test(bool exact_kernel = false)
{
        // types
  typedef typename K::FT FT;
  typedef typename K::Line_3 Line;
  typedef typename K::Point_3 Point;
  typedef typename K::Vector_3 Vector;
  typedef typename K::Segment_3 Segment;
  typedef typename K::Ray_3 Ray;
  typedef typename K::Line_3 Line;
  typedef typename K::Sphere_3 Sphere;
  typedef typename K::Plane_3 Plane;
  typedef typename K::Triangle_3 Triangle;
  typedef typename K::Iso_cuboid_3 Iso_cuboid_3;

  CGAL::Bbox_3 bbox(1.0,1.0,1.0,10.0,50.0,100.0);

  Point p1(FT(0.), FT(0.), FT(0.));
  Point p2(FT(0.), FT(100.), FT(100.));
  Point p3(FT(100.), FT(0.), FT(100.));
  Point p4(FT(100.), FT(100.), FT(0.));
  Point p5(FT(0.), FT(0.), FT(100.));
  Point p6(FT(100.), FT(100.), FT(100.));
  Point p7(FT(-1.), FT(-1.), FT(-1.));

  Point pA(FT(5.), FT(0.), FT(0.));
  Point pB(FT(5.), FT(10.), FT(100.));
  Point pC(FT(5.), FT(10.), FT(-1.));
  Point pE(FT(5.), FT(10.), FT(0.));

  Segment s12(p1,p2);
  Segment s13(p1,p3);
  Segment s14(p1,p4);
  Segment s15(p1,p5);
  Segment s16(p1,p6);
  Segment s17(p1,p7);
  Segment s71(p7,p1);

  Segment sAB(pA,pB);
  Segment sBA(pB,pA);
  Segment sBC(pB,pC);
  Segment sCE(pC,pE);
  Segment sEC(pE,pC);

  bool b = test_aux(s12,"s12",bbox,false);
  b &= test_aux(s13,"s13",bbox,false);
  b &= test_aux(s14,"s14",bbox,false);
  b &= test_aux(s15,"s15",bbox,false);
  b &= test_aux(s16,"s16",bbox,true);
  b &= test_aux(s17,"s17",bbox,false);
  b &= test_aux(s71,"s71",bbox,false);

  b &= test_aux(sAB,"sAB",bbox,true);
  b &= test_aux(sBA,"sBA",bbox,true);
  b &= test_aux(sBC,"sBC",bbox,true);
  b &= test_aux(sCE,"sCE",bbox,false);
  b &= test_aux(sEC,"sEC",bbox,false);

  CGAL::Bbox_3 bbox_elem_1834(2, 1.81818, 0.166666,
                              2.18182, 2.18182, 0.333333);

  Point source_1834(2, 2, 1);
  Point target_1834(2, 2, 0.75);
  Segment segment_query_1834( source_1834, target_1834 );
  Point source_1834a(2, 2, 0.5);
  Point target_1834a(2, 2, 0.75);
  Segment segment_query_1834a( source_1834a, target_1834a );

  b &= test_aux(segment_query_1834,
                "segment_query_1834", bbox_elem_1834, false);
  b &= test_aux(segment_query_1834.opposite(),
                "segment_query_1834.opposite()", bbox_elem_1834, false);
  b &= test_aux(segment_query_1834a,
                "segment_query_1834a", bbox_elem_1834, false);
  b &= test_aux(segment_query_1834a.opposite(),
                "segment_query_1834a.opposite()", bbox_elem_1834, false);
  b &= test_case<K>(2., 2., 1.,
                    2., 2., 0.75,
                    1.81818, 2., 0.,
                    2., 2.18182, 0.333333, false);
  b &= test_case<K>(2., 2., 0.5,
                    2., 2., 0.75,
                    1.81818, 2., 0.,
                    2., 2.18182, 0.333333, false);

  CGAL::Bbox_3 bbox_elem_1834b(1.81818, 2, 0,
                               2, 2.18182, 0.333333);
  Segment segment_query_1834b(Point(2, 2, 1),
                              Point(2, 2, 0.75));
  b &= test_aux(segment_query_1834b,
                "segment_query_1834b", bbox_elem_1834b, false);
  b &= test_aux(segment_query_1834b.opposite(),
                "segment_query_1834b.opposite()", bbox_elem_1834b, false);
  b &= test_case<K>(2., 2., 1.,
                    2., 2., 0.75,
                    1.81818, 2., 0.,
                    2., 2.18182, 0.333333, false);

  Ray r12(p1,p2);
  Ray r13(p1,p3);
  Ray r14(p1,p4);
  Ray r15(p1,p5);
  Ray r16(p1,p6);
  Ray r17(p1,p7);
  Ray r71(p7,p1);

  Ray rAB(pA,pB);
  Ray rBA(pB,pA);
  Ray rBC(pB,pC);
  Ray rCE(pC,pE);
  Ray rEC(pE,pC);

  b &= test_aux(r12,"r12",bbox,false);
  b &= test_aux(r13,"r13",bbox,false);
  b &= test_aux(r14,"r14",bbox,false);
  b &= test_aux(r15,"r15",bbox,false);
  b &= test_aux(r16,"r16",bbox,true);
  b &= test_aux(r17,"r17",bbox,false);
  b &= test_aux(r71,"r71",bbox,true);

  b &= test_aux(rAB,"rAB",bbox,true);
  b &= test_aux(rBA,"rBA",bbox,true);
  b &= test_aux(rBC,"rBC",bbox,true);
  b &= test_aux(rCE,"rCE",bbox,true);
  b &= test_aux(rEC,"rEC",bbox,false);

  Line l12(p1,p2);
  Line l13(p1,p3);
  Line l14(p1,p4);
  Line l15(p1,p5);
  Line l16(p1,p6);
  Line l17(p1,p7);
  Line l71(p7,p1);

  Line lAB(pA,pB);
  Line lBA(pB,pA);
  Line lBC(pB,pC);
  Line lCE(pC,pE);
  Line lEC(pE,pC);

  b &= test_aux(l12,"l12",bbox,false);
  b &= test_aux(l13,"l13",bbox,false);
  b &= test_aux(l14,"l14",bbox,false);
  b &= test_aux(l15,"l15",bbox,false);
  b &= test_aux(l16,"l16",bbox,true);
  b &= test_aux(l17,"l17",bbox,true);
  b &= test_aux(l71,"l71",bbox,true);

  b &= test_aux(lAB,"lAB",bbox,true);
  b &= test_aux(lBA,"lBA",bbox,true);
  b &= test_aux(lBC,"lBC",bbox,true);
  b &= test_aux(lCE,"lCE",bbox,true);
  b &= test_aux(lEC,"lEC",bbox,true);

  Sphere sphA_1(pA,1);
  Sphere sphB_1(pB,1);
  Sphere sphC_1(pC,1);
  Sphere sphE_1(pE,1);
  Sphere sph1_3(p1,3);

  b &= test_aux(sphA_1,"sphA_1",bbox,false);
  b &= test_aux(sphB_1,"sphB_1",bbox,true);
  b &= test_aux(sphC_1,"sphC_1",bbox,false);
  b &= test_aux(sphE_1,"sphE_1",bbox,true);
  b &= test_aux(sph1_3,"sph1_3",bbox,true);

  b &= test_aux(sphA_1,"sphA_1",Iso_cuboid_3(bbox),false);
  b &= test_aux(sphB_1,"sphB_1",Iso_cuboid_3(bbox),true);
  b &= test_aux(sphC_1,"sphC_1",Iso_cuboid_3(bbox),false);
  b &= test_aux(sphE_1,"sphE_1",Iso_cuboid_3(bbox),true);
  b &= test_aux(sph1_3,"sph1_3",Iso_cuboid_3(bbox),true);

  Plane Pl1(Point(1,1,1),Vector(0,0,1));
  Plane Pl2(Point(1,1,1),Vector(1,1,1));
  Plane Pl3(Point(5,5,5),Vector(1,2,-5));
  Plane Pl4(Point(11,50,100),Vector(1,1,1));

  b &= test_aux(Pl1,"Pl1",bbox,true);
  b &= test_aux(Pl2,"Pl2",bbox,true);
  b &= test_aux(Pl3,"Pl3",bbox,true);
  b &= test_aux(Pl4,"Pl4",bbox,false);

  b &= test_aux(Pl1,"Pl1",Iso_cuboid_3(bbox),true);
  b &= test_aux(Pl2,"Pl2",Iso_cuboid_3(bbox),true);
  b &= test_aux(Pl3,"Pl3",Iso_cuboid_3(bbox),true);
  b &= test_aux(Pl4,"Pl4",Iso_cuboid_3(bbox),false);

  Triangle t123(p1,p2,p3);
  Triangle t124(p1,p2,p4);
  Triangle t126(p1,p2,p6);
  Triangle t136(p1,p3,p6);
  Triangle tABC(pA,pB,pC);
  Triangle t1(Point(1,1,1),Point(0,0,0),Point(0,0,1));
  Triangle t2(Point(4,1,7),Point(8,1,99),Point(7,1,11));
  Triangle t3(Point(0,1,1),Point(0,0,0),Point(0,0,1));

  b &= test_aux(t123,"t123",bbox,true);
  b &= test_aux(t124,"t124",bbox,true);
  b &= test_aux(t126,"t126",bbox,true);
  b &= test_aux(t136,"t136",bbox,true);
  b &= test_aux(tABC,"tABC",bbox,true);
  b &= test_aux(t1,"t1",bbox,true);
  b &= test_aux(t2,"t2",bbox,true);
  b &= test_aux(t3,"t3",bbox,false);

  b &= test_aux(t123,"t123",Iso_cuboid_3(bbox),true);
  b &= test_aux(t124,"t124",Iso_cuboid_3(bbox),true);
  b &= test_aux(t126,"t126",Iso_cuboid_3(bbox),true);
  b &= test_aux(t136,"t136",Iso_cuboid_3(bbox),true);
  b &= test_aux(tABC,"tABC",Iso_cuboid_3(bbox),true);
  b &= test_aux(t1,"t1",Iso_cuboid_3(bbox),true);
  b &= test_aux(t2,"t2",Iso_cuboid_3(bbox),true);
  b &= test_aux(t3,"t3",Iso_cuboid_3(bbox),false);

  // Test more bboxes
  CGAL::Bbox_3 bbox2(-0.248143,-0.49325,0.0747943,-0.107021,-0.406955,0.151042);
  Segment seg2(Point(FT(0.10114),FT(0.23963),FT(0.0854394)),
               Point(FT(1.35831),FT(1.52921),FT(0.524127)) );

  CGAL::Bbox_3 bbox3(-0.489613, -0.333874, -0.154123, -0.100856, 0.0477374, 0.155364);
  Segment seg3(Point(FT(-0.123786),FT(0.0689497),FT(0.274589)),
               Point(FT(-0.203405),FT(-0.119905),FT(0.0661125)) );

  CGAL::Bbox_3 bbox4(-0.161409, -0.462146, -0.104619, -0.111159, -0.408844, -0.0594407);
  Segment seg4(Point(FT(0.0586705),FT(-0.276077),FT(0.0726862)),
               Point(FT(-1.12212),FT(-1.19194),FT(-1.25109)) );

  Ray ray2(seg2.source(), seg2.target());
  Ray ray3(seg3.source(), seg3.target());
  Ray ray4(seg4.source(), seg4.target());

  Line line2(seg2);
  Line line3(seg3);
  Line line4(seg4);

  b &= test_aux(seg2, "seg2", bbox2, false);
  b &= test_aux(seg3, "seg3", bbox3, true);
  b &= test_aux(seg4, "seg4", bbox4, false);

  b &= test_aux(ray2, "ray2", bbox2, false);
  b &= test_aux(ray3, "ray3", bbox3, true);
  b &= test_aux(ray4, "ray4", bbox4, false);

  b &= test_aux(line2, "line2", bbox2, false);
  b &= test_aux(line3, "line3", bbox3, true);
  b &= test_aux(line4, "line4", bbox4, false);

  Iso_cuboid_3 ic(bbox);
  b &= test_aux(ic, "ic", bbox, true);

  // Use do_intersect(bbox,bbox)
  CGAL::do_intersect(bbox2,bbox4);

  b &= test_case<K>(1., 1., 0.,
                    1., 1., 1.,
                    0., 0., 0.,
                    1., 1., 1., true);
  b &= test_case<K>(0.5, 0.5, -0.5,
                    0.5, 0.5, 0.5,
                    -0.5, -0.5, -0.5,
                    0.5, 0.5, 0.5, true);
  float f = 0.5f;
  double d = boost::math::nextafter(f, f+1);
  double d2 = boost::math::nextafter(f, f-1);
  b &= test_case<K>(d, 0.5, -0.5,
                    d, 0.5, 0.5,
                    -0.5, -0.5, -0.5,
                    0.5, 0.5, 0.5, false, exact_kernel, true,
                    false, false, false, false, false);
  b &= test_case<K>(d2, 0.5, -0.5,
                    d, 0.5, 0.5,
                    -0.5, -0.5, -0.5,
                    0.5, 0.5, 0.5, true, exact_kernel, true,
                    false, false, false, false, false);

  b &= test_case<K>(1., 1., 0.,
                    2., 2., 2.,
                    0., 0., 0.,
                    1., 1., 1., true);
  b &= test_case<K>(1., 1., 1.,
                    1., 1., 1.,
                    0., 0., 0.,
                    1., 1., 1., true);
  b &= test_case<K>(0.9, 0.9, 0.9,
                    0.9, 0.9, 0.9,
                    0., 0., 0.,
                    1., 1., 1., true);
  b &= test_case<K>(0., 0., 0.,
                    0., 0., 0.,
                    0., 0., 0.,
                    1., 1., 1., true);
  b &= test_case<K>(0.1, 0., 0.1,
                    0.1, 0., 0.1,
                    0., 0., 0.,
                    1., 1., 1., true);
  b &= test_case<K>(0.1, -0.1, 0.1,
                    0.1, -0.1, 0.1,
                    0., 0., 0.,
                    1., 1., 1., false);
  b &= test_case<K>(0.1, 0.1, 0.1,
                    0.1, 0.1, 0.1,
                    0., 0., 0.,
                    1., 1., 1., true);
  b &= test_case<K>(1., 1., 1.1,
                    1., 1., 1.1,
                    0., 0., 0.,
                    1., 1., 1., false);
  return b;
}

template <typename K>
bool test_kernel(bool exact_predicates = true, K /*k*/ = K())
{
  bool b = test<K>(exact_predicates) &&
    intensive_test<K>(exact_predicates);
  test_speed<K>();
  return b;
}

int main()
{
  srand(0);
  std::cout << std::setprecision(5);

  bool b;
  std::cout << "Testing with Simple_cartesian<float>..." << std::endl ;
  b = test_kernel<CGAL::Simple_cartesian<float> >(false);

  std::cout << std::endl << "Testing with Simple_cartesian<double>..." << std::endl ;
  b &= test_kernel<CGAL::Simple_cartesian<double> >(true);

  std::cout << std::endl << "Testing with Simple_cartesian<Rational>..." << std::endl ;
  b &= test_kernel<CGAL::Simple_cartesian<Rational> >(true);

  std::cout << std::endl << "Testing with Cartesian<float>..." << std::endl ;
  b &= test_kernel<CGAL::Cartesian<float> >(false);

  std::cout << std::endl << "Testing with Cartesian<double>..." << std::endl ;
  b &= test_kernel<CGAL::Cartesian<double> >(true);

  std::cout << std::endl << "Testing with Filtered_kernel<Simple_cartesian<double> > without static filters..." << std::endl ;
  typedef CGAL::Filtered_kernel<CGAL::Simple_cartesian<double>, false> Fk_no_static;
  b &= test_kernel<Fk_no_static>();

  std::cout << std::endl << "Testing with Exact_predicates_inexact_constructions_kernel..." << std::endl ;
  b &= test_kernel<CGAL::Exact_predicates_inexact_constructions_kernel>();

  std::cout << std::endl << "Testing with Exact_predicates_exact_constructions_kernel..." << std::endl ;
  b &= test_kernel<CGAL::Exact_predicates_exact_constructions_kernel>();

  if ( b )
    return EXIT_SUCCESS;
  else
    return EXIT_FAILURE;
}