#include "src/utils/distributions.h"

#include <gtest/gtest.h>

#include <Eigen/Dense>
#include <stan/math/prim.hpp>
#include <vector>

#include "src/utils/rng.h"

TEST(mix_dist, 1) {
  auto& rng = bayesmix::Rng::Instance().get();

  int nclus = 5;
  Eigen::VectorXd weights1 =
      stan::math::dirichlet_rng(Eigen::VectorXd::Ones(nclus), rng);
  Eigen::VectorXd means1(nclus);
  Eigen::VectorXd sds1(nclus);

  for (int i = 0; i < nclus; i++) {
    means1(i) = stan::math::normal_rng(0, 2, rng);
    sds1(i) = stan::math::uniform_rng(0.1, 2.0, rng);
  }

  int nclus2 = 10;
  Eigen::VectorXd weights2 =
      stan::math::dirichlet_rng(Eigen::VectorXd::Ones(nclus2), rng);
  Eigen::VectorXd means2(nclus2);
  Eigen::VectorXd sds2(nclus2);

  for (int i = 0; i < nclus2; i++) {
    means2(i) = stan::math::normal_rng(0, 2, rng);
    sds2(i) = stan::math::uniform_rng(0.1, 2.0, rng);
  }

  double dist = bayesmix::gaussian_mixture_dist(means1, sds1, weights1, means2,
                                                sds2, weights2);

  ASSERT_GE(dist, 0.0);
}

TEST(mix_dist, 2) {
  int nclus = 5;
  auto& rng = bayesmix::Rng::Instance().get();

  Eigen::VectorXd weights1 =
      stan::math::dirichlet_rng(Eigen::VectorXd::Ones(nclus), rng);
  Eigen::VectorXd means1(nclus);
  Eigen::VectorXd sds1(nclus);

  for (int i = 0; i < nclus; i++) {
    means1(i) = stan::math::normal_rng(0, 2, rng);
    sds1(i) = stan::math::uniform_rng(0.1, 2.0, rng);
  }

  double dist_to_self = bayesmix::gaussian_mixture_dist(
      means1, sds1, weights1, means1, sds1, weights1);

  ASSERT_DOUBLE_EQ(dist_to_self, 0.0);
}

TEST(student_t, squareform) {
  Eigen::MatrixXd A = Eigen::MatrixXd::Random(5, 5);
  Eigen::MatrixXd sigma =
      (A * A.transpose()) + 1.0 * Eigen::MatrixXd::Identity(5, 5);
  Eigen::VectorXd mean = Eigen::VectorXd::Zero(5);
  double df = 15;

  Eigen::MatrixXd sigma_inv = stan::math::inverse_spd(sigma);
  Eigen::MatrixXd sigma_inv_chol =
      Eigen::LLT<Eigen::MatrixXd>(sigma_inv).matrixU();

  Eigen::VectorXd x = Eigen::VectorXd::Ones(5);

  double sq1 = (x - mean).transpose() * sigma_inv * (x - mean);
  double sq2 = (sigma_inv_chol * (x - mean)).squaredNorm();

  ASSERT_DOUBLE_EQ(sq1, sq2);
}

TEST(student_t, optimized) {
  Eigen::MatrixXd A = Eigen::MatrixXd::Random(5, 5);
  Eigen::MatrixXd sigma =
      (A * A.transpose()) + 1.0 * Eigen::MatrixXd::Identity(5, 5);
  Eigen::VectorXd mean = Eigen::VectorXd::Zero(5);
  double df = 15;

  Eigen::VectorXd x = Eigen::VectorXd::Ones(5);

  double lpdf_stan = stan::math::multi_student_t_lpdf(x, df, mean, sigma);
  // std::cout << "lpdf_stan: " << lpdf_stan << std::endl;

  Eigen::MatrixXd sigma_inv = stan::math::inverse_spd(sigma);
  Eigen::MatrixXd sigma_inv_chol =
      Eigen::LLT<Eigen::MatrixXd>(sigma_inv).matrixU();
  Eigen::VectorXd diag = sigma_inv_chol.diagonal();
  double logdet = 2 * log(diag.array()).sum();

  double our_lpdf = bayesmix::multi_student_t_invscale_lpdf(
      x, df, mean, sigma_inv_chol, logdet);

  // std::cout << "our_lpdf: " << our_lpdf << std::endl;

  ASSERT_LE(std::abs(our_lpdf - lpdf_stan), 0.001);
}

TEST(student_t, marginal) {
  double var_scaling = 0.1;
  double deg_free = 10;
  int dim = 3;

  Eigen::MatrixXd A = Eigen::MatrixXd::Random(dim, dim);
  Eigen::MatrixXd scale_inv =
      (A * A.transpose()) + 1.0 * Eigen::MatrixXd::Identity(dim, dim);

  Eigen::MatrixXd sigma_n =
      scale_inv * (var_scaling + 1) / (var_scaling * (deg_free - dim + 1));
  double nu_n = deg_free - dim + 1;

  Eigen::VectorXd datum = Eigen::VectorXd::Ones(dim);
  Eigen::VectorXd mean = Eigen::VectorXd::Zero(dim);

  Eigen::MatrixXd scale = stan::math::inverse_spd(scale_inv);
  Eigen::MatrixXd scale_chol = Eigen::LLT<Eigen::MatrixXd>(scale).matrixU();

  double coeff = (var_scaling + 1) / (var_scaling * (deg_free - dim + 1));
  Eigen::MatrixXd scale_chol_n = scale_chol / std::sqrt(coeff);
  Eigen::VectorXd diag = scale_chol_n.diagonal();
  double logdet = 2 * log(diag.array()).sum();

  double old_qf = (datum - mean).transpose() *
                  stan::math::inverse_spd(sigma_n) * (datum - mean);

  double new_qf = (scale_chol_n * (datum - mean)).squaredNorm();

  ASSERT_DOUBLE_EQ(old_qf, new_qf);

  double old_lpdf =
      stan::math::multi_student_t_lpdf(datum, nu_n, mean, sigma_n);

  double new_lpdf = bayesmix::multi_student_t_invscale_lpdf(
      datum, nu_n, mean, scale_chol_n, logdet);

  ASSERT_LE(std::abs(old_lpdf - new_lpdf), 0.001);
}

TEST(mult_normal, lpdf_grid) {
  int dim = 3;

  Eigen::MatrixXd data = Eigen::MatrixXd::Random(20, dim);
  Eigen::VectorXd mean = Eigen::ArrayXd::LinSpaced(dim, 0.0, 10.0);
  Eigen::MatrixXd tmp = Eigen::MatrixXd::Random(dim + 1, dim);
  Eigen::MatrixXd prec =
      tmp.transpose() * tmp + Eigen::MatrixXd::Identity(dim, dim);
  Eigen::MatrixXd prec_chol = Eigen::LLT<Eigen::MatrixXd>(prec).matrixU();
  Eigen::VectorXd diag = prec_chol.diagonal();
  double prec_logdet = 2 * log(diag.array()).sum();

  Eigen::VectorXd lpdfs = bayesmix::multi_normal_prec_lpdf_grid(
      data, mean, prec_chol, prec_logdet);

  for (int i = 0; i < 20; i++) {
    double curr = bayesmix::multi_normal_prec_lpdf(data.row(i), mean,
                                                   prec_chol, prec_logdet);
    ASSERT_DOUBLE_EQ(curr, lpdfs(i));
  }
}

TEST(mult_t, lpdf_grid) {
  int dim = 3;

  Eigen::MatrixXd data = Eigen::MatrixXd::Random(20, dim);
  Eigen::VectorXd mean = Eigen::ArrayXd::LinSpaced(dim, 0.0, 10.0);
  Eigen::MatrixXd tmp = Eigen::MatrixXd::Random(dim + 1, dim);
  Eigen::MatrixXd invscale =
      tmp.transpose() * tmp + Eigen::MatrixXd::Identity(dim, dim);
  Eigen::MatrixXd invscale_chol =
      Eigen::LLT<Eigen::MatrixXd>(invscale).matrixU();
  Eigen::VectorXd diag = invscale_chol.diagonal();
  double invscale_logdet = 2 * log(diag.array()).sum();
  double df = 10;

  Eigen::VectorXd lpdfs = bayesmix::multi_student_t_invscale_lpdf_grid(
      data, df, mean, invscale_chol, invscale_logdet);

  for (int i = 0; i < 20; i++) {
    double curr = bayesmix::multi_student_t_invscale_lpdf(
        data.row(i), df, mean, invscale_chol, invscale_logdet);
    ASSERT_DOUBLE_EQ(curr, lpdfs(i));
  }
}

TEST(lpdf_woodbury, 1) {
  int dim = 1000;
  int q = 10;
  auto& rng = bayesmix::Rng::Instance().get();
  Eigen::VectorXd mean(dim);
  Eigen::VectorXd datum(dim);
  Eigen::VectorXd sigma_diag(dim);
  Eigen::MatrixXd lambda(dim, q);

  for (size_t j = 0; j < dim; j++) {
    mean[j] = stan::math::normal_rng(0, 1, rng);

    sigma_diag[j] = stan::math::inv_gamma_rng(2.5, 1, rng);

    for (size_t i = 0; i < q; i++) {
      lambda(j, i) = stan::math::normal_rng(0, 1, rng);
    }
  }

  Eigen::MatrixXd cov =
      lambda * lambda.transpose() + Eigen::MatrixXd(sigma_diag.asDiagonal());

  datum = stan::math::multi_normal_rng(mean, cov, rng);

  double stan_lpdf = stan::math::multi_normal_lpdf(datum, mean, cov);
  double our_lpdf =
      bayesmix::multi_normal_lpdf_woodbury(datum, mean, sigma_diag, lambda);

  ASSERT_LE(std::abs(stan_lpdf - our_lpdf), 1e-10);
}


//
// Created by a.kiryanenko on 3/26/20.
//

#include "../SpuUltraGraphAdapter.h"
#include "../SpuUltraGraphProperty.h"
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/depth_first_search.hpp>
#include "GraphPerformanceTest.h"

using namespace SPU_GRAPH;
using namespace boost;

typedef boost::adjacency_list <
        boost::vecS, // как хранить вершины - в векторе
        boost::vecS, // как хранить ребра из каждой вершины - в векторе
        boost::directedS,
        no_property,
        property < edge_weight_t, int >
> AdjacencyListGraph;

template <class G>
void depth_first_test(G &g) {
    // Выполняю алгоритм поиска в глубину
    depth_first_search(g, visitor(default_dfs_visitor()));
}

int main()
{
    cout << "SpuUltraGraph performance test" << endl;
    cout << "==========================================" << endl;
    GraphPerformanceTest<SpuUltraGraph> spu_graph_test(depth_first_test, "depth_first_test_SpuUltraGraph.csv");
    spu_graph_test.is_mutable_test = false;
    spu_graph_test.start();

    cout << "adjacency_list performance test" << endl;
    cout << "==========================================" << endl;
    GraphPerformanceTest<AdjacencyListGraph> adjacency_list_test(depth_first_test, "depth_first_test_adjacency_list.csv");
    adjacency_list_test.is_mutable_test = false;
    adjacency_list_test.start();

    cout << "adjacency_matrix performance test" << endl;
    cout << "==========================================" << endl;
    GraphPerformanceTest<AdjacencyMatrixGraph> adjacency_matrix_test(depth_first_test, "depth_first_test_adjacency_matrix.csv");
    adjacency_matrix_test.is_mutable_test = false;
    adjacency_matrix_test.end_vertices_cnt = 20000;
    adjacency_matrix_test.start();
    return 0;
}


#ifndef __EXPGRAPH_HPP
#include <Eigen/Dense>
#include<vector>
using namespace std;
class ExpGraph{
	public:
		//None of data, oldprms, oldprmlbls, updateData(), and updateOldParameters are mandatory
		//I implemented them for my own application
		//data holds the current timestep's vectors, oldprms/oldprmlbls keeps track of old parameters and their labels
		//updateData() does what it advertises
		//updateOldParameters makes sure that the KernDynMeans and VectorGraph objects have the same ordering of oldprmlbls,
		//and also updates oldprms (weighted by gammas) to reflect the clustering that was just completed
		//
		//basically, the pattern for clustering batch-sequential data is:
		//1) VectorGraph::updateData( [the data for this timestep] );
		//2) KernDynMeans::cluster( [the vector graph] );
		//3) VectorGraph::updateOldPrms( [output of KernDynMeans::cluster] );
		//Go back to 1 for the next time step
		std::vector<V2d> data, oldprms;
		std::vector<int> oldprmlbls;
		ExpGraph(){
			data.clear();
			oldprms.clear();
			oldprmlbls.clear();
		}
		void updateData(std::vector<V2d> data){
			this->data = data;
		}
		void updateOldParameters(std::vector<V2d> data, std::vector<int> lbls, std::vector<double> gammas, std::vector<int> prmlbls){
			std::vector<V2d> updatedoldprms;
			for (int i = 0; i < prmlbls.size(); i++){
				//if there is no data assigned to this cluster, must be old/uninstantiated
				if (find(lbls.begin(), lbls.end(), prmlbls[i]) == lbls.end()){
					int oldidx = distance(oldprmlbls.begin(), find(oldprmlbls.begin(), oldprmlbls.end(), prmlbls[i]));
					updatedoldprms.push_back(oldprms[oldidx]);
				//if the label is not in oldprmlbls, must be a new cluster
				//furthermore, must have at least one label in lbls = prmlbls[i]
				} else if (find(oldprmlbls.begin(), oldprmlbls.end(), prmlbls[i]) == oldprmlbls.end()) {
					V2d tmpprm = V2d::Zero();
					int tmpcnt = 0;
					for (int j =0 ; j < lbls.size(); j++){
						if (lbls[j] == prmlbls[i]){
							tmpprm += data[j];
							tmpcnt++;
						}
					}
					tmpprm /= tmpcnt;
					updatedoldprms.push_back(tmpprm);
				//old instantiated cluster
				} else {
					int oldidx = distance(oldprmlbls.begin(), find(oldprmlbls.begin(), oldprmlbls.end(), prmlbls[i]));
					V2d tmpprm = gammas[i]*oldprms[oldidx];
					int tmpcnt = 0;
					for (int j =0 ; j < lbls.size(); j++){
						if (lbls[j] == prmlbls[i]){
							tmpprm += data[j];
							tmpcnt++;
						}
					}
					tmpprm /= (gammas[i]+tmpcnt);
					updatedoldprms.push_back(tmpprm);
				}
			}
			this->oldprms = updatedoldprms;
			this->oldprmlbls = prmlbls;
		}

		//---ALL FUNCTIONS BELOW ARE MANDATORY---
		//Any affinity class must implement all of the below functions, as KernDynMeans calls them explicitly

		double diagSelfSimDD(const int i) const{
			return data[i].transpose()*data[i];
		}
		double offDiagSelfSimDD(const int i) const{
			return 0;
		}
		double selfSimPP(const int i) const{
			return oldprms[i].transpose()*oldprms[i];
		}
		double simDD(const int i, const int j) const{
			return data[i].transpose()*data[j];
		}
		double simDP(const int i, const int j) const{
			return data[i].transpose()*oldprms[j];
		}
		int getNodeCt(const int i) const{
			return 1;
		}
		int getNNodes() const {
			return data.size();
		}
		int getNOldPrms() const {
			return oldprms.size();
		}
};

#define __EXPGRAPH_HPP
#endif /* __EXPGRAPH_HPP */


//
// Created by vlad on 02.12.16.
//

#include <route_solver/abc/abc.hpp>
#include <route_solver/utils/common.hpp>

#include <gtest/gtest.h>

#ifdef CUDA_ENABLED
#include <Eigen/LU>
#include <Eigen/Core>
#include <Eigen/Dense>
#include "cuda_func_opt.hpp"
#endif

namespace rs {
class FuncSolution
	: public Solution<FuncSolution> {
public:

	FuncSolution(double x1, double x2)
		: x1(x1), x2(x2), value(x1 * x1 + x2 * x2)
	{}

	FuncSolution(const FuncSolution& other) = default;

	double fitnessImpl() const
	{
		if (value >= 0)
		{
			return 1 / (1 + value);
		}
		else
		{
			return 1 + fabs(value);
		}
	}

	double getX1() const
	{
		return x1;
	}
	double getX2() const
	{
		return x2;
	}
	double getValue() const
	{
		return value;
	}

private:

	double x1;
	double x2;
	double value;
};

class FuncSolutionProducer
	: public SolutionProducer<FuncSolutionProducer, FuncSolution> {
public:

	void newBetterSolutionFoundImpl(const FuncSolution&) const
	{
	}
	FuncSolution generateRandomImpl() const
	{
		return FuncSolution(newValue(), newValue());
	}

	FuncSolution produceNeighborhoodImpl(const FuncSolution& solution) const
	{
		return FuncSolution(solution.getX1() + utils::randomNumber(-1.0, 1.0),
							solution.getX2() + utils::randomNumber(-1.0, 1.0));
	}

private:
	static double newValue()
	{
		return utils::randomNumber(-5.0, 5.0);
	}
};

}

TEST(ABCTests, simple_func_optimization)
{
	std::srand(unsigned(std::time(0)));
	auto s = rs::beeColonySolve<rs::FuncSolutionProducer, rs::FuncSolution>([]()
	{
		return rs::FuncSolutionProducer();
	}
	, rs::BeeColonyParams(6, 1000, 20, 5));
	std::cout << " x1 " << s.getX1() << " x2 " << s.getX2() << " value " << s.getValue() << std::endl;
}

#ifdef CUDA_ENABLED

static int a = ei_test_init_cuda();

double dot_cpu(const std::vector<Eigen::Vector3d>& v1, const std::vector<Eigen::Vector3d>& v2)
{
	double x = 0;

	for (int i = 0; i < v1.size(); ++i)
	{
		x += v1[i].dot(v2[i]);
	}

	return x;
}

static const int N = 10000000;

TEST(MatrixOps, case_cuda)
{
	std::vector<Eigen::Vector3d> v1(N, Eigen::Vector3d{ 1.0, 1.0, 1.0 });
	std::vector<Eigen::Vector3d> v2(N, Eigen::Vector3d{ -1.0, 1.0, 1.0 });
	double x = dot_cuda(v1, v2);
}

TEST(MatrixOps, case_cpu)
{
	std::vector<Eigen::Vector3d> v1(N, Eigen::Vector3d{ 1.0, 1.0, 1.0 });
	std::vector<Eigen::Vector3d> v2(N, Eigen::Vector3d{ -1.0, 1.0, 1.0 });
	double x = dot_cpu(v1, v2);
}

#endif


/* Copyright 2022 Zuru Tech HK Limited.
 *
 * Licensed under the Apache License, Version 2.0(the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <chrono>
#include <stdexcept>

#include <Eigen/CholmodSupport>
#include <Eigen/Dense>
#include <Eigen/KLUSupport>
#include <Eigen/Sparse>
#include <Eigen/UmfPackSupport>

#include <solvers/SparseSystem.hpp>
#include <solvers/SuiteSparseSolver.hpp>

namespace solvers {

Eigen::VectorXd SuiteSparseSolver::_cholmod_solve(const SparseSystem& system,
                                                  double& duration) const
{
    auto [A, b] = system.toEigenUpperCSR();
    Eigen::VectorXd result;
    std::chrono::time_point start = std::chrono::high_resolution_clock::now();
    switch (_method) {
        case SuiteSparseMethod::SimplicialLLT: {
            Eigen::CholmodSimplicialLLT<
                Eigen::SparseMatrix<double, Eigen::RowMajor>, Eigen::Upper>
                simplicial_llt;
            simplicial_llt.compute(A);
            result = simplicial_llt.solve(b);
            break;
        }
        case SuiteSparseMethod::SimplicialLDLT: {
            Eigen::CholmodSimplicialLDLT<
                Eigen::SparseMatrix<double, Eigen::RowMajor>, Eigen::Upper>
                simplicial_ldlt;
            simplicial_ldlt.compute(A);
            result = simplicial_ldlt.solve(b);
            break;
        }
        case SuiteSparseMethod::SupernodalLLT: {
            Eigen::CholmodSupernodalLLT<
                Eigen::SparseMatrix<double, Eigen::RowMajor>, Eigen::Upper>
                supernodal_llt;
            supernodal_llt.compute(A);
            result = supernodal_llt.solve(b);
            break;
        }
        default:
            throw std::logic_error("Invalid solving method");
            break;
    }
    std::chrono::time_point end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> time_difference = end - start;
    duration = time_difference.count();
    return result;
}

Eigen::VectorXd SuiteSparseSolver::_cholmod_gpu_solve(
    const SparseSystem& system,
    double& duration) const
{
    auto [A, b] = system.toEigenCSR();
    Eigen::SparseMatrix<double, Eigen::RowMajor, SuiteSparse_long> A_long(A);
    cholmod_sparse cholmod_A = viewAsCholmod(A_long);
    cholmod_A.stype = 1;
    cholmod_dense cholmod_b = viewAsCholmod(b);
    cholmod_dense* cholmod_x;
    auto* c = new cholmod_common();
    cholmod_factor* L;
    cholmod_l_start(c);
    c->useGPU = 1;
    switch (_method) {
        case SuiteSparseMethod::SimplicialLLT: {
            c->final_asis = 0;
            c->supernodal = CHOLMOD_SIMPLICIAL;
            c->final_ll = 1;
            break;
        }
        case SuiteSparseMethod::SimplicialLDLT: {
            c->final_asis = 1;
            c->supernodal = CHOLMOD_SIMPLICIAL;
            break;
        }
        case SuiteSparseMethod::SupernodalLLT: {
            c->final_asis = 1;
            c->supernodal = CHOLMOD_SUPERNODAL;
            break;
        }
        default:
            throw std::logic_error("Invalid solving method");
            break;
    }
    std::chrono::time_point start = std::chrono::high_resolution_clock::now();
    L = cholmod_l_analyze(&cholmod_A, c);
    cholmod_l_factorize(&cholmod_A, L, c);
    cholmod_x = cholmod_l_solve(CHOLMOD_A, L, &cholmod_b, c);
    std::chrono::time_point end = std::chrono::high_resolution_clock::now();
    Eigen::VectorXd result = Eigen::Map<Eigen::VectorXd>(
        static_cast<double*>(cholmod_x->x), static_cast<int64_t>(system.dim()));
    cholmod_l_free_factor(&L, c);
    cholmod_l_free_dense(&cholmod_x, c);
    cholmod_l_finish(c);
    std::chrono::duration<double> time_difference = end - start;
    duration = time_difference.count();
    return result;
}

Eigen::VectorXd SuiteSparseSolver::_lu_solve(const SparseSystem& system,
                                             double& duration) const
{
    auto [A, b] = system.toEigenCSC();
    Eigen::VectorXd result;

    std::chrono::time_point start = std::chrono::high_resolution_clock::now();
    if (_method == SuiteSparseMethod::LU) {
        Eigen::UmfPackLU<Eigen::SparseMatrix<double, Eigen::ColMajor>>
            umfpacklu;
        umfpacklu.compute(A);
        result = umfpacklu.solve(b);
    }
    else {
        Eigen::KLU<Eigen::SparseMatrix<double, Eigen::ColMajor>> klu;
        klu.compute(A);
        result = klu.solve(b);
    }
    std::chrono::time_point end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> time_difference = end - start;
    duration = time_difference.count();
    return result;
}

Eigen::VectorXd SuiteSparseSolver::solve(const SparseSystem& system,
                                         double& duration) const
{
    Eigen::VectorXd result;

    switch (_method) {
        case SuiteSparseMethod::SimplicialLLT:
        case SuiteSparseMethod::SimplicialLDLT:
        case SuiteSparseMethod::SupernodalLLT:
            if (_gpu) {
                result = _cholmod_gpu_solve(system, duration);
            }
            else {
                result = _cholmod_solve(system, duration);
            }
            break;
        case SuiteSparseMethod::LU:
        case SuiteSparseMethod::KLU:
            result = _lu_solve(system, duration);
            break;
        default:
            throw std::logic_error("Invalid solving method");
            break;
    }
    return result;
}

}    // namespace solvers


#ifndef STAN_MATH_PRIM_SCAL_PROB_EXP_MOD_NORMAL_RNG_HPP
#define STAN_MATH_PRIM_SCAL_PROB_EXP_MOD_NORMAL_RNG_HPP

#include <stan/math/prim/scal/err/check_consistent_sizes.hpp>
#include <stan/math/prim/scal/err/check_finite.hpp>
#include <stan/math/prim/scal/err/check_not_nan.hpp>
#include <stan/math/prim/scal/err/check_positive_finite.hpp>
#include <stan/math/prim/scal/fun/constants.hpp>
#include <stan/math/prim/scal/meta/length.hpp>
#include <stan/math/prim/scal/meta/VectorBuilder.hpp>
#include <stan/math/prim/scal/prob/normal_rng.hpp>
#include <stan/math/prim/scal/prob/exponential_rng.hpp>
#include <stan/math/prim/scal/meta/include_summand.hpp>
#include <stan/math/prim/scal/fun/value_of.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/variate_generator.hpp>

namespace stan {
  namespace math {

    template <class RNG>
    inline double
    exp_mod_normal_rng(double mu,
                       double sigma,
                       double lambda,
                       RNG& rng) {
      static const char* function("exp_mod_normal_rng");

      check_finite(function, "Location parameter", mu);
      check_positive_finite(function, "Inv_scale parameter", lambda);
      check_positive_finite(function, "Scale parameter", sigma);

      return normal_rng(mu, sigma, rng)
        + exponential_rng(lambda, rng);
    }

  }
}
#endif


/**********************************************************************
align.cpp - Align two molecules or vectors of vector3

Copyright (C) 2010 by Noel M. O'Boyle

This file is part of the Open Babel project.
For more information, see <http://openbabel.org/>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
***********************************************************************/

#include <openbabel/babelconfig.h>

#include <vector>
#include <climits> // UINT_MAX

#include <openbabel/math/align.h>
#include <openbabel/graphsym.h>
#include <openbabel/math/vector3.h>

#include <Eigen/Core>
#include <Eigen/Geometry>
#include <Eigen/SVD>
#include <Eigen/LU>

using namespace std;

namespace OpenBabel
{
  OBAlign::OBAlign(bool includeH, bool symmetry) : _method(OBAlign::Kabsch)
  {
    _ready = false;
    _symmetry = symmetry;
    _includeH = includeH;
    _prefmol = 0;
  }

  OBAlign::OBAlign(const vector<vector3> &ref, const vector<vector3> &target) : _method(OBAlign::Kabsch)
  {
    SetRef(ref);
    SetTarget(target);
    _symmetry = false;
    _prefmol = 0;
  }

  OBAlign::OBAlign(const OBMol &refmol, const OBMol &targetmol, bool includeH, bool symmetry) : _method(OBAlign::Kabsch)
  {
    _symmetry = symmetry;
    _includeH = includeH;
    SetRefMol(refmol);
    SetTargetMol(targetmol);
  }

  void OBAlign::VectorsToMatrix(const vector<vector3> *pcoords, Eigen::MatrixXd &coords) {

    vector<vector3>::size_type N = pcoords->size();
    coords.resize(3, N);

    // Create a 3xN matrix of the coords
    vector<vector3>::const_iterator it;
    vector<vector3>::size_type colm;
    for (colm=0,it=pcoords->begin();colm<N;++colm,++it)
      coords.col(colm) = Eigen::Vector3d( it->AsArray() );
  }

  Eigen::Vector3d OBAlign::MoveToOrigin(Eigen::MatrixXd &coords) {

    vector<vector3>::size_type N = coords.cols();

    // Find the centroid
    Eigen::Vector3d centroid;
    centroid = coords.rowwise().sum() / N;

    // Subtract the centroids
    for (vector<vector3>::size_type i=0; i<N; ++i)
      coords.col(i) -= centroid;
    return centroid;
  }

  void OBAlign::SetRef(const vector<vector3> &ref) {
    _pref = &ref;
    VectorsToMatrix(_pref, _mref);
    _ref_centr = MoveToOrigin(_mref);

    _ready = false;
  }

  void OBAlign::SetTarget(const vector<vector3> &target) {
    _ptarget = ⌖
    VectorsToMatrix(_ptarget, _mtarget);
    _target_centr = MoveToOrigin(_mtarget);

    _ready = false;
  }

  void OBAlign::SetRefMol(const OBMol &refmol) {
    _prefmol = &refmol;

    // Set up the BitVec for the hydrogens and store the refmol coords
    _frag_atoms.Clear();
    _frag_atoms.Resize(refmol.NumAtoms() + 1);
    _refmol_coords.resize(0);
    OBAtom* atom;
    int delta = 1;
    _newidx.resize(0);

    for (unsigned int i=1; i<=refmol.NumAtoms(); ++i) {
      atom = refmol.GetAtom(i);
      if (_includeH || !atom->IsHydrogen()) {
        _frag_atoms.SetBitOn(i);
        _newidx.push_back(i - delta);
        _refmol_coords.push_back(atom->GetVector());
      }
      else {
        delta++;
        _newidx.push_back(UINT_MAX);
      }
    }
    SetRef(_refmol_coords);

    if (_symmetry) {
      FindAutomorphisms((OBMol*)&refmol, _aut, _frag_atoms);
    }
  }

  void OBAlign::SetTargetMol(const OBMol &targetmol) {
    _ptargetmol = &targetmol;
    _targetmol_coords.resize(0);
    OBAtom const *atom;
    for (unsigned int i=1; i<=targetmol.NumAtoms(); ++i) {
      atom = targetmol.GetAtom(i);
      if (_includeH || !atom->IsHydrogen())
        _targetmol_coords.push_back(atom->GetVector());
    }
    SetTarget(_targetmol_coords);
  }

  void OBAlign::SetMethod(OBAlign::AlignMethod method) {
    _method = method;
  }

/* Evaluates the Newton-Raphson correction for the Horn quartic.
   only 11 FLOPs */
  static double eval_horn_NR_corrxn(const vector<double> &c, const double x)
  {
    double x2 = x*x;
    double b = (x2 + c[2])*x;
    double a = b + c[1];

    return((a*x + c[0])/(2.0*x2*x + b + a));
  }

  /* Newton-Raphson root finding */
  static double QCProot(const vector<double> &coeff, double guess, const double delta)
  {
    int             i;
    double          oldg;
    double initialg = guess;

    for (i = 0; i < 50; ++i)
    {
        oldg = guess;
        /* guess -= (eval_horn_quart(coeff, guess) / eval_horn_quart_deriv(coeff, guess)); */
        guess -= eval_horn_NR_corrxn(coeff, guess);

        if (fabs(guess - oldg) < fabs(delta*guess))
            return(guess);
    }

    return initialg + 1.0; // Failed to converge!
  }

  vector<double> CalcQuarticCoeffs(const Eigen::Matrix3d &M)
  {
    vector<double> coeff(4);

    double          Sxx, Sxy, Sxz, Syx, Syy, Syz, Szx, Szy, Szz;
    double          Szz2, Syy2, Sxx2, Sxy2, Syz2, Sxz2, Syx2, Szy2, Szx2,
                    SyzSzymSyySzz2, Sxx2Syy2Szz2Syz2Szy2, Sxy2Sxz2Syx2Szx2,
                    SxzpSzx, SyzpSzy, SxypSyx, SyzmSzy,
                    SxzmSzx, SxymSyx, SxxpSyy, SxxmSyy;

#ifdef HAVE_EIGEN3
    Eigen::MatrixXd M_sqr = M.array().square();
#else
    Eigen::MatrixXd M_sqr = M.cwise().square();
#endif

    Sxx = M(0, 0);
    Sxy = M(1, 0);
    Sxz = M(2, 0);
    Syx = M(0, 1);
    Syy = M(1, 1);
    Syz = M(2, 1);
    Szx = M(0, 2);
    Szy = M(1, 2);
    Szz = M(2, 2);

    Sxx2 = Sxx * Sxx;
    Syy2 = Syy * Syy;
    Szz2 = Szz * Szz;

    Sxy2 = Sxy * Sxy;
    Syz2 = Syz * Syz;
    Sxz2 = Sxz * Sxz;

    Syx2 = Syx * Syx;
    Szy2 = Szy * Szy;
    Szx2 = Szx * Szx;

    SyzSzymSyySzz2 = 2.0*(Syz*Szy - Syy*Szz);
    Sxx2Syy2Szz2Syz2Szy2 = Syy2 + Szz2 - Sxx2 + Syz2 + Szy2;

    /* coeff[4] = 1.0; */
    /* coeff[3] = 0.0; */
    // coeff[2] = -2.0 * (Sxx2 + Syy2 + Szz2 + Sxy2 + Syx2 + Sxz2 + Szx2 + Syz2 + Szy2);
    coeff[2] = -2.0 * M_sqr.sum();
    coeff[1] = 8.0 * (Sxx*Syz*Szy + Syy*Szx*Sxz + Szz*Sxy*Syx - Sxx*Syy*Szz - Syz*Szx*Sxy - Szy*Syx*Sxz);

    SxzpSzx = Sxz+Szx;
    SyzpSzy = Syz+Szy;
    SxypSyx = Sxy+Syx;
    SyzmSzy = Syz-Szy;
    SxzmSzx = Sxz-Szx;
    SxymSyx = Sxy-Syx;
    SxxpSyy = Sxx+Syy;
    SxxmSyy = Sxx-Syy;
    Sxy2Sxz2Syx2Szx2 = Sxy2 + Sxz2 - Syx2 - Szx2;

    coeff[0] = Sxy2Sxz2Syx2Szx2 * Sxy2Sxz2Syx2Szx2
             + (Sxx2Syy2Szz2Syz2Szy2 + SyzSzymSyySzz2) * (Sxx2Syy2Szz2Syz2Szy2 - SyzSzymSyySzz2)
             + (-(SxzpSzx)*(SyzmSzy)+(SxymSyx)*(SxxmSyy-Szz)) * (-(SxzmSzx)*(SyzpSzy)+(SxymSyx)*(SxxmSyy+Szz))
             + (-(SxzpSzx)*(SyzpSzy)-(SxypSyx)*(SxxpSyy-Szz)) * (-(SxzmSzx)*(SyzmSzy)-(SxypSyx)*(SxxpSyy+Szz))
             + (+(SxypSyx)*(SyzpSzy)+(SxzpSzx)*(SxxmSyy+Szz)) * (-(SxymSyx)*(SyzmSzy)+(SxzpSzx)*(SxxpSyy+Szz))
             + (+(SxypSyx)*(SyzmSzy)+(SxzmSzx)*(SxxmSyy-Szz)) * (-(SxymSyx)*(SyzpSzy)+(SxzmSzx)*(SxxpSyy-Szz));

    return coeff;
  }

  void OBAlign::TheobaldAlign(const Eigen::MatrixXd &mtarget)
  {
    // M = B(t) times A (where A, B are N x 3 matrices)
    Eigen::Matrix3d M = mtarget * _mref.transpose();

    // Maximum value for lambda is (Ga + Gb) / 2
    double innerprod = mtarget.squaredNorm() + _mref.squaredNorm();

    vector<double> coeffs = CalcQuarticCoeffs(M);
    double lambdamax = QCProot(coeffs, 0.5 * innerprod, 1e-6);
    if (lambdamax > (0.5 * innerprod))
      _fail = true;
    else {
      double sqrdev = innerprod - (2.0 * lambdamax);
      _rmsd = sqrt(sqrdev / mtarget.cols());
    }
  }

  void OBAlign::SimpleAlign(const Eigen::MatrixXd &mtarget)
  {
    // Covariance matrix C = X times Y(t)
    Eigen::Matrix3d C = _mref * mtarget.transpose();

    // Singular Value Decomposition of C into USV(t)
#ifdef HAVE_EIGEN3
    Eigen::JacobiSVD<Eigen::Matrix3d> svd(C, Eigen::ComputeFullU | Eigen::ComputeFullV);
#else
    Eigen::SVD<Eigen::Matrix3d> svd(C);
#endif

    // Prepare matrix T
    double sign = (C.determinant() > 0) ? 1. : -1.; // Sign of determinant
    Eigen::Matrix3d T = Eigen::Matrix3d::Identity();
    T(2,2) = sign;

    // Optimal rotation matrix, U, is V T U(t)
    _rotMatrix = svd.matrixV() * T * svd.matrixU().transpose();

    // Rotate target using rotMatrix
    _result = _rotMatrix.transpose() * mtarget;

    Eigen::MatrixXd deviation = _result - _mref;
#ifdef HAVE_EIGEN3
    Eigen::MatrixXd sqr = deviation.array().square();
#else
    Eigen::MatrixXd sqr = deviation.cwise().square();
#endif
    double sum = sqr.sum();
    _rmsd = sqrt( sum / sqr.cols() );

  }

  bool OBAlign::Align()
  {
    vector<vector3>::size_type N = _ptarget->size();

    if (_pref->size() != N) {
      obErrorLog.ThrowError(__FUNCTION__, "Cannot align the reference and target as they are of different size" , obError);
      return false;
    }

    if (!_symmetry || _aut.size() == 1) {
      if (_method == OBAlign::Kabsch)
        SimpleAlign(_mtarget);
      else
        TheobaldAlign(_mtarget);
    }
    else {  // Iterate over the automorphisms

      // ...for storing the results from the lowest rmsd to date
      double min_rmsd = DBL_MAX;
      Eigen::MatrixXd result, rotMatrix;

      // Try all of the symmetry-allowed permutations
      OBIsomorphismMapper::Mappings::const_iterator cit;
      Eigen::MatrixXd mtarget(_mtarget.rows(), _mtarget.cols());

      for (unsigned int k = 0; k < _aut.size(); ++k) {
        // Rearrange columns of _mtarget for this permutation
        unsigned int i=0;
        for (unsigned int j=1; j<=_prefmol->NumAtoms(); ++j) {
          if (_frag_atoms.BitIsSet(j)) {
            for (std::size_t l = 0; l < _aut[k].size(); ++l)
              if (_aut[k][l].first == j - 1) {
                mtarget.col(i) = _mtarget.col(_newidx[_aut[k][l].second]);
                break;
              }
            i++;
          }
        }
        if (_method == OBAlign::Kabsch)
          SimpleAlign(mtarget);
        else
          TheobaldAlign(mtarget);
        if (_rmsd < min_rmsd) {
          min_rmsd = _rmsd;
          result = _result;
          rotMatrix = _rotMatrix;
        }
      }

      // Restore the best answer from memory
      _rmsd = min_rmsd;
      _result = result;
      _rotMatrix = rotMatrix;
    }

    _ready = true;
    return true;
  }

  matrix3x3 OBAlign::GetRotMatrix()
  {
    if (!_ready) {
      obErrorLog.ThrowError(__FUNCTION__, "Rotation matrix not available until you call Align()" , obError);
      return matrix3x3();
    }

    // Convert Eigen::Matrix to matrix3x3
    double rot[3][3];
    for (int row=0; row<3; ++row)
       for (int col=0; col<3; ++col)
         rot[col][row] = _rotMatrix(row, col); // Return in form suitable for use in expressions like "result *= rotMatrix";
    matrix3x3 rotmat = matrix3x3(rot);

    return rotmat;
  }

  vector<vector3> OBAlign::GetAlignment() {
    vector<vector3> aligned_coords;
    if (!_ready) {
      obErrorLog.ThrowError(__FUNCTION__, "Alignment not available until you call Align()" , obError);
      return aligned_coords;
    }

    if (!_prefmol || _includeH) {
      // Add back the centroid of the reference and convert to vv3
      Eigen::Vector3d tmp;
      aligned_coords.reserve(_result.cols());
      for (int i=0; i<_result.cols(); ++i) {
        tmp = _result.col(i) + _ref_centr;
        aligned_coords.push_back(vector3(tmp(0), tmp(1), tmp(2)));
      }
    }
    else { // Need to deal with the case where hydrogens were excluded
      vector<vector3> target_coords;
      for (unsigned int i=1; i<=_ptargetmol->NumAtoms(); ++i)
        target_coords.push_back(_ptargetmol->GetAtom(i)->GetVector());
      Eigen::MatrixXd mtarget;
      VectorsToMatrix(&target_coords, mtarget);

      // Subtract the centroid of the non-H atoms
      for (vector<vector3>::size_type i=0; i<mtarget.cols(); ++i)
        mtarget.col(i) -= _target_centr;

      // Rotate
      Eigen::MatrixXd result = mtarget.transpose() * _rotMatrix;
      result.transposeInPlace();

      // Add back the centroid of the reference and convert to vv3
      Eigen::Vector3d tmp;
      aligned_coords.reserve(_result.cols());
      for (int i=0; i<result.cols(); ++i) {
        tmp = result.col(i) + _ref_centr;
        aligned_coords.push_back(vector3(tmp(0), tmp(1), tmp(2)));
      }
    }

    return aligned_coords;
  }

  bool OBAlign::UpdateCoords(OBMol* target) {
    if (!_ready) {
      obErrorLog.ThrowError(__FUNCTION__, "Alignment not available until you call Align()" , obError);
      return false;
    }

    vector<vector3> newcoords = GetAlignment();
    if (newcoords.size() != target->NumAtoms()) {
      obErrorLog.ThrowError(__FUNCTION__, "Cannot update the target molecule with the alignment coordinates as they are of different size" , obError);
      return false;
    }

    int i = 0;
    FOR_ATOMS_OF_MOL(a, *target) {
      a->SetVector(newcoords.at(i));
      i++;
    }

    return true;
  }

  double OBAlign::GetRMSD() {
    if (!_ready) {
      obErrorLog.ThrowError(__FUNCTION__, "RMSD not available until you call Align()" , obError);
      return (double) NULL;
    }

    return _rmsd;
  }

} // namespace OpenBabel

//! \file align.cpp
//! \brief Handle 3D coordinates.


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

/*
 Copyright (C) 2008 Klaus Spanderen
 Copyright (C) 2014 Johannes Göttker-Schnetmann

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

#ifndef quantlib_test_fd_heston_hpp
#define quantlib_test_fd_heston_hpp

#include <boost/test/unit_test.hpp>
#include "speedlevel.hpp"

/* remember to document new and/or updated tests in the Doxygen
   comment block of the corresponding class */

class FdHestonTest {
public:
    static void testFdmHestonVarianceMesher();
    static void testFdmHestonBarrier();
    static void testFdmHestonBarrierVsBlackScholes();
    static void testFdmHestonAmerican();
    static void testFdmHestonIkonenToivanen();
    static void testFdmHestonEuropeanWithDividends();
    static void testFdmHestonConvergence();
    static void testFdmHestonBlackScholes();
    static void testFdmHestonIntradayPricing();
    static void testMethodOfLines();
    static void testSpuriousOscillations();

    static boost::unit_test_framework::test_suite* suite(SpeedLevel);
};

#endif


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

#ifdef _MSC_VER
#pragma once
#endif

#include <boost/math/special_functions/math_fwd.hpp>
#include <boost/math/tools/config.hpp>
#include <boost/math/constants/constants.hpp>
#include <boost/math/policies/error_handling.hpp>
#include <boost/math/special_functions/ellint_rd.hpp>
#include <boost/math/special_functions/ellint_rf.hpp>
#include <boost/math/special_functions/pow.hpp>

namespace boost { namespace math { namespace detail{

   template <typename T, typename Policy>
   T ellint_rg_imp(T x, T y, T z, const Policy& pol)
   {
      BOOST_MATH_STD_USING
      static const char* function = "boost::math::ellint_rf<%1%>(%1%,%1%,%1%)";

      if(x < 0 || y < 0 || z < 0)
      {
         return policies::raise_domain_error<T>(function,
            "domain error, all arguments must be non-negative, "
            "only sensible result is %1%.",
            std::numeric_limits<T>::quiet_NaN(), pol);
      }
      //
      // Function is symmetric in x, y and z, but we require
      // (x - z)(y - z) >= 0 to avoid cancellation error in the result
      // which implies (for example) x >= z >= y
      //
      using std::swap;
      if(x < y)
         swap(x, y);
      if(x < z)
         swap(x, z);
      if(y > z)
         swap(y, z);

      BOOST_ASSERT(x >= z);
      BOOST_ASSERT(z >= y);
      //
      // Special cases from http://dlmf.nist.gov/19.20#ii
      //
      if(x == z)
      {
         if(y == z)
         {
            // x = y = z
            // This also works for x = y = z = 0 presumably.
            return sqrt(x);
         }
         else if(y == 0)
         {
            // x = y, z = 0
            return constants::pi<T>() * sqrt(x) / 4;
         }
         else
         {
            // x = z, y != 0
            swap(x, y);
            return (x == 0) ? T(sqrt(z) / 2) : T((z * ellint_rc_imp(x, z, pol) + sqrt(x)) / 2);
         }
      }
      else if(y == z)
      {
         if(x == 0)
            return constants::pi<T>() * sqrt(y) / 4;
         else
            return (y == 0) ? T(sqrt(x) / 2) : T((y * ellint_rc_imp(x, y, pol) + sqrt(x)) / 2);
      }
      else if(y == 0)
      {
         swap(y, z);
         //
         // Special handling for common case, from
         // Numerical Computation of Real or Complex Elliptic Integrals, eq.46
         //
         T xn = sqrt(x);
         T yn = sqrt(y);
         T x0 = xn;
         T y0 = yn;
         T sum = 0;
         T sum_pow = 0.25f;

         while(fabs(xn - yn) >= 2.7 * tools::root_epsilon<T>() * fabs(xn))
         {
            T t = sqrt(xn * yn);
            xn = (xn + yn) / 2;
            yn = t;
            sum_pow *= 2;
            sum += sum_pow * boost::math::pow<2>(xn - yn);
         }
         T RF = constants::pi<T>() / (xn + yn);
         return ((boost::math::pow<2>((x0 + y0) / 2) - sum) * RF) / 2;
      }
      return (z * ellint_rf_imp(x, y, z, pol)
         - (x - z) * (y - z) * ellint_rd_imp(x, y, z, pol) / 3
         + sqrt(x * y / z)) / 2;
   }

} // namespace detail

template <class T1, class T2, class T3, class Policy>
inline typename tools::promote_args<T1, T2, T3>::type
   ellint_rg(T1 x, T2 y, T3 z, const Policy& pol)
{
   typedef typename tools::promote_args<T1, T2, T3>::type result_type;
   typedef typename policies::evaluation<result_type, Policy>::type value_type;
   return policies::checked_narrowing_cast<result_type, Policy>(
      detail::ellint_rg_imp(
         static_cast<value_type>(x),
         static_cast<value_type>(y),
         static_cast<value_type>(z), pol), "boost::math::ellint_rf<%1%>(%1%,%1%,%1%)");
}

template <class T1, class T2, class T3>
inline typename tools::promote_args<T1, T2, T3>::type
   ellint_rg(T1 x, T2 y, T3 z)
{
   return ellint_rg(x, y, z, policies::policy<>());
}

}} // namespaces

#endif // BOOST_MATH_ELLINT_RG_HPP


// Copyright (c) 2019 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE ConstExprAddTest

#include <boost/test/unit_test.hpp>
#include <filereader.hpp>
#include <popart/builder.hpp>
#include <popart/dataflow.hpp>
#include <popart/devicemanager.hpp>
#include <popart/half.hpp>
#include <popart/inputshapeinfo.hpp>
#include <popart/ir.hpp>
#include <popart/names.hpp>
#include <popart/ndarraywrapper.hpp>
#include <popart/op/identity.hpp>
#include <popart/op/l1.hpp>
#include <popart/session.hpp>
#include <popart/sgd.hpp>
#include <popart/tensor.hpp>
#include <popart/tensordata.hpp>
#include <popart/tensors.hpp>
#include <popart/testdevice.hpp>

#include <math.h>

using namespace popart;

BOOST_AUTO_TEST_CASE(ConstExprTest_Add0) {

  // The compute graph :
  //
  // data  -----------------------------|
  //                                    |
  //                                    |
  //                                    |- RESHAPE ---> output
  //                                    |
  // shape0 -------|                    |
  //               |                    |
  //               |- ADD - outshape ---|
  //               |
  // shape1 -------|

  // We will reshape a tensor from rank-4:
  Shape inShape = {2, 5, 3, 4};
  // to rank-2: {10, 12},
  // Note above that the total number elements of the tensor remains 120

  // where the output shape {10, 12} will be the sum of two tensors,
  // 1)
  Shape shape0 = {7, 4};
  // 2)
  Shape shape1 = {3, 8};

  Shape outShapeSize = {static_cast<int64_t>(shape0.size())};
  TensorInfo inInfo{"FLOAT", inShape};

  ConstVoidData out0ShapeData = {shape0.data(), {"INT64", outShapeSize}};
  ConstVoidData out1ShapeData = {shape1.data(), {"INT64", outShapeSize}};

  // Build an onnx model
  auto builder = Builder::create();
  auto aiOnnx  = builder->aiOnnxOpset9();
  // The two fixed-point tensors which are Constants
  auto shape0Id   = aiOnnx.constant(out0ShapeData, "out0ShapeData");
  auto shape1Id   = aiOnnx.constant(out1ShapeData, "out1ShapeData");
  auto inId       = builder->addInputTensor(inInfo);
  auto outShapeId = aiOnnx.add({shape0Id, shape1Id});
  auto outId      = aiOnnx.reshape({inId, outShapeId});
  auto l1         = builder->aiGraphcoreOpset1().l1loss({outId}, 0.1);

  auto proto      = builder->getModelProto();
  auto modelProto = io::getModelFromString(proto);

  // Create the IR, adding outId as an anchor
  auto art       = AnchorReturnType("All");
  auto dataFlow  = DataFlow(1, {{outId, art}});
  auto optimizer = ConstSGD(0.01);
  auto device    = createTestDevice(TEST_TARGET);

  Ir ir;
  ir.prepare({modelProto,
              InputShapeInfo(),
              dataFlow,
              l1,
              &optimizer,
              *device,
              {}, // no SessionOptions
              Patterns::create({"PostNRepl"}).enableRuntimeAsserts(false)});

  // Check the ir
  // 1) that the Reshape Op is present,
  BOOST_CHECK(ir.opsOfType(Onnx::AiOnnx::OpSet9::Reshape).size() == 1);
  // 2) that the shape of the output tensor is as specified.
  Shape outShape;
  for (int i = 0; i < outShapeSize[0]; ++i) {
    outShape.push_back(shape0[i] + shape1[i]);
  }
  BOOST_CHECK(ir.getMainGraphTensors().get(outId)->info.shape() == outShape);
}

BOOST_AUTO_TEST_CASE(ConstExprTest_Add1) {
  // Testing ConstExpr folding on broadcast adds of
  // initializers and constants

  // The compute graph :
  //
  // w0 --|
  //      |--[bc-add]-- a0 --|
  // w1 --|                  |
  //                         |--[bc-add]-- a2 -|
  // c0 --|                  |                 |
  //      |--[bc-add]-- a1 --|                 |-- [matmul] -- o
  // c1 --|                                    |
  //                                           |
  // i1 ---------------------------------------|
  //

  // weights
  TensorInfo w0Shape{"FLOAT", std::vector<int64_t>{1, 3}};
  float w0Vals[1 * 3]  = {0};
  ConstVoidData w0Data = {w0Vals, w0Shape};

  TensorInfo w1Shape{"FLOAT", std::vector<int64_t>{3, 3}};
  float w1Vals[3 * 3]  = {1};
  ConstVoidData w1Data = {w1Vals, w1Shape};

  // consts
  TensorInfo c0Shape{"FLOAT", std::vector<int64_t>{1, 3}};
  float c0Vals[1 * 3]  = {2};
  ConstVoidData c0Data = {c0Vals, c0Shape};

  TensorInfo c1Shape{"FLOAT", std::vector<int64_t>{1}};
  float c1Vals[1]      = {3};
  ConstVoidData c1Data = {c1Vals, c1Shape};

  // input
  TensorInfo inputInfo{"FLOAT", std::vector<int64_t>{3, 4}};

  // Build an onnx model
  auto builder = Builder::create();
  auto aiOnnx  = builder->aiOnnxOpset9();

  auto w0Id = builder->addInitializedInputTensor(w0Data);
  auto w1Id = builder->addInitializedInputTensor(w1Data);
  auto a0   = aiOnnx.add({w0Id, w1Id}, "a0");

  auto c0Id = aiOnnx.constant(c0Data, "c0Data");
  auto c1Id = aiOnnx.constant(c1Data, "c1Data");
  auto a1   = aiOnnx.add({c0Id, c1Id}, "a1");

  auto a2      = aiOnnx.add({a0, a1}, "a2");
  auto inputId = builder->addInputTensor(inputInfo);
  auto outId   = aiOnnx.matmul({a2, inputId});
  auto l1      = builder->aiGraphcoreOpset1().l1loss({outId}, 0.1);

  auto proto      = builder->getModelProto();
  auto modelProto = io::getModelFromString(proto);

  // Create the IR, adding outId as an anchor
  auto art       = AnchorReturnType("All");
  auto dataFlow  = DataFlow(1, {{outId, art}});
  auto optimizer = ConstSGD(0.01);
  auto device    = createTestDevice(TEST_TARGET);

  Ir ir;
  ir.prepare({modelProto,
              InputShapeInfo(),
              dataFlow,
              {}, // no loss
              {}, // no optimizer
              *device,
              {}, // no SessionOptions
              Patterns::create({"PostNRepl"}).enableRuntimeAsserts(false)});

  // Check that the Add Op is has been removed from the IR
  // by ConstExpr folding
  // TODO: this test will give a false pass if the model is using
  // a newer opset. Fix when T6274 is complete
  BOOST_CHECK(ir.opsOfType(Onnx::AiOnnx::OpSet9::Add).size() == 0);
}

BOOST_AUTO_TEST_CASE(ConstExprTest_Add2) {
  // Testing ConstExpr folding on an input tensor whose consumer
  // has another input that cannot be removed by ConstExpr folding

  // Model from the builder:
  //
  // v0 ----|
  //        |--[add]-- a0 --|
  //        |               |
  // c0 --|-|               |--[add]-- o
  //      |                 |
  //      |--[add]---- a1 --|
  // c1 --|
  //
  // Expected outcome after ConstExpr folding :
  //
  // v0 ---|
  //       |--[add]-- a0 --|
  // c0 ---|               |--[add]-- o
  //                       |
  // a1 -------------------|
  //

  // consts
  TensorInfo c0Shape{"FLOAT", std::vector<int64_t>{2, 2}};
  float c0Vals[2 * 2]  = {2};
  ConstVoidData c0Data = {c0Vals, c0Shape};

  TensorInfo c1Shape{"FLOAT", std::vector<int64_t>{2, 2}};
  float c1Vals[2 * 2]  = {3};
  ConstVoidData c1Data = {c1Vals, c1Shape};

  // input
  TensorInfo inputInfo{"FLOAT", std::vector<int64_t>{2, 2}};

  // Build an onnx model
  auto builder = Builder::create();
  auto aiOnnx  = builder->aiOnnxOpset9();

  auto v0Id = builder->addInputTensor(inputInfo);
  auto c0Id = aiOnnx.constant(c0Data, "c0Data");
  auto c1Id = aiOnnx.constant(c1Data, "c1Data");

  auto a0 = aiOnnx.add({v0Id, c0Id}, "a0");
  auto a1 = aiOnnx.add({c0Id, c1Id}, "a1");

  auto o = aiOnnx.add({a0, a1}, "o");
  builder->addOutputTensor(o);

  auto proto      = builder->getModelProto();
  auto modelProto = io::getModelFromString(proto);

  // Create the IR, adding outId as an anchor
  auto art      = AnchorReturnType("All");
  auto dataFlow = DataFlow(1, {{o, art}});
  auto device   = createTestDevice(TEST_TARGET);

  Ir ir;
  ir.prepare({modelProto,
              InputShapeInfo(),
              dataFlow,
              {}, // no loss
              {}, // no optimizer
              *device,
              {}, // no SessionOptions
              Patterns::create({"PostNRepl"}).enableRuntimeAsserts(false)});

  // Check that the producer of a1 Add Op is has been removed from the IR
  // by ConstExpr folding
  BOOST_CHECK(ir.opsOfType(Onnx::AiOnnx::OpSet9::Add).size() == 2);
}

template <typename T> void ConstExprTest_Add_Type(std::string type) {

  // The compute graph :
  //
  // data  -----------------------------|
  //                                    |
  //                                    |
  //                                    |- ADD ---> output
  //                                    |
  // shape0 -------|                    |
  //               |                    |
  //               |- ADD - outshape ---|
  //               |
  // shape1 -------|

  Shape inShape = {2, 2};

  T input1_raw[] = {(T)1.2f, 2, 3, 4};
  std::vector<T> input1(input1_raw, std::end(input1_raw));

  T input2_raw[] = {(T)1.7f, 2, 3, 4};
  std::vector<T> input2(input2_raw, std::end(input2_raw));

  Shape outShapeSize = {2, 2};
  TensorInfo inInfo{type, inShape};

  ConstVoidData out0ShapeData = {input1.data(), {type, outShapeSize}};
  ConstVoidData out1ShapeData = {input2.data(), {type, outShapeSize}};

  T output_raw[4];
  popart::NDArrayWrapper<T> output(output_raw, {2, 2});

  // Build an onnx model
  auto builder = Builder::create();
  auto aiOnnx  = builder->aiOnnxOpset9();
  // The two fixed-point tensors which are Constants
  auto shape0Id   = aiOnnx.constant(out0ShapeData, "out0ShapeData");
  auto shape1Id   = aiOnnx.constant(out1ShapeData, "out1ShapeData");
  auto inId       = builder->addInputTensor(inInfo);
  auto outShapeId = aiOnnx.add({shape0Id, shape1Id});
  auto outId      = aiOnnx.add({inId, outShapeId});
  builder->addOutputTensor(outId);

  std::map<popart::TensorId, popart::IArray &> anchors = {{outId, output}};

  auto proto = builder->getModelProto();

  auto art      = AnchorReturnType("All");
  auto dataFlow = DataFlow(1, {{outId, art}});

  auto device = popart::createTestDevice(TEST_TARGET);

  auto session = popart::InferenceSession::createFromOnnxModel(
      proto,
      dataFlow,
      device,
      InputShapeInfo(),
      {}, // no SessionOptions
      Patterns::create({"PostNRepl"}).enableRuntimeAsserts(false));

  T rawInputData[4] = {(T)1.1f, 2, 3, 4};
  popart::NDArrayWrapper<T> inData(rawInputData, {2, 2});
  std::map<popart::TensorId, popart::IArray &> inputs = {{inId, inData}};

  session->prepareDevice();
  popart::StepIO stepio(inputs, anchors);
  session->run(stepio);

  // Check the ir
  popart::logging::ir::err("input1 : {}", input1[0]);
  popart::logging::ir::err("input2 : {}", input2[0]);
  popart::logging::ir::err("indata : {}", inData[0]);
  popart::logging::ir::err("output : {}", output[0]);

  BOOST_CHECK((input1[0] + input2[0] + inData[0]) == output[0]);
}

BOOST_AUTO_TEST_CASE(ConstExprTest_Div0) {

  // The compute graph :
  //
  // data  -----------------------------|
  //                                    |
  //                                    |
  //                                    |- RESHAPE ---> output
  //                                    |
  // shape0 -------|                    |
  //               |                    |
  //               |- ADD - outshape ---|
  //               |
  // shape1 -------|

  // We will reshape a tensor from rank-4:
  Shape inShape = {2, 2, 2};
  // to rank-2: {2, 4},
  // Note above that the total number elements of the tensor remains 120

  // where the output shape {2, 4} will be the sum of two tensors,
  // 1)
  Shape shape0 = {4, 12};
  // 2)
  Shape shape1 = {2, 3};

  Shape outShapeSize = {static_cast<int64_t>(shape0.size())};
  TensorInfo inInfo{"FLOAT", inShape};

  ConstVoidData out0ShapeData = {shape0.data(), {"INT64", outShapeSize}};
  ConstVoidData out1ShapeData = {shape1.data(), {"INT64", outShapeSize}};

  // Build an onnx model
  auto builder = Builder::create();
  auto aiOnnx  = builder->aiOnnxOpset9();
  // The two fixed-point tensors which are Constants
  auto shape0Id   = aiOnnx.constant(out0ShapeData, "out0ShapeData");
  auto shape1Id   = aiOnnx.constant(out1ShapeData, "out1ShapeData");
  auto inId       = builder->addInputTensor(inInfo);
  auto outShapeId = aiOnnx.div({shape0Id, shape1Id});
  auto outId      = aiOnnx.reshape({inId, outShapeId});
  auto l1         = builder->aiGraphcoreOpset1().l1loss({outId}, 0.1);

  auto proto      = builder->getModelProto();
  auto modelProto = io::getModelFromString(proto);

  // Create the IR, adding outId as an anchor
  auto art       = AnchorReturnType("All");
  auto dataFlow  = DataFlow(1, {{outId, art}});
  auto optimizer = ConstSGD(0.01);
  auto device    = createTestDevice(TEST_TARGET);

  Ir ir;
  ir.prepare({modelProto,
              InputShapeInfo(),
              dataFlow,
              l1,
              &optimizer,
              *device,
              {}, // no SessionOptions
              Patterns::create({"PostNRepl"}).enableRuntimeAsserts(false)});

  // Check the ir
  // 1) that the Reshape Op is present,
  BOOST_CHECK(ir.opsOfType(Onnx::AiOnnx::OpSet9::Reshape).size() == 1);
  // 2) that the shape of the output tensor is as specified.
  Shape outShape;
  for (int i = 0; i < outShapeSize[0]; ++i) {
    outShape.push_back(shape0[i] / shape1[i]);
  }
  BOOST_CHECK(ir.getMainGraphTensors().get(outId)->info.shape() == outShape);
}

BOOST_AUTO_TEST_CASE(ConstExprTest_Add_Types) {
  // ConstExprTest_Add_Type<uint32_t>("UINT32");
  // ConstExprTest_Add_Type<uint64_t>("UINT64");
  ConstExprTest_Add_Type<int32_t>("INT32");
  // ConstExprTest_Add_Type<int64_t>("INT64");
  ConstExprTest_Add_Type<popart::float16_t>("FLOAT16");
  ConstExprTest_Add_Type<float>("FLOAT");
  // ConstExprTest_Add_Type("DOUBLE");
}

template <typename T> std::string getTypeString();

template <> std::string getTypeString<int64_t>() { return "INT64"; }
template <> std::string getTypeString<float16_t>() { return "FLOAT16"; }
template <> std::string getTypeString<float_t>() { return "FLOAT"; }

template <typename T>
void ConstExprTest_Elementwise_Test(
    std::vector<T> in0,
    std::vector<T> in1,
    std::vector<T> output,
    std::function<TensorId(std::unique_ptr<popart::Builder> &builder,
                           const std::vector<TensorId> &args)> elementWiseFn) {

  // The compute graph :
  //
  // data  -----------------------------|
  //                                    |
  //                                    |
  //                                    |- ADD ---> output
  //                                    |
  // shape0 -------|                    |
  //               |                    |
  //               |- OP - outshape ----|
  //               |
  // shape1 -------|

  std::vector<T> data = {42, 42};
  Shape dataShape     = {static_cast<int64_t>(data.size())};
  TensorInfo dataInfo{getTypeString<T>(), dataShape};

  Shape in0Shape        = {static_cast<int64_t>(in0.size())};
  ConstVoidData in0Data = {in0.data(), {getTypeString<T>(), in0Shape}};

  Shape in1Shape        = {static_cast<int64_t>(in1.size())};
  ConstVoidData in1Data = {in1.data(), {getTypeString<T>(), in1Shape}};

  // Build an onnx model
  auto builder = Builder::create();
  auto aiOnnx  = builder->aiOnnxOpset10();
  // The two fixed-point tensors which are Constants
  auto in0Id      = aiOnnx.constant(in0Data, "in0Data");
  auto in1Id      = aiOnnx.constant(in1Data, "in1Data");
  auto dataId     = builder->addInputTensor(dataInfo);
  auto outShapeId = elementWiseFn(builder, {in0Id, in1Id});

  auto outId = aiOnnx.add({dataId, outShapeId});
  auto l1    = builder->aiGraphcoreOpset1().l1loss({outId}, 0.1);

  auto proto      = builder->getModelProto();
  auto modelProto = io::getModelFromString(proto);

  // Create the IR, adding outId as an anchor
  auto art       = AnchorReturnType("All");
  auto dataFlow  = DataFlow(1, {{outId, art}});
  auto optimizer = ConstSGD(0.01);
  auto device    = createTestDevice(TEST_TARGET);

  Ir ir;
  ir.prepare({modelProto,
              InputShapeInfo(),
              dataFlow,
              l1,
              &optimizer,
              *device,
              {}, // no SessionOptions
              Patterns::create({"PostNRepl"}).enableRuntimeAsserts(false)});

  // Check the ir
  // 1) that the Add Op is present,
  BOOST_CHECK(ir.opsOfType(Onnx::AiOnnx::OpSet9::Add).size() == 1);
  // 2) that the value of the output tensor is as specified.
  T *p = static_cast<T *>(
      ir.getMainGraphTensors().get(outShapeId)->tensorData()->data());
  for (int i = 0; i < output.size(); ++i) {
    BOOST_CHECK(p[i] == output[i]);
  }
}

BOOST_AUTO_TEST_CASE(ConstExprTest_Elementwise) {
  ConstExprTest_Elementwise_Test<int64_t>(
      {4, 12},
      {2, 3},
      {2, 4},
      [](std::unique_ptr<popart::Builder> &builder,
         const std::vector<TensorId> &args) -> TensorId {
        return builder->aiOnnxOpset10().div(args);
      });

  ConstExprTest_Elementwise_Test<int64_t>(
      {4, 12},
      {2}, // Will be broadcast to {2, 2}
      {2, 6},
      [](std::unique_ptr<popart::Builder> &builder,
         const std::vector<TensorId> &args) -> TensorId {
        return builder->aiOnnxOpset10().div(args);
      });

  ConstExprTest_Elementwise_Test<float16_t>(
      {8.0, 18.0},
      {4.0, 3.0},
      {2.0, 6.0},
      [](std::unique_ptr<popart::Builder> &builder,
         const std::vector<TensorId> &args) -> TensorId {
        return builder->aiOnnxOpset10().div(args);
      });

  ConstExprTest_Elementwise_Test<float_t>(
      {10.0, 21.0},
      {4.0, 5.0},
      {2.5, 4.2},
      [](std::unique_ptr<popart::Builder> &builder,
         const std::vector<TensorId> &args) -> TensorId {
        return builder->aiOnnxOpset10().div(args);
      });

  ConstExprTest_Elementwise_Test<int64_t>(
      {4, 12},
      {2, 3},
      {6, 15},
      [](std::unique_ptr<popart::Builder> &builder,
         const std::vector<TensorId> &args) -> TensorId {
        return builder->aiOnnxOpset10().add(args);
      });

  ConstExprTest_Elementwise_Test<int64_t>(
      {4, 12},
      {2, 3},
      {2, 9},
      [](std::unique_ptr<popart::Builder> &builder,
         const std::vector<TensorId> &args) -> TensorId {
        return builder->aiOnnxOpset10().sub(args);
      });

  ConstExprTest_Elementwise_Test<float_t>(
      {4.1, 101},
      {0.1, 50.5},
      {4.0, 50.5},
      [](std::unique_ptr<popart::Builder> &builder,
         const std::vector<TensorId> &args) -> TensorId {
        return builder->aiOnnxOpset10().sub(args);
      });

  ConstExprTest_Elementwise_Test<int64_t>(
      {4, 12},
      {2, 3},
      {8, 36},
      [](std::unique_ptr<popart::Builder> &builder,
         const std::vector<TensorId> &args) -> TensorId {
        return builder->aiOnnxOpset10().mul(args);
      });

  ConstExprTest_Elementwise_Test<float16_t>(
      {4., 12},
      {0.5, 1.5},
      {2.0, 18},
      [](std::unique_ptr<popart::Builder> &builder,
         const std::vector<TensorId> &args) -> TensorId {
        return builder->aiOnnxOpset10().mul(args);
      });

  ConstExprTest_Elementwise_Test<int64_t>(
      {4, -12},
      {-3, 8},
      {1, -4},
      [](std::unique_ptr<popart::Builder> &builder,
         const std::vector<TensorId> &args) -> TensorId {
        return builder->aiGraphcoreOpset1().fmod(args);
      });
}


#include <gtest/gtest.h>

#include "scheme/objective/hash/XformMap.hh"
#include "scheme/numeric/rand_xform.hh"
#include <Eigen/Geometry>

#include <sparsehash/dense_hash_set>

#include <random>
#include "scheme/util/Timer.hh"

#include <fstream>

namespace scheme { namespace objective { namespace hash { namespace xmtest {

using std::cout;
using std::endl;

typedef Eigen::Transform<double,3,Eigen::AffineCompact> Xform;
// typedef Eigen::Affine3d Xform;

TEST( XformMap, stores_correctly ){
	int NSAMP = 100000;

	std::mt19937 rng((unsigned int)time(0) + 296720384);
	std::uniform_real_distribution<> runif;

	XformMap< Xform, double> xmap( 0.5, 10.0 );
	std::vector< std::pair<Xform,double> > dat;
	for(int i = 0; i < NSAMP; ++i){
		Xform x;
		numeric::rand_xform( rng, x, 256.0 );
		double val = runif(rng);
		xmap.insert(x,val);
		dat.push_back( std::make_pair(x,val) );
	}

	XformMap< Xform, double > const & xmap_test( xmap );

	util::Timer<> t;

	for(int i = 0; i < dat.size(); ++i){
		Xform const & x = dat[i].first;
		double const v = dat[i].second;
		EXPECT_EQ( xmap_test[x], v );
		// cout << x.translation().transpose() << " " << v << endl;
	}
	cout << "XformMap " << NSAMP << " lookup rate: " << (double)NSAMP / t.elapsed() << " /sec ";

	// { // no way to check if stream in binary!
	// 	std::cout << "following failure message is expected" << std::endl;
	// 	std::ofstream out("test.sxm" );// , std::ios::binary );
	// 	ASSERT_FALSE( xmap.save( out, "foo" ) );
	// 	out.close();
	// }
	std::ofstream out("test.sxm" , std::ios::binary );
	ASSERT_TRUE( xmap.save( out, "foo" ) );
	out.close();

	XformMap< Xform, double > xmap_loaded;
	std::ifstream in( "test.sxm"  , std::ios::binary );
	ASSERT_TRUE( xmap_loaded.load( in ) );
	in.close();

	ASSERT_EQ( xmap.cart_resl_, xmap_loaded.cart_resl_ );
	ASSERT_EQ( xmap.ang_resl_, xmap_loaded.ang_resl_ );
	for(int i = 0; i < dat.size(); ++i){
		Xform const & x = dat[i].first;
		double const v = dat[i].second;
		ASSERT_EQ( xmap.hasher_.get_key(x) , xmap_loaded.hasher_.get_key(x) );
		ASSERT_EQ( xmap_loaded[x], v );
		// cout << x.translation().transpose() << " " << v << endl;
	}

}

double get_ident_lever_dis( Xform x, double lever_dis ){
	util::SimpleArray<7,double> x_lever_coord;
	x_lever_coord[0] = x.translation()[0];
	x_lever_coord[1] = x.translation()[1];
	x_lever_coord[2] = x.translation()[2];
	Eigen::Matrix<double,3,3> rot;
	get_transform_rotation( x, rot );
	Eigen::Quaternion<double> q(rot);
	bool neg = q.w()<0.0;
	x_lever_coord[3] = ( neg? -q.w() : q.w() ) * 2.0 * lever_dis;
	x_lever_coord[4] = ( neg? -q.x() : q.x() ) * 2.0 * lever_dis;
	x_lever_coord[5] = ( neg? -q.y() : q.y() ) * 2.0 * lever_dis;
	x_lever_coord[6] = ( neg? -q.z() : q.z() ) * 2.0 * lever_dis;
	util::SimpleArray<7,double> ident(0,0,0,lever_dis*2.0,0,0,0);
	return (x_lever_coord-ident).norm();
}

TEST( XformMap, insert_sphere ){
	int NSAMP2 = 10000;

	// typedef XformMap< Xform, double, 0 > XMap;
	typedef XformMap< Xform, double> XMap;
	std::mt19937 rng((unsigned int)time(0) + 3457820);
	std::uniform_real_distribution<> runif;
	Xform x;
	double cart_resl = 1.0;
	double lever = 3.0;
	double ang_resl = cart_resl/lever*180.0/M_PI;
	XMap xmap( 1.00, ang_resl );
	double rad = 3.0;
	cout << "cart_resl " << cart_resl << " ang_resl " << ang_resl << " lever " << lever << " sphere rad " << rad << endl;
	double angrad = rad/lever*180.0/M_PI;
	double quatrad = numeric::deg2quat(angrad);
	numeric::rand_xform( rng, x, 256.0 );
	XformHashNeighbors< XMap::Hasher > nbcache( rad, angrad, xmap.hasher_, 500.0 );
	int nbitercount = xmap.insert_sphere( x, rad, lever, 12345.0, nbcache );
	cout << nbitercount << " " << xmap.count(12345.0) << " " << xmap.size()-(float)xmap.count(0)
	     << " " << (float)xmap.count(0) / xmap.size() << " " << xmap.map_.size() << endl;
	int n_cart_fail=0, n_rot_fail=0, n_both_fail=0;
	int n_lever_false_pos=0, n_lever_false_neg=0, n_within=0, n_without=0;
	for(int i = 0; i < NSAMP2; ++i){
		Xform p;
		numeric::rand_xform_quat(rng, p, rad, 0.0 );
		if( xmap[ x*p ] != 12345.0 ) ++n_cart_fail;

		numeric::rand_xform_quat(rng, p, 0.0, quatrad );
		if( xmap[ x*p ] != 12345.0 ) ++n_rot_fail;

		double split1 = runif(rng);
		double split2 = sqrt( 1.0 - split1*split1 );
		numeric::rand_xform_quat(rng, p, split1*rad, split2*quatrad );
		if( xmap[ x*p ] != 12345.0 ) ++n_both_fail;

		numeric::rand_xform_quat(rng, p, 1.5*split1*rad, 1.5*split2*quatrad );
		// numeric::rand_xform_quat(rng, p, rad, quatrad );
		if( get_ident_lever_dis(p,lever) < rad ){
			++n_within;
			if( xmap[ x*p ] != 12345.0 ) ++n_lever_false_neg;
		} else {
			++n_without;
			if( xmap[ x*p ] == 12345.0 ) ++n_lever_false_pos;
		}

	}
	cout << "CART FAIL FRAC " << (float)n_cart_fail/NSAMP2 << endl;
	cout << "ROT  FAIL FRAC " << (float)n_rot_fail/NSAMP2  << endl;
	cout << "BOTH FAIL FRAC " << (float)n_both_fail/NSAMP2 << endl;

	cout << "FALSE POS " << (float)n_lever_false_pos/n_without << endl;
	cout << "FALSE NEG " << (float)n_lever_false_neg/n_within
	      << ",  FRAC " << (float)n_within/NSAMP2 << endl;

	ASSERT_LT( (float)n_cart_fail/NSAMP2, 0.03 );
	ASSERT_LT( (float)n_rot_fail/NSAMP2, 0.03 );
	ASSERT_LT( (float)n_both_fail/NSAMP2, 0.01 );
	ASSERT_LT( (float)n_lever_false_pos/n_without, 0.30 );
	ASSERT_LT( (float)n_lever_false_neg/n_within , 0.015 );

}

TEST( XformMap, test_bt24_bcc6 ){
	typedef Eigen::Transform<double,3,Eigen::AffineCompact> EigenXform;
	typedef scheme::objective::hash::XformMap< EigenXform, double, XformHash_bt24_BCC6 > XMap;

	XMap xmap( 1.0, 10.0 );

}

TEST( XformMap, DISABLED_test_float_double ){
	typedef Eigen::Transform<double,3,Eigen::AffineCompact> EigenXformD;
	typedef scheme::objective::hash::XformMap< EigenXformD, double, XformHash_bt24_BCC6 > XMapD;
	typedef Eigen::Transform<float,3,Eigen::AffineCompact> EigenXformF;
	typedef scheme::objective::hash::XformMap< EigenXformF, double, XformHash_bt24_BCC6 > XMapF;

	ASSERT_TRUE( false );

}

}}}}


#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>

#include <CGAL/Polyhedron_3.h>
#include <CGAL/boost/graph/properties_Polyhedron_3.h>
#include <CGAL/IO/Polyhedron_iostream.h>

#include <CGAL/Surface_mesh.h>
#include <CGAL/boost/graph/properties_Surface_mesh.h>

#include <CGAL/Polygon_mesh_processing/measure.h>
#include <CGAL/Polygon_mesh_processing/bbox.h>
#include <CGAL/Polygon_mesh_processing/stitch_borders.h>

#include <CGAL/Bbox_3.h>

#include <iostream>
#include <fstream>
#include <iterator>
#include <list>

#include <boost/foreach.hpp>

typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::Point_3 Point;

typedef CGAL::Polyhedron_3<K>       Polyhedron;
typedef CGAL::Surface_mesh<Point>   Surface_mesh;

namespace PMP = CGAL::Polygon_mesh_processing;

template<typename Mesh>
void test(const Mesh& pmesh)
{
  typedef typename boost::graph_traits<Mesh>::halfedge_descriptor halfedge_descriptor;
  typedef typename boost::graph_traits<Mesh>::face_descriptor     face_descriptor;

  halfedge_descriptor border_he;
  BOOST_FOREACH(halfedge_descriptor h, halfedges(pmesh))
  {
    if (is_border(h, pmesh))
    {
      border_he = h;
      break;
    }
  }
  double border_l = PMP::face_border_length(border_he, pmesh);
  std::cout << "length of hole border = " << border_l << std::endl;

  face_descriptor valid_patch_face;
  unsigned int count = 0;
  BOOST_FOREACH(halfedge_descriptor h, halfedges(pmesh))
  {
    if (is_border(h, pmesh) || is_border(opposite(h, pmesh), pmesh))
      continue;
    else
    {
      double face_area = PMP::face_area(face(h, pmesh), pmesh);
      std::cout << "face area = " << face_area << std::endl;

      if(++count == 20)
      {
        valid_patch_face = face(h, pmesh);
        break;
      }
    }
  }

  std::list<face_descriptor> patch;
  patch.push_back(valid_patch_face);
  while (patch.size() < 5)
  {
    face_descriptor f = patch.front();
    patch.pop_front();
    BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(halfedge(f, pmesh), pmesh))
    {
      if (boost::graph_traits<Mesh>::null_halfedge() != opposite(h, pmesh))
        patch.push_back(face(opposite(h, pmesh), pmesh));
      patch.push_back(f);
    }
    if (patch.front() == valid_patch_face)
      break;//back to starting point
  }

  double patch_area = PMP::area(patch, pmesh);
  std::cout << "patch area = " << patch_area << std::endl;

  double mesh_area = PMP::area(pmesh);
  std::cout << "mesh area = " << mesh_area << std::endl;

  double mesh_area_np = PMP::area(pmesh,
    PMP::parameters::geom_traits(K()));
  std::cout << "mesh area (NP) = " << mesh_area_np << std::endl;

  CGAL::Bbox_3 bb = PMP::bbox_3(pmesh);
  std::cout << "bbox x[" << bb.xmin() << "; " << bb.xmax() << "]" << std::endl;
  std::cout << "     y[" << bb.ymin() << "; " << bb.ymax() << "]" << std::endl;
  std::cout << "     z[" << bb.zmin() << "; " << bb.zmax() << "]" << std::endl;

}

void test_polyhedron(const char* filename)
{
  //run test for a Polyhedron
  Polyhedron poly; // file should contain oriented polyhedron
  std::ifstream input(filename);

  if (!input || !(input >> poly))
  {
    std::cerr << "Error: cannot read Polyhedron : " << filename << "\n";
    assert(!poly.empty());
    assert(false);
    return;
  }

  test(poly);
}

void test_closed_surface_mesh(const char* filename)
{
  Surface_mesh sm;
  std::ifstream input(filename);

  if (!input || !(input >> sm))
  {
    std::cerr << "Error: cannot read Surface mesh : " << filename << "\n";
    assert(sm.number_of_vertices() > 0);
    assert(false);
    return;
  }

  test(sm);

  double vol = PMP::volume(sm);
  std::cout << "volume = " << vol << std::endl;
}

int main(int argc, char* argv[])
{
  const char* filename_polyhedron =
    (argc > 1) ? argv[1] : "data/mech-holes-shark.off";
  test_polyhedron(filename_polyhedron);

  const char* filename_surface_mesh =
    (argc > 1) ? argv[1] : "data/elephant.off";
  test_closed_surface_mesh(filename_surface_mesh);

  std::cerr << "All done." << std::endl;
  return 0;
}


#include <gtest/gtest.h>
#include <Eigen/Core>
#include <sophus/se3.hpp>

#include <determinant4x4.h>
#include <quaternion_product.h>
#include <random>

/*
  Test quaternion product utlity module
*/
TEST(DiospyrosTest, crossProductTest) {
    Eigen::Vector<float, 3> lhs(1, 2, 3);
    Eigen::Vector<float, 3> rhs(-1, 4, 6);

    Eigen::Vector<float, 3> result = crossProduct(lhs, rhs);

    auto expected = lhs.cross(rhs);

    EXPECT_EQ(expected(0), result(0));
    EXPECT_EQ(expected(1), result(1));
    EXPECT_EQ(expected(2), result(2));
}

TEST(DiospyrosTest, quaternionTest) {
    std::mt19937 gen;
    std::uniform_real_distribution<> dist;

    gen = std::mt19937(0);
    dist = std::uniform_real_distribution<>(-1.0f, 1.0f);

    ::Eigen::Quaternion<float> aq = ::Eigen::Quaternion<float>::UnitRandom();
    ::Eigen::Vector3f at(dist(gen), dist(gen), dist(gen));
    Sophus::SE3<float> a(aq, at);

    ::Eigen::Quaternion<float> bq = ::Eigen::Quaternion<float>::UnitRandom();
    ::Eigen::Vector3f bt(dist(gen), dist(gen), dist(gen));
    Sophus::SE3<float> b(bq, bt);

    Sophus::SE3<float> c = a * b;

    Eigen::Vector<float, 4> aq_(aq.x(), aq.y(), aq.z(), aq.w());
    Eigen::Vector<float, 3> at_(at(0), at(1), at(2));
    SE3T a_ = {aq_, at_};

    Eigen::Vector<float, 4> bq_(bq.x(), bq.y(), bq.z(), bq.w());
    Eigen::Vector<float, 3> bt_(bt(0), bt(1), bt(2));
    SE3T b_ = {bq_, bt_};

    SE3T c_ = quaternionProduct(a_, b_);

    auto eq = c.unit_quaternion();
    auto et = c.translation();

    auto rq = c_.quaternion;
    auto rt = c_.translation;

    EXPECT_NEAR(eq.w(), rq(3), 1E-5);
    EXPECT_NEAR(eq.x(), rq(0), 1E-5);
    EXPECT_NEAR(eq.y(), rq(1), 1E-5);
    EXPECT_NEAR(eq.z(), rq(2), 1E-5);

    EXPECT_NEAR(et(0), rt(0), 1E-5);
    EXPECT_NEAR(et(1), rt(1), 1E-5);
    EXPECT_NEAR(et(2), rt(2), 1E-5);
}


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

#include "lagrange.h"
#include "dof-permutations.h"
#include "lattice.h"
#include "libtab.h"
#include "polyset.h"
#include <Eigen/Dense>
#include <iostream>
#include <numeric>

using namespace libtab;

//----------------------------------------------------------------------------
FiniteElement libtab::create_lagrange(cell::type celltype, int degree,
                                      const std::string& name)
{
  if (celltype == cell::type::point)
    throw std::runtime_error("Invalid celltype");

  const int ndofs = polyset::dim(celltype, degree);

  const std::vector<std::vector<std::vector<int>>> topology
      = cell::topology(celltype);
  std::vector<std::vector<int>> entity_dofs(topology.size());

  // Create points at nodes, ordered by topology (vertices first)
  Eigen::ArrayXXd pt(ndofs, topology.size() - 1);
  if (degree == 0)
  {
    pt = lattice::create(celltype, 0, lattice::type::equispaced, true);
    for (std::size_t i = 0; i < entity_dofs.size(); ++i)
      entity_dofs[i].resize(topology[i].size(), 0);
    entity_dofs[topology.size() - 1][0] = 1;
  }
  else
  {
    int c = 0;
    for (std::size_t dim = 0; dim < topology.size(); ++dim)
    {
      for (std::size_t i = 0; i < topology[dim].size(); ++i)
      {
        const Eigen::ArrayXXd entity_geom
            = cell::sub_entity_geometry(celltype, dim, i);

        if (dim == 0)
        {
          pt.row(c++) = entity_geom.row(0);
          entity_dofs[0].push_back(1);
        }
        else if (dim == topology.size() - 1)
        {
          const Eigen::ArrayXXd lattice = lattice::create(
              celltype, degree, lattice::type::equispaced, false);
          for (int j = 0; j < lattice.rows(); ++j)
            pt.row(c++) = lattice.row(j);
          entity_dofs[dim].push_back(lattice.rows());
        }
        else
        {
          cell::type ct = cell::sub_entity_type(celltype, dim, i);
          const Eigen::ArrayXXd lattice
              = lattice::create(ct, degree, lattice::type::equispaced, false);
          entity_dofs[dim].push_back(lattice.rows());
          for (int j = 0; j < lattice.rows(); ++j)
          {
            pt.row(c) = entity_geom.row(0);
            for (int k = 0; k < lattice.cols(); ++k)
            {
              pt.row(c) += (entity_geom.row(k + 1) - entity_geom.row(0))
                           * lattice(j, k);
            }
            ++c;
          }
        }
      }
    }
  }

  int perm_count = 0;
  for (std::size_t i = 1; i < topology.size() - 1; ++i)
    perm_count += topology[i].size() * i;

  std::vector<Eigen::MatrixXd> base_permutations(
      perm_count, Eigen::MatrixXd::Identity(ndofs, ndofs));
  if (celltype == cell::type::triangle)
  {
    Eigen::ArrayXi edge_ref = dofperms::interval_reflection(degree - 1);
    for (int edge = 0; edge < 3; ++edge)
    {
      const int start = 3 + edge_ref.size() * edge;
      for (int i = 0; i < edge_ref.size(); ++i)
      {
        base_permutations[edge](start + i, start + i) = 0;
        base_permutations[edge](start + i, start + edge_ref[i]) = 1;
      }
    }
  }
  else if (celltype == cell::type::tetrahedron)
  {
    Eigen::ArrayXi edge_ref = dofperms::interval_reflection(degree - 1);
    for (int edge = 0; edge < 6; ++edge)
    {
      const int start = 4 + edge_ref.size() * edge;
      for (int i = 0; i < edge_ref.size(); ++i)
      {
        base_permutations[edge](start + i, start + i) = 0;
        base_permutations[edge](start + i, start + edge_ref[i]) = 1;
      }
    }
    Eigen::ArrayXi face_ref = dofperms::triangle_reflection(degree - 2);
    Eigen::ArrayXi face_rot = dofperms::triangle_rotation(degree - 2);
    for (int face = 0; face < 4; ++face)
    {
      const int start = 4 + edge_ref.size() * 6 + face_ref.size() * face;
      for (int i = 0; i < face_rot.size(); ++i)
      {
        base_permutations[6 + 2 * face](start + i, start + i) = 0;
        base_permutations[6 + 2 * face](start + i, start + face_rot[i]) = 1;
        base_permutations[6 + 2 * face + 1](start + i, start + i) = 0;
        base_permutations[6 + 2 * face + 1](start + i, start + face_ref[i]) = 1;
      }
    }
  }

  // Point evaluation of basis
  Eigen::MatrixXd dualmat = polyset::tabulate(celltype, degree, 0, pt)[0];
  Eigen::MatrixXd coeffs = compute_expansion_coefficients(
      Eigen::MatrixXd::Identity(ndofs, ndofs), dualmat);

  return FiniteElement(name, celltype, degree, {1}, coeffs, entity_dofs,
                       base_permutations);
}
//-----------------------------------------------------------------------------
FiniteElement libtab::create_dlagrange(cell::type celltype, int degree,
                                       const std::string& name)
{
  if (celltype != cell::type::interval and celltype != cell::type::triangle
      and celltype != cell::type::tetrahedron)
    throw std::runtime_error("Invalid celltype");

  // Only tabulate for scalar. Vector spaces can easily be built from
  // the scalar space.

  const int ndofs = polyset::dim(celltype, degree);

  std::vector<std::vector<std::vector<int>>> topology
      = cell::topology(celltype);
  std::vector<std::vector<int>> entity_dofs(topology.size());
  for (std::size_t i = 0; i < topology.size(); ++i)
    entity_dofs[i].resize(topology[i].size(), 0);
  entity_dofs[topology.size() - 1][0] = ndofs;

  Eigen::ArrayXXd geometry = cell::geometry(celltype);
  const Eigen::ArrayXXd lattice
      = lattice::create(celltype, degree, lattice::type::equispaced, true);

  // Create points at nodes, ordered by topology (vertices first)
  Eigen::ArrayXXd pt(ndofs, topology.size() - 1);
  for (int j = 0; j < lattice.rows(); ++j)
  {
    pt.row(j) = geometry.row(0);
    for (int k = 0; k < geometry.rows() - 1; ++k)
      pt.row(j) += (geometry.row(k + 1) - geometry.row(0)) * lattice(j, k);
  }

  // Point evaluation of basis
  Eigen::MatrixXd dualmat = polyset::tabulate(celltype, degree, 0, pt)[0];

  Eigen::MatrixXd coeffs = compute_expansion_coefficients(
      Eigen::MatrixXd::Identity(ndofs, ndofs), dualmat);

  int perm_count = 0;
  for (std::size_t i = 1; i < topology.size() - 1; ++i)
    perm_count += topology[i].size() * i;

  std::vector<Eigen::MatrixXd> base_permutations(
      perm_count, Eigen::MatrixXd::Identity(ndofs, ndofs));

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


#include <boost/algorithm/cxx11/iota.hpp>
#include <boost/algorithm/cxx11/is_sorted.hpp>
#include <boost/algorithm/cxx11/copy_if.hpp>
#include <vector>
#include <iterator>
#include <iostream>

using namespace boost::algorithm;

int main()
{
  std::vector<int> v;
  iota_n(std::back_inserter(v), 10, 5);
  std::cout.setf(std::ios::boolalpha);
  std::cout << is_increasing(v) << '\n';
  std::ostream_iterator<int> out{std::cout, ","};
  copy_until(v, out, [](int i){ return i > 12; });
}


/* test_uniform_smallint_distribution.cpp
 *
 * Copyright Steven Watanabe 2011
 * 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)
 *
 * $Id: test_uniform_smallint_distribution.cpp 71018 2011-04-05 21:27:52Z steven_watanabe $
 *
 */

#include <boost/random/uniform_smallint.hpp>
#include <limits>

#define BOOST_RANDOM_DISTRIBUTION boost::random::uniform_smallint<>
#define BOOST_RANDOM_ARG1 a
#define BOOST_RANDOM_ARG2 b
#define BOOST_RANDOM_ARG1_DEFAULT 0
#define BOOST_RANDOM_ARG2_DEFAULT 9
#define BOOST_RANDOM_ARG1_VALUE 5
#define BOOST_RANDOM_ARG2_VALUE 250

#define BOOST_RANDOM_DIST0_MIN 0
#define BOOST_RANDOM_DIST0_MAX 9
#define BOOST_RANDOM_DIST1_MIN 5
#define BOOST_RANDOM_DIST1_MAX 9
#define BOOST_RANDOM_DIST2_MIN 5
#define BOOST_RANDOM_DIST2_MAX 250

#define BOOST_RANDOM_TEST1_PARAMS (0, 9)
#define BOOST_RANDOM_TEST1_MIN 0
#define BOOST_RANDOM_TEST1_MAX 9

#define BOOST_RANDOM_TEST2_PARAMS (10, 19)
#define BOOST_RANDOM_TEST2_MIN 10
#define BOOST_RANDOM_TEST2_MAX 19

#include "test_distribution.ipp"


/*
    This file is part of Mitsuba, a physically based rendering system.

    Copyright (c) 2007-2014 by Wenzel Jakob and others.

    Mitsuba is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License Version 3
    as published by the Free Software Foundation.

    Mitsuba is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <mitsuba/core/plugin.h>
#include <mitsuba/core/statistics.h>
#include <mitsuba/core/chisquare.h>
#include <mitsuba/core/fresolver.h>
#include <mitsuba/render/testcase.h>
#include <boost/bind.hpp>
#include "../bsdfs/microfacet.h"

/* Statistical significance level of the test. Set to
   1/4 percent by default -- we want there to be strong
   evidence of an implementaiton error before failing
   a test case */
#define SIGNIFICANCE_LEVEL 0.0025f

/* Relative bound on what is still accepted as roundoff
   error -- be quite tolerant */
#if defined(SINGLE_PRECISION)
	#define ERROR_REQ 1e-2f
#else
	#define ERROR_REQ 1e-5
#endif

MTS_NAMESPACE_BEGIN

class TestChiSquare : public TestCase {
public:
	MTS_BEGIN_TESTCASE()
	MTS_DECLARE_TEST(test01_Microfacet)
	MTS_DECLARE_TEST(test02_MicrofacetVisible)
	MTS_END_TESTCASE()

	class MicrofacetAdapter {
	public:
		MicrofacetAdapter(Sampler *sampler, const MicrofacetDistribution &distr, const Vector &wi = Vector(0.0f)) : m_sampler(sampler), m_distr(distr), m_wi(wi) { }

		boost::tuple<Vector, Float, EMeasure> generateSample() {
			Float pdf;

			if (m_wi.lengthSquared() == 0) {
				Normal m = m_distr.sampleAll(m_sampler->next2D(), pdf);
				Float pdf_ref = m_distr.pdfAll(m);

				SAssert(std::isfinite(pdf) && pdf > 0);
				SAssert(std::isfinite(pdf_ref) && pdf_ref > 0);
				SAssert(std::isfinite(m.x) && std::isfinite(m.y) && std::isfinite(m.z));
				SAssert(std::abs(m.length() - 1) < 1e-4f);
				SAssert(std::abs((pdf-pdf_ref)/pdf_ref) < 1e-4f);
				return boost::make_tuple(m, 1.0f, ESolidAngle);
			} else {
				Normal m = m_distr.sampleVisible(m_wi, m_sampler->next2D());
				SAssert(std::isfinite(m.x) && std::isfinite(m.y) && std::isfinite(m.z));
				SAssert(std::abs(m.length() - 1) < 1e-4f);
				return boost::make_tuple(m, 1.0f, ESolidAngle);
			}
		}

		Float pdf(const Vector &d, EMeasure measure) const {
			if (measure != ESolidAngle)
				return 0.0f;

			Float pdf = m_wi.lengthSquared() == 0 ? m_distr.pdfAll(d)
				: m_distr.pdfVisible(m_wi, d);
			SAssert(std::isfinite(pdf) && pdf >= 0);

			return pdf;
		}

	private:
		ref<Sampler> m_sampler;
		MicrofacetDistribution m_distr;
		Vector m_wi;
	};

	void test01_Microfacet() {
		int thetaBins = 20;
		std::vector<MicrofacetDistribution> distrs;

		distrs.push_back(MicrofacetDistribution(MicrofacetDistribution::EBeckmann, 0.5f));
		distrs.push_back(MicrofacetDistribution(MicrofacetDistribution::EBeckmann, 0.5f, 0.3f));
		distrs.push_back(MicrofacetDistribution(MicrofacetDistribution::EGGX, 0.5f));
		distrs.push_back(MicrofacetDistribution(MicrofacetDistribution::EGGX, 0.5f, 0.3f));
		distrs.push_back(MicrofacetDistribution(MicrofacetDistribution::EPhong, 0.5f));
		distrs.push_back(MicrofacetDistribution(MicrofacetDistribution::EPhong, 0.5f, 0.3f));

		ref<Sampler> sampler = static_cast<Sampler *> (PluginManager::getInstance()->
				createObject(MTS_CLASS(Sampler), Properties("independent")));
		ref<ChiSquare> chiSqr = new ChiSquare(thetaBins, 2*thetaBins, (int) distrs.size());
		chiSqr->setLogLevel(EDebug);

		for (size_t i=0; i<distrs.size(); ++i) {
			Log(EInfo, "Testing %s", distrs[i].toString().c_str());
			// Initialize the tables used by the chi-square test
			MicrofacetAdapter adapter(sampler, distrs[i]);
			chiSqr->fill(
				boost::bind(&MicrofacetAdapter::generateSample, &adapter),
				boost::bind(&MicrofacetAdapter::pdf, &adapter, _1, _2)
			);

			// (the following assumes that the distribution has 1 parameter, e.g. exponent value)
			ChiSquare::ETestResult result = chiSqr->runTest(SIGNIFICANCE_LEVEL);
			if (result == ChiSquare::EReject) {
				std::string filename = formatString("failure_%i.m", (int) i);
				chiSqr->dumpTables(filename);
				failAndContinue(formatString("Uh oh, the chi-square test indicates a potential "
					"issue. Dumped the contingency tables to '%s' for user analysis",
					filename.c_str()));
			} else {
				//chiSqr->dumpTables(formatString("success_%i.m", (int) i));
				succeed();
			}
		}
	}

	void test02_MicrofacetVisible() {
		int thetaBins = 10;
		std::vector<std::pair<MicrofacetDistribution, Vector> > distrs;

		ref<Sampler> sampler = static_cast<Sampler *> (PluginManager::getInstance()->
				createObject(MTS_CLASS(Sampler), Properties("independent")));
		for (int i=0; i<10; ++i) {
			Vector wi = warp::squareToUniformHemisphere(sampler->next2D());
			distrs.push_back(std::make_pair(MicrofacetDistribution(MicrofacetDistribution::EBeckmann, 0.3f), wi));
			distrs.push_back(std::make_pair(MicrofacetDistribution(MicrofacetDistribution::EBeckmann, 0.5f, 0.3f), wi));
			distrs.push_back(std::make_pair(MicrofacetDistribution(MicrofacetDistribution::EGGX, 0.1f), wi));
			distrs.push_back(std::make_pair(MicrofacetDistribution(MicrofacetDistribution::EGGX, 0.2f, 0.3f), wi));
		}

		ref<ChiSquare> chiSqr = new ChiSquare(thetaBins, 2*thetaBins, (int) distrs.size());
		chiSqr->setLogLevel(EDebug);

		for (size_t i=0; i<distrs.size(); ++i) {
			Log(EInfo, "Testing %s (wi=%s)", distrs[i].first.toString().c_str(), distrs[i].second.toString().c_str());
			// Initialize the tables used by the chi-square test
			MicrofacetAdapter adapter(sampler, distrs[i].first, distrs[i].second);
			chiSqr->fill(
				boost::bind(&MicrofacetAdapter::generateSample, &adapter),
				boost::bind(&MicrofacetAdapter::pdf, &adapter, _1, _2)
			);

			// (the following assumes that the distribution has 1 parameter, e.g. exponent value)
			ChiSquare::ETestResult result = chiSqr->runTest(SIGNIFICANCE_LEVEL);
			if (result == ChiSquare::EReject) {
				std::string filename = formatString("failure_%i.m", (int) i);
				chiSqr->dumpTables(filename);
				failAndContinue(formatString("Uh oh, the chi-square test indicates a potential "
					"issue. Dumped the contingency tables to '%s' for user analysis",
					filename.c_str()));
			} else {
				//chiSqr->dumpTables(formatString("success_%i.m", (int) i));
				succeed();
			}
		}
	}
};

MTS_EXPORT_TESTCASE(TestChiSquare, "Chi-square test for microfacet sampling")
MTS_NAMESPACE_END


#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <random>
#include <cmath>
#include <fstream>
#include <armadillo>

using namespace std;
using namespace cv;

#include "input.h"

void findEllipses(vector<Mat> channels, int k, Mat &sc, Mat &res, vector<RotatedRect> &minEllipse) {
    vector<Vec4i> hierarchy;
    vector<vector<Point>> contours;

    // Blur the image before binarization
    blur(channels[k], sc, Size(3, 3));

    //Mat img_copy(sc.rows, sc.cols, CV_8UC3, Scalar(255, 255, 255));

    // Binarize it and find contours
    threshold(sc, sc, 105, 255, THRESH_BINARY);
    findContours(sc, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));

    vector<RotatedRect> minRect_red(contours.size());

    for (int i = 0; i < contours.size(); i++) {
        minRect_red[i] = minAreaRect(Mat(contours[i]));
        //printf("contour %d, size %ld, %ld x %ld\n", i, contours[i].size(), minRect_red[i].size.width, minRect_red[i].size.height);
        if (contours[i].size() > 5 && minRect_red[i].size.width > 60.0){
            minEllipse.push_back(fitEllipse(Mat(contours[i])));
            //{ minEllipse_red[i] = fitEllipse( Mat(contours[i]) ); }
        }
    }

    // Draw the resulting ellipses
    for (int i = 0; i < minEllipse.size(); i++) {
        Scalar color = Scalar(rand() % 255, rand() % 255, rand() % 255);
        // contour
        drawContours(res, contours, i, color, 1, 8, vector<Vec4i>(), 0, Point());
        // ellipse
        ellipse(res, minEllipse[i], color, 2, 8);
        // rotated rectangle
        Point2f rect_points[4];
        minRect_red[i].points(rect_points);
        for (int j = 0; j < 4; j++)
            line(res, rect_points[j], rect_points[(j + 1) % 4], color, 1, 8);
    }
}

void generatePattern() {
    // 300 dpi (print) = 2480 X 3508 pixels (This is "A4" as I know it, i.e. "210mm X 297mm @ 300 dpi")
    // 600 dpi =
    //unsigned int height = 1123, width = 1587;
    unsigned int height = 2480 - 100, width = 3508 - 100;
    Mat image(height, width, CV_8UC3, Scalar(255, 255, 255));
    int x = 90, y = 55, radius = 30;
    while (x + 15 < width){
        while (y + 15 < height) {
            circle(image, Point(x, y), radius, Scalar(0, 0, 0), CV_FILLED, 4);
            y += 90;
        }
        y = 55;
        x += 90;

    }

    imwrite("picture.png", image);
}

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

    // Parse input options
    InputParser input(argc, argv);
    if (input.cmdOptionExists("-g")){
        // Generate image with a test pattern to photo
        cout << "Generating test pattern" << endl;
        generatePattern();
        return 0;
    }

    const std::string &filename = input.getCmdOption("-f");
    if (!filename.empty()){
        // Source image file name
    }

    // Load photo of the template
    Mat img = imread("src4.png", 1);

    // Create images for the channels
    Mat img_resR(img.rows, img.cols, CV_8UC3, Scalar(255, 255, 255));
    Mat img_resG(img.rows, img.cols, CV_8UC3, Scalar(255, 255, 255));
    Mat img_resB(img.rows, img.cols, CV_8UC3, Scalar(255, 255, 255));

    // Create images for the offsets
    Mat img_offs1(img.rows, img.cols, CV_8UC3, Scalar(255, 255, 255));
    Mat img_offs2(img.rows, img.cols, CV_8UC3, Scalar(255, 255, 255));

    // Split source image into three channels
    vector<Mat> channels;
    split(img, channels);

    // Find ellipses centers and save them as red,blue and green.jpg files
    Mat red, green, blue;
    vector<RotatedRect> minEllipse[3];

    findEllipses(channels, 2, red, img_resR, minEllipse[2]);
    imwrite("red.jpg", img_resR);

    findEllipses(channels, 0, blue, img_resB, minEllipse[0]);
    imwrite("blue.jpg", img_resB);

    findEllipses(channels, 1, green, img_resG, minEllipse[1]);
    imwrite("green.jpg", img_resG);

    // Take G channel as the main one and calc offsets of R and B, draw and save them
    for (int i = 0; i < minEllipse[1].size(); i++) {
        for (int j = 0; j < minEllipse[2].size(); j++) {
            if (abs(minEllipse[1][i].center.x - minEllipse[2][j].center.x) < 10 && abs(minEllipse[1][i].center.y - minEllipse[2][j].center.y) < 10) {
                swap(minEllipse[2][j], minEllipse[2][i]);

                break;
            }
        }
        Point distort;
        distort = (minEllipse[2][i].center - minEllipse[1][i].center) * 15;
        line(img_offs1, (Point)minEllipse[1][i].center, (Point)minEllipse[2][i].center + distort, Scalar(0, 0, 255), 3);
        circle(img_offs1, (Point)minEllipse[1][i].center, 1, Scalar(0, 255, 0), CV_FILLED, 15);
        //        cout << "Rect[" << i << "].center_green = " << minEllipse_green[i].center << "; Rect[" << i << "].center_red = " << minEllipse_red[i].center << endl;
    }

    imwrite("red_ffs.jpg", img_offs1);

    for (int i = 0; i < minEllipse[1].size(); i++) {
        for (int j = 0; j < minEllipse[2].size(); j++) {
            if (abs(minEllipse[1][i].center.x - minEllipse[2][j].center.x) < 10 && abs(minEllipse[1][i].center.y - minEllipse[2][j].center.y) < 10 && i != j) {
                swap(minEllipse[2][j], minEllipse[2][i]);
                break;
            }
        }
        Point distort;
        distort = (minEllipse[2][i].center - minEllipse[1][i].center) * 15;
        line(img_offs2, (Point)minEllipse[1][i].center, (Point)minEllipse[2][i].center + distort, Scalar(255, 0, 0), 3);
        circle(img_offs2, (Point)minEllipse[1][i].center, 1, Scalar(0, 255, 0), CV_FILLED, 15);
        //        cout << "Rect[" << i << "].center_green = " << minEllipse_green[i].center << "; Rect[" << i << "].center_blue = " << minEllipse_blue[i].center << endl;
        //break;
    }

    imwrite("blue_ffs.jpg", img_offs2);

    //namedWindow("Display window", WINDOW_NORMAL);
    //imshow("Display window", img_copy);

    //namedWindow("imge", WINDOW_NORMAL);
    //imshow("imge", imge);

    waitKey(0);
    red.release();
    green.release();
    blue.release();

    return 0;
}