//***************************************************************************
//* Copyright (c) 2015 Saint Petersburg State University
//* Copyright (c) 2011-2014 Saint Petersburg Academic University
//* All Rights Reserved
//* See file LICENSE for details.
//***************************************************************************

/*
 * kmer_cluster.hpp
 *
 *  Created on: 16.07.2011
 *      Author: snikolenko
 */

#ifndef KMER_CLUSTER_HPP
#define KMER_CLUSTER_HPP

#include "hamcluster.hpp"
#include "kmer_data.hpp"

#include <string>
#include <vector>

#include <boost/numeric/ublas/fwd.hpp>

class KMerClustering {
public:
  KMerClustering(KMerData &data, unsigned nthreads, const std::string &workdir, bool debug) :
      data_(data), nthreads_(nthreads), workdir_(workdir), debug_(debug) { }

  void process(const std::string &Prefix);

private:
  KMerData &data_;
  unsigned nthreads_;
  std::string workdir_;
  bool debug_;

  struct Center {
    hammer::ExpandedSeq center_;
    size_t count_;
  };

  double ClusterBIC(const std::vector<Center> &centers,
                    const std::vector<size_t> &indices, const std::vector<hammer::ExpandedKMer> &kmers) const;

  /**
    * perform l-means clustering on the set of k-mers with initial centers being the l most frequent k-mers here
    * @param indices fill array centers with cluster centers; centers[k].count shows how many different kmers are in this cluster (used later)
    * @param centers fill array indices with ints from 0 to l that denote which kmers belong where
    * @return the resulting likelihood of this clustering
    */
  double lMeansClustering(unsigned l, const std::vector<hammer::ExpandedKMer> &kmers,
                          std::vector<size_t> & indices, std::vector<Center> & centers);

  size_t SubClusterSingle(const std::vector<size_t> & block, std::vector< std::vector<size_t> > & vec);

  std::string GetGoodKMersFname() const;
  std::string GetBadKMersFname() const;

  size_t ProcessCluster(const std::vector<size_t> &cur_class,
                        boost::numeric::ublas::matrix<uint64_t> &errs,
                        std::ofstream &ofs, std::ofstream &ofs_bad,
                        size_t &gsingl, size_t &tsingl, size_t &tcsingl, size_t &gcsingl,
                        size_t &tcls, size_t &gcls, size_t &tkmers, size_t &tncls);

private:
  DECL_LOGGER("Hamming Subclustering");
};

#endif // KMER_CLUSTER_HPP


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_MEMORY_FUNCTIONS_SIMD_SSE_SSE2_MAKE_HPP_INCLUDED
#define BOOST_SIMD_MEMORY_FUNCTIONS_SIMD_SSE_SSE2_MAKE_HPP_INCLUDED
#ifdef BOOST_SIMD_HAS_SSE2_SUPPORT

#include <boost/simd/memory/functions/make.hpp>
#include <boost/simd/preprocessor/make_helper.hpp>
#include <boost/dispatch/attributes.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT          ( make_
                                    , boost::simd::tag::sse2_
                                    , (A0)
                                    , ((target_ < simd_ < double_<A0>
                                                        , boost::simd::tag::sse_
                                                        >
                                                >
                                      ))
                                    )
  {
    BOOST_SIMD_MAKE_BODY(2) { return _mm_setr_pd(a0, a1); }
  };

  BOOST_DISPATCH_IMPLEMENT          ( make_
                                    , boost::simd::tag::sse2_
                                    , (A0)
                                    , ((target_ < simd_ < ints64_<A0>
                                                        , boost::simd::tag::sse_
                                                        >
                                                >
                                      ))
                                    )
  {
    BOOST_SIMD_MAKE_BODY(2)
    {
#ifdef BOOST_SIMD_ARCH_X86_64
      return _mm_set_epi64x(a1, a0);
#else
      return _mm_setr_epi32( (uint64_t(a0) & 0x00000000FFFFFFFFULL)
                           , (uint64_t(a0) & 0xFFFFFFFF00000000ULL) >> 32
                           , (uint64_t(a1) & 0x00000000FFFFFFFFULL)
                           , (uint64_t(a1) & 0xFFFFFFFF00000000ULL) >> 32
                           );
#endif
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( make_
                                    , boost::simd::tag::sse2_
                                    , (A0)
                                    , ((target_ < simd_ < single_<A0>
                                                        , boost::simd::tag::sse_
                                                        >
                                                >
                                      ))
                                    )
  {
    BOOST_SIMD_MAKE_BODY(4) { return _mm_setr_ps(a0, a1, a2, a3); }
  };

  BOOST_DISPATCH_IMPLEMENT          ( make_
                                    , boost::simd::tag::sse2_
                                    , (A0)
                                    , ((target_ < simd_ < ints32_<A0>
                                                        , boost::simd::tag::sse_
                                                        >
                                                >
                                      ))
                                    )
  {
    BOOST_SIMD_MAKE_BODY(4) { return _mm_setr_epi32(a0, a1, a2, a3); }
  };

  BOOST_DISPATCH_IMPLEMENT          ( make_
                                    , boost::simd::tag::sse2_
                                    , (A0)
                                    , ((target_ < simd_ < ints16_<A0>
                                                        , boost::simd::tag::sse_
                                                        >
                                                >
                                      ))
                                    )
  {
    BOOST_SIMD_MAKE_BODY(8)
    {
      return _mm_setr_epi16(a0, a1, a2, a3 , a4, a5, a6, a7 );
    }
  };

  BOOST_DISPATCH_IMPLEMENT         ( make_
                                    , boost::simd::tag::sse2_
                                    , (A0)
                                    , ((target_ < simd_ < ints8_<A0>
                                                        , boost::simd::tag::sse_
                                                        >
                                                >
                                      ))
                                    )
  {
    BOOST_SIMD_MAKE_BODY(16)
    {
      return _mm_setr_epi8 ( a0, a1 , a2,  a3 ,  a4,  a5,  a6,  a7
                           , a8, a9, a10, a11 , a12, a13, a14, a15 );
    }
  };
} } }

#endif
#endif


//==================================================================================================
/**
  Copyright 2016 Numscale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
**/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_X86_AVX2_SIMD_FUNCTION_IS_EQUAL_HPP_INCLUDED
#define BOOST_SIMD_ARCH_X86_AVX2_SIMD_FUNCTION_IS_EQUAL_HPP_INCLUDED

#include <boost/simd/detail/overload.hpp>
#include <boost/simd/meta/as_logical.hpp>

namespace boost { namespace simd { namespace ext
{
   namespace bd = boost::dispatch;
   namespace bs = boost::simd;

   BOOST_DISPATCH_OVERLOAD( is_equal_
                          , (typename A0)
                          , bs::avx2_
                          , bs::pack_<bd::ints64_<A0>, bs::avx_>
                          , bs::pack_<bd::ints64_<A0>, bs::avx_>
                          )
   {
      BOOST_FORCEINLINE
      bs::as_logical_t<A0> operator()(A0 const& a0, A0 const& a1) const BOOST_NOEXCEPT
      {
        return _mm256_cmpeq_epi64(a0,a1);
      }
   };

   BOOST_DISPATCH_OVERLOAD( is_equal_
                          , (typename A0)
                          , bs::avx2_
                          , bs::pack_<bd::ints32_<A0>, bs::avx_>
                          , bs::pack_<bd::ints32_<A0>, bs::avx_>
                          )
   {
      BOOST_FORCEINLINE
      bs::as_logical_t<A0> operator()(A0 const& a0, A0 const& a1) const BOOST_NOEXCEPT
      {
        return _mm256_cmpeq_epi32(a0,a1);
      }
   };

   BOOST_DISPATCH_OVERLOAD( is_equal_
                          , (typename A0)
                          , bs::avx2_
                          , bs::pack_<bd::ints16_<A0>, bs::avx_>
                          , bs::pack_<bd::ints16_<A0>, bs::avx_>
                          )
   {
      BOOST_FORCEINLINE
      bs::as_logical_t<A0> operator()(A0 const& a0, A0 const& a1) const BOOST_NOEXCEPT
      {
        return _mm256_cmpeq_epi16(a0,a1);
      }
   };

   BOOST_DISPATCH_OVERLOAD( is_equal_
                          , (typename A0)
                          , bs::avx2_
                          , bs::pack_<bd::ints8_<A0>, bs::avx_>
                          , bs::pack_<bd::ints8_<A0>, bs::avx_>
                          )
   {
      BOOST_FORCEINLINE
      bs::as_logical_t<A0> operator()(A0 const& a0, A0 const& a1) const BOOST_NOEXCEPT
      {
        return _mm256_cmpeq_epi8(a0,a1);
      }
   };
} } }

#endif


#include <ctime>
#include <mpi.h>
#include <iostream>
#include <random>
#include <stdlib.h>
#include <unistd.h>

#include <boost/mpi.hpp>

#include "MC_structure.h"
#include "structure_in.h"
#include "configure_in.h"
#include "spin_out.h"
#include "result_out.h"
#include "log.h"
#include "initialization.h"

#include "methods/classical.h"
#include "methods/parallel_tempering.h"

namespace mpi = boost::mpi;

using namespace std;

int main(int argc, char** argv) {
    mpi::environment env;
    mpi::communicator world;

    Supercell supercell;
    MonteCarlo monte_carlo;
    string cell_structure_file = "POSCAR";
    string input_file = "input.toml";
    string output_file = "output.txt";
    string spin_structure_file_prefix = "spin";

    auto logger = fmt::output_file("log.txt");
    time_t now = time(0);
    string dt = ctime(&now);

    // Read parameters and broadcast data using root processor
    if(world.rank() == 0) {
        logger.print("Material Monte Carlo is run on {} CPU cores.\n", world.size());
        logger.print("Start time: " + dt);
        // Read information from command line.
        ReadOptions(argc, argv, cell_structure_file, input_file, output_file, spin_structure_file_prefix);

        // Read information from input file.
        ReadSettingFile(supercell, monte_carlo, input_file);
        logger.print("Successfully process input file: " + input_file + ".\n");

        // Read information from POSCAR
        ReadPOSCAR(supercell, cell_structure_file);
        logger.print("Successfully load structure file: " + cell_structure_file + ".\n");
    }
    // Broadcast monte_carlo, base_site, lattice and spin_structure_file_prefix.
    broadcast(world, supercell.base_site, 0);
    broadcast(world, supercell.lattice, 0);
    broadcast(world, supercell.initialization, 0);
    broadcast(world, monte_carlo, 0);

    // Enlarge the cell with given n.
    EnlargeCell(supercell);
    InitializeSupercell(supercell);

    // Output the coordinate number
    if(world.rank() == 0) {
        WriteLog(supercell, monte_carlo, logger);
        WriteSpin(supercell, "structure_initialized");
    }

    vector<double> energy;
    vector<double> Cv;
    vector<double> moment;
    vector<double> chi;
    vector<double> moment_projection;
    vector<double> chi_projection;
    if(monte_carlo.methods == Methods::classical) {
        ClassicalMonteCarlo(env, world,
            monte_carlo, supercell, spin_structure_file_prefix,
            energy, Cv,
            moment, chi,
            moment_projection, chi_projection);
    } else {
        ParallelTemperingMonteCarlo(env, world,
            monte_carlo, supercell, spin_structure_file_prefix,
            energy, Cv,
            moment, chi,
            moment_projection, chi_projection);
    }

    // Output the thermal dynamic result using root processor.
    if(world.rank() == 0) {
        WriteOutput(monte_carlo, energy, Cv, moment, chi, moment_projection, chi_projection, supercell.lattice.field, output_file);
        logger.print("Successfully output all results into " + output_file + ".\n");
        now = time(0);
        dt = ctime(&now);
        logger.print("End time: " + dt);
    }
    return 0;
}


#include <boost/geometry/strategies/agnostic/point_in_poly_oriented_winding.hpp>


#define BOOST_TEST_MODULE pcraster old_calc parser
#include <boost/test/unit_test.hpp>
#include "geo_filecreatetester.h"
#include "com_exception.h"
#include "calc_runscript.h"

BOOST_AUTO_TEST_CASE(model_parser)
{
  using namespace calc;

  geo::FileCreateTester mt("parsertest.res");
  runScriptString("parsertest.res = inp1s.map + 4;");
  BOOST_CHECK(mt.equalTo("inp5s.map",false));
}


//| This file is a part of the sferes2 framework.
//| Copyright 2009, ISIR / Universite Pierre et Marie Curie (UPMC)
//| Main contributor(s): Jean-Baptiste Mouret, mouret@isir.fr
//|
//| This software is a computer program whose purpose is to facilitate
//| experiments in evolutionary computation and evolutionary robotics.
//|
//| This software is governed by the CeCILL license under French law
//| and abiding by the rules of distribution of free software.  You
//| can use, modify and/ or redistribute the software under the terms
//| of the CeCILL license as circulated by CEA, CNRS and INRIA at the
//| following URL "http://www.cecill.info".
//|
//| As a counterpart to the access to the source code and rights to
//| copy, modify and redistribute granted by the license, users are
//| provided only with a limited warranty and the software's author,
//| the holder of the economic rights, and the successive licensors
//| have only limited liability.
//|
//| In this respect, the user's attention is drawn to the risks
//| associated with loading, using, modifying and/or developing or
//| reproducing the software by the user in light of its specific
//| status of free software, that may mean that it is complicated to
//| manipulate, and that also therefore means that it is reserved for
//| developers and experienced professionals having in-depth computer
//| knowledge. Users are therefore encouraged to load and test the
//| software's suitability as regards their requirements in conditions
//| enabling the security of their systems and/or data to be ensured
//| and, more generally, to use and operate it in the same conditions
//| as regards security.
//|
//| The fact that you are presently reading this means that you have
//| had knowledge of the CeCILL license and that you accept its terms.

#ifndef FIT_MAP_DEEP_LEARNING_HPP
#define FIT_MAP_DEEP_LEARNING_HPP

#include "fit_deep_learning.hpp"
#include <modules/map_elite/fit_map.hpp>

#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>

// Headers specifics to the computations we need
#include <boost/accumulators/statistics/mean.hpp>

#include <stdexcept> // Exception

#define FIT_MAP_DEEP_LEARNING(Name) SFERES_FITNESS(Name, sferes::fit::FitDeepLearning)

namespace sferes
{
  namespace fit
  {
    SFERES_FITNESS(FitMapDeepLearning, sferes::fit::FitDeepLearning)
    {
    	/*
    	private:
    	struct ArgMax
    	{
    		unsigned int category;
    		float probability;
    	};

    	ArgMax getMaxProbability(const cv::Mat& image)
			{
    		this->initCaffeNet();	//Initialize caffe

				// Initialize test network
				shared_ptr<Net<float> > caffe_test_net = shared_ptr<Net<float> >( new Net<float>(Params::image::model_definition));

				// Get the trained model
				caffe_test_net->CopyTrainedLayersFrom(Params::image::pretrained_model);

				// Run ForwardPrefilled
				float loss;		//	const vector<Blob<float>*>& result = caffe_test_net.ForwardPrefilled(&loss);

				// Add images and labels manually to the ImageDataLayer
				vector<cv::Mat> images(1, image);
				vector<int> labels(1, 0);
				const shared_ptr<ImageDataLayer<float> > image_data_layer =
						boost::static_pointer_cast<ImageDataLayer<float> >(
								caffe_test_net->layer_by_name("data"));

				image_data_layer->AddImagesAndLabels(images, labels);

				vector<Blob<float>* > dummy_bottom_vec;
				const vector<Blob<float>*>& result = caffe_test_net->Forward(dummy_bottom_vec, &loss);

				// Get the highest layer of Softmax
				const float* argmax = result[1]->cpu_data();

				ArgMax m;
				m.category = (int) argmax[0];		// Category
				m.probability = (float) argmax[1];	// Probability

				return m;
			}
    	*/

    private:
    	void _setProbabilityList(const cv::Mat& image)
			{
				this->initCaffeNet();	//Initialize caffe

				// Initialize test network
				shared_ptr<Net<float> > caffe_test_net = shared_ptr<Net<float> >( new Net<float>(Params::image::model_definition, caffe::TEST));

				// Get the trained model
				caffe_test_net->CopyTrainedLayersFrom(Params::image::pretrained_model);

				// Run ForwardPrefilled
				float loss;

				// Add images and labels manually to the ImageDataLayer
				vector<int> labels(10, 0);
				vector<cv::Mat> images;

				// Add images to the list
				if (Params::image::use_crops)
				{
					// Ten crops have been stored in the vector
					this->_createTenCrops(image, images, Params::image::crop_size);
				}
				else
				{
					images.push_back(image);
				}

				// Classify images
				// 2015-01-16: Using this Memory Data Layer from this Caffe branch:
				// https://github.com/BVLC/caffe/pull/1416
				const shared_ptr<MemoryDataLayer<float> > memory_data_layer =
							boost::static_pointer_cast<MemoryDataLayer<float> >(
									caffe_test_net->layer_by_name("data"));

			  memory_data_layer->AddMatVector(images, labels);

				const vector<Blob<float>*>& result = caffe_test_net->ForwardPrefilled(&loss);

				// Get the highest layer of Softmax
				const float* softmax = result[1]->cpu_data();

				vector<double> values;

				// Clear the probability in case it is called twice
				_prob.clear();

				float score = -1.0;
				int label = -1;

				// If use 10 crops, we have to average the predictions of 10 crops
				if (Params::image::use_crops)
				{
					// Average the predictions of evaluating 10 crops
					for(int i = 0; i < Params::image::num_categories; ++i)
					{
						boost::accumulators::accumulator_set<double, boost::accumulators::stats<boost::accumulators::tag::mean> > avg;

						for(int j = 0; j < 10 * Params::image::num_categories; j += Params::image::num_categories)
						{
							avg(softmax[i + j]);
						}

						double mean = boost::accumulators::mean(avg);

						// Push 1000 probabilities in the list
						_prob.push_back(mean);

						// Record the top-1 label
						if (mean > score)
						{
							score = mean;
							label = i;
						}
					}
				}
				else
				{
					for(int i = 0; i < Params::image::num_categories; ++i)
					{
						float v = softmax[i];

						// Push 1000 probabilities in the list
						_prob.push_back(v);

						// Record the top-1 label
						if (v > score)
						{
							score = v;
							label = i;
						}
					}
				}

				assert(_prob.size() == Params::image::num_categories);

				// Top-1 score and label
				_top1_score = score;
				_top1_label = label;
			}

      public:
    	FitMapDeepLearning() : _prob(Params::image::num_categories) { }
			const std::vector<float>& desc() const { return _prob; }

			// Indiv will have the type defined in the main (phen_t)
			template<typename Indiv>
			void eval(const Indiv& ind)
			{
				if (Params::image::color)
				{
					// Convert image to BGR before evaluating
					cv::Mat output;

					// Convert HLS into BGR because imwrite uses BGR color space
					cv::cvtColor(ind.image(), output, CV_HLS2BGR);

					// Create an empty list to store get 1000 probabilities
					_setProbabilityList(output);
				}
				else	// Grayscale
				{
					// Create an empty list to store get 1000 probabilities
					_setProbabilityList(ind.image());
				}
			}

			float value(int category) const
			{
				assert(category < _prob.size());
				return _prob[category];
			}

			float value() const
			{
				throw std::runtime_error("This value function should not be called as"
														" we don't provide a single fitness value, but instead a whole vector.");
				return this->_value;
			}

			float score() const
			{
				return _top1_score;
			}

			int label() const
			{
				return _top1_label;
			}

			template<class Archive>
			void serialize(Archive & ar, const unsigned int version) {
				sferes::fit::Fitness<Params,  typename stc::FindExact<FitMapDeepLearning<Params, Exact>, Exact>::ret>::serialize(ar, version);
				ar & BOOST_SERIALIZATION_NVP(_prob);
			}

      protected:
			  std::vector<float> _prob; // List of probabilities
			  float _top1_score;		// Top-1 confidence score
				int _top1_label;						// Top-1 label
    };
  }
}

#endif


#include "generate_data/full_scene/scene_generator.h"

#include "intersect/triangle.h"
#include "lib/assert.h"
#include "lib/unit_vector.h"
#include "lib/vector_type.h"
#include "rng/uniform/uniform.h"
#include "scene/material.h"

#define TINYOBJLOADER_IMPLEMENTATION
#include <Eigen/Dense>
#include <tiny_obj_loader.h>

#include <iostream>
#include <optional>
#include <random>
#include <vector>

#include "dbg.h"

namespace generate_data {
namespace full_scene {
static VectorT<TriangleNormals> load_obj(const std::string &path) {
  VectorT<TriangleNormals> out;

  tinyobj::attrib_t attrib;
  std::vector<tinyobj::shape_t> shapes;
  std::vector<tinyobj::material_t> mesh_materials;

  std::string err;
  bool ret = tinyobj::LoadObj(&attrib, &shapes, &mesh_materials, &err,
                              path.c_str(), nullptr, true);
  if (!ret) {
    std::cerr << "Failed to load/parse .obj file" << std::endl;
    std::cerr << err << std::endl;
    unreachable();
  }

  for (size_t s = 0; s < shapes.size(); s++) {
    size_t index_offset = 0;
    for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) {
      unsigned fv = shapes[s].mesh.num_face_vertices[f];
      if (fv != 3) {
        std::cerr << "only triangles supported and obj contains non-triangles"
                  << std::endl;
        unreachable();
      }

      std::array<Eigen::Vector3f, 3> vertices;
      std::array<std::optional<UnitVector>, 3> normals_op;
      for (size_t v = 0; v < fv; v++) {
        tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
        tinyobj::real_t vx = attrib.vertices[3 * idx.vertex_index + 0];
        tinyobj::real_t vy = attrib.vertices[3 * idx.vertex_index + 1];
        tinyobj::real_t vz = attrib.vertices[3 * idx.vertex_index + 2];
        tinyobj::real_t nx;
        tinyobj::real_t ny;
        tinyobj::real_t nz;
        // TODO: currently texture not supported...
        tinyobj::real_t tx;
        tinyobj::real_t ty;

        if (idx.normal_index != -1) {
          nx = attrib.normals[3 * idx.normal_index + 0];
          ny = attrib.normals[3 * idx.normal_index + 1];
          nz = attrib.normals[3 * idx.normal_index + 2];
          normals_op[v] = UnitVector::new_normalize({nx, ny, nz});
        }

        if (idx.texcoord_index != -1) {
          tx = attrib.texcoords[2 * idx.texcoord_index + 0];
          ty = attrib.texcoords[2 * idx.texcoord_index + 1];
        } else {
          tx = 0;
          ty = 0;
        }

        Eigen::Vector3f vertex(vx, vy, vz);

        vertices[v] = vertex;
      }

      intersect::Triangle triangle{vertices};

      std::array<UnitVector, 3> normals;
      for (size_t v = 0; v < fv; v++) {
        normals[v] = optional_unwrap_or_else(normals_op[v],
                                             [&] { return triangle.normal(); });
      }

      out.push_back(TriangleNormals{
          .tri = triangle,
          .normals = normals,
      });

      index_offset += fv;
    }
  }

  return out;
}

SceneGenerator::SceneGenerator() {
  sphere_ = load_obj(OBJ_DIR_PATH "sphere.obj");
  monkey_ = load_obj(OBJ_DIR_PATH "monkey.obj");
  torus_ = load_obj(OBJ_DIR_PATH "torus.obj");
  meshs_ = {&sphere_, &torus_};
}

scene::Material random_bsdf(std::mt19937 &rng, bool force_emissive) {
  auto random_float_rgb = [&](auto dist) -> FloatRGB {
    return {{dist(rng), dist(rng), dist(rng)}};
  };

  auto random_weights = [&]() -> FloatRGB {
    std::uniform_real_distribution dist(0.f, 1.f);
    FloatRGB out = random_float_rgb(dist);
    auto mag = dist(rng);
    return out().matrix().normalized().array() * mag;
  };

  bool is_emissive = force_emissive || std::bernoulli_distribution(0.3)(rng);
  auto emission = [&]() -> FloatRGB {
    if (is_emissive) {
      return random_float_rgb(std::uniform_real_distribution(0.f, 70.f));
    } else {
      return FloatRGB::Zero();
    }
  }();

  auto bsdf = [&]() -> bsdf::UnionBSDF {
    bool is_only_transparent = std::bernoulli_distribution(0.2)(rng);

    // we could also have transparent + diffuse + glossy, but that't not
    // really needed atm
    if (is_only_transparent) {
      float ior = std::uniform_real_distribution(1.1f, 2.f)(rng);
      return {
          {tag_v<bsdf::BSDFType::DielectricRefractive>, random_weights(), ior}};
    }

    bool is_only_mirror = std::bernoulli_distribution(0.1)(rng);
    if (is_only_mirror) {
      return {{tag_v<bsdf::BSDFType::Mirror>, random_weights()}};
    }

    bool is_glossy = std::bernoulli_distribution(0.4)(rng);
    auto specular = random_weights();
    auto shininess = std::uniform_real_distribution(2.f, 50.f)(rng);
    bool is_diffuse = std::bernoulli_distribution(0.8)(rng);
    auto diffuse = random_weights();

    if (is_glossy && !is_diffuse) {
      return {{tag_v<bsdf::BSDFType::Glossy>, specular, shininess}};
    } else if (!is_glossy && is_diffuse) {
      return {{tag_v<bsdf::BSDFType::Diffuse>, diffuse}};
    } else {
      float div_factor = (diffuse + specular).maxCoeff();
      diffuse = diffuse / div_factor;
      specular = specular / div_factor;
      float total_diffuse_mass = diffuse.sum();
      float total_specular_mass = specular.sum();
      float diffuse_weight =
          total_diffuse_mass / (total_diffuse_mass + total_specular_mass);
      float glossy_weight = 1. - diffuse_weight;

      debug_assert((diffuse + specular).maxCoeff() < 1.f + 1e-4f);

      const auto new_diffuse = diffuse / diffuse_weight;
      const auto new_specular = specular / glossy_weight;

      return {{tag_v<bsdf::BSDFType::DiffuseGlossy>,
               {{{new_diffuse}, {new_specular, shininess}},
                {diffuse_weight, glossy_weight}}}};
    }
  }();

  return {
      .bsdf = bsdf,
      .emission = emission,
  };
}

// could actually use meshes part of scene_...
void SceneGenerator::add_mesh(const VectorT<TriangleNormals> &tris,
                              const Eigen::Affine3f &transform,
                              unsigned material_idx) {
  unsigned start_idx = scene_.triangles_.size();
  intersect::accel::AABB bounds;
  for (const auto &tri : tris) {
    std::array<UnitVector, 3> normals;
    auto new_tri = tri.tri.transform(transform);
    bounds = bounds.union_other(new_tri.bounds());
    scene_.triangles_.push_back_all(
        new_tri,
        scene::TriangleData{tri.normals, material_idx}.transform(transform));
  }
  unsigned end_idx = scene_.triangles_.size();

  bool is_emissive =
      (scene_.materials_[material_idx].emission().array() > 0.f).any();

  if (is_emissive) {
    scene_.emissive_clusters_.push_back({
        .material_idx = material_idx,
        .start_idx = start_idx,
        .end_idx = end_idx,
        .aabb = bounds,
    });
  }

  overall_aabb_ = overall_aabb_.union_other(bounds);
}

std::tuple<const scene::Scene &, unsigned>
SceneGenerator::generate(std::mt19937 &rng) {
  // clear! (could be more efficient...)
  scene_ = scene::Scene{};
  overall_aabb_ = intersect::accel::AABB::empty();

  unsigned mesh_count, individual_tri_count, total_size;
  while (true) {
    mesh_count = 0; // std::uniform_int_distribution(0u, 3u)(rng);
    individual_tri_count = std::uniform_int_distribution(0u, 30u)(rng);
    total_size = mesh_count + individual_tri_count;

    if (total_size >= 2) {
      break; // some interaction
    }
  }

  bool has_emissive = false;
  for (unsigned i = 0; i < total_size; ++i) {
    const auto material =
        random_bsdf(rng, !has_emissive && i == total_size - 1);
    has_emissive = has_emissive || material.emission.sum() > 1e-4f;
    scene_.materials_.push_back(material);
  }

  auto random_vec = [&](auto dist) -> Eigen::Vector3f {
    return {dist(rng), dist(rng), dist(rng)};
  };

  unsigned total_mesh_size = 0;
  for (unsigned i = 0; i < mesh_count; ++i) {
    std::uniform_real_distribution<float> angle_dist{-M_PI, M_PI};
    Eigen::Affine3f transform{
        Eigen::Translation3f{
            random_vec(std::uniform_real_distribution{-2.f, 2.f})} *
        Eigen::Scaling(std::uniform_real_distribution{0.1f, 1.f}(rng)) *
        Eigen::AngleAxisf{angle_dist(rng), Eigen::Vector3f::UnitX()} *
        Eigen::AngleAxisf{angle_dist(rng), Eigen::Vector3f::UnitY()} *
        Eigen::AngleAxisf{angle_dist(rng), Eigen::Vector3f::UnitZ()}};

    unsigned mesh_idx =
        std::uniform_int_distribution{size_t(0), meshs_.size() - 1}(rng);
    add_mesh(*meshs_[mesh_idx], transform, i);
    total_mesh_size += meshs_[mesh_idx]->size();
  }

  auto random_vert = [&]() {
    return random_vec(std::uniform_real_distribution{-0.5f, 0.5f});
  };

  for (unsigned j = mesh_count; j < total_size; ++j) {
    const auto center = random_vec(std::uniform_real_distribution{-2.f, 2.f});
    const float scale = std::bernoulli_distribution(0.2)(rng) ? 4.f : 1.f;
    const intersect::Triangle tri{
        scale * random_vert() + center,
        scale * random_vert() + center,
        scale * random_vert() + center,
    };
    const auto normal = tri.normal();

    add_mesh({TriangleNormals{.tri = tri, .normals = {normal, normal, normal}}},
             Eigen::Affine3f::Identity(), j);
  }

  // one mesh (everything is flattened anyway...)
  scene_.meshs_.push_back_all(scene_.triangles_.size(), overall_aabb_, "unique",
                              scene_.emissive_clusters_.size());
  scene_.transformed_objects_.push_back_all(
      intersect::TransformedObject(Eigen::Affine3f::Identity(), overall_aabb_),
      0);

  return {scene_, total_mesh_size};
}
} // namespace full_scene
} // namespace generate_data


#ifndef VIENNACL_LINALG_AMG_HPP_
#define VIENNACL_LINALG_AMG_HPP_

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

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

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

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

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

/** @file viennacl/linalg/amg.hpp
    @brief Main include file for algebraic multigrid (AMG) preconditioners.  Experimental.

    Implementation contributed by Markus Wagner
*/

#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/lu.hpp>
#include <boost/numeric/ublas/operation.hpp>
#include <boost/numeric/ublas/vector_proxy.hpp>
#include <boost/numeric/ublas/matrix_proxy.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/triangular.hpp>
#include <vector>
#include <cmath>
#include "viennacl/forwards.h"
#include "viennacl/tools/tools.hpp"
#include "viennacl/linalg/prod.hpp"
#include "viennacl/linalg/direct_solve.hpp"

#include "viennacl/linalg/detail/amg/amg_base.hpp"
#include "viennacl/linalg/detail/amg/amg_coarse.hpp"
#include "viennacl/linalg/detail/amg/amg_interpol.hpp"

#include <map>

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

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

#define VIENNACL_AMG_COARSE_LIMIT 50
#define VIENNACL_AMG_MAX_LEVELS 100

namespace viennacl
{
  namespace linalg
  {
    typedef detail::amg::amg_tag          amg_tag;

    /** @brief Setup AMG preconditioner
    *
    * @param A      Operator matrices on all levels
    * @param P      Prolongation/Interpolation operators on all levels
    * @param Pointvector  Vector of points on all levels
    * @param tag    AMG preconditioner tag
    */
    template<typename InternalType1, typename InternalType2>
    void amg_setup(InternalType1 & A, InternalType1 & P, InternalType2 & Pointvector, amg_tag & tag)
    {
      typedef typename InternalType2::value_type PointVectorType;

      unsigned int i, iterations, c_points, f_points;
      detail::amg::amg_slicing<InternalType1,InternalType2> Slicing;

      // Set number of iterations. If automatic coarse grid construction is chosen (0), then set a maximum size and stop during the process.
      iterations = tag.get_coarselevels();
      if (iterations == 0)
        iterations = VIENNACL_AMG_MAX_LEVELS;

      // For parallel coarsenings build data structures (number of threads set automatically).
      if (tag.get_coarse() == VIENNACL_AMG_COARSE_RS0 || tag.get_coarse() == VIENNACL_AMG_COARSE_RS3)
        Slicing.init(iterations);

      for (i=0; i<iterations; ++i)
      {
        // Initialize Pointvector on level i and construct points.
        Pointvector[i] = PointVectorType(static_cast<unsigned int>(A[i].size1()));
        Pointvector[i].init_points();

        // Construct C and F points on coarse level (i is fine level, i+1 coarse level).
        detail::amg::amg_coarse (i, A, Pointvector, Slicing, tag);

        // Calculate number of C and F points on level i.
        c_points = Pointvector[i].get_cpoints();
        f_points = Pointvector[i].get_fpoints();

        #if defined (VIENNACL_AMG_DEBUG) //or defined(VIENNACL_AMG_DEBUGBENCH)
        std::cout << "Level " << i << ": ";
        std::cout << "No of C points = " << c_points << ", ";
        std::cout << "No of F points = " << f_points << std::endl;
        #endif

        // Stop routine when the maximal coarse level is found (no C or F point). Coarsest level is level i.
        if (c_points == 0 || f_points == 0)
          break;

        // Construct interpolation matrix for level i.
        detail::amg::amg_interpol (i, A, P, Pointvector, tag);

        // Compute coarse grid operator (A[i+1] = R * A[i] * P) with R = trans(P).
        detail::amg::amg_galerkin_prod(A[i], P[i], A[i+1]);

        // Test triple matrix product. Very slow for large matrix sizes (ublas).
        // test_triplematprod(A[i],P[i],A[i+1]);

        Pointvector[i].delete_points();

        #ifdef VIENNACL_AMG_DEBUG
        std::cout << "Coarse Grid Operator Matrix:" << std::endl;
        printmatrix (A[i+1]);
        #endif

        // If Limit of coarse points is reached then stop. Coarsest level is level i+1.
        if (tag.get_coarselevels() == 0 && c_points <= VIENNACL_AMG_COARSE_LIMIT)
        {
          tag.set_coarselevels(i+1);
          return;
        }
      }
      tag.set_coarselevels(i);
    }

    /** @brief Initialize AMG preconditioner
    *
    * @param mat    System matrix
    * @param A      Operator matrices on all levels
    * @param P      Prolongation/Interpolation operators on all levels
    * @param Pointvector  Vector of points on all levels
    * @param tag    AMG preconditioner tag
    */
    template<typename MatrixType, typename InternalType1, typename InternalType2>
    void amg_init(MatrixType const & mat, InternalType1 & A, InternalType1 & P, InternalType2 & Pointvector, amg_tag & tag)
    {
      //typedef typename MatrixType::value_type ScalarType;
      typedef typename InternalType1::value_type SparseMatrixType;

      if (tag.get_coarselevels() > 0)
      {
        A.resize(tag.get_coarselevels()+1);
        P.resize(tag.get_coarselevels());
        Pointvector.resize(tag.get_coarselevels());
      }
      else
      {
        A.resize(VIENNACL_AMG_MAX_LEVELS+1);
        P.resize(VIENNACL_AMG_MAX_LEVELS);
        Pointvector.resize(VIENNACL_AMG_MAX_LEVELS);
      }

      // Insert operator matrix as operator for finest level.
      SparseMatrixType A0 (mat);
      A.insert_element (0, A0);
    }

    /** @brief Save operators after setup phase for CPU computation.
    *
    * @param A      Operator matrices on all levels on the CPU
    * @param P      Prolongation/Interpolation operators on all levels on the CPU
    * @param R      Restriction operators on all levels on the CPU
    * @param A_setup    Operators matrices on all levels from setup phase
    * @param P_setup    Prolongation/Interpolation operators on all levels from setup phase
    * @param tag    AMG preconditioner tag
    */
    template<typename InternalType1, typename InternalType2>
    void amg_transform_cpu (InternalType1 & A, InternalType1 & P, InternalType1 & R, InternalType2 & A_setup, InternalType2 & P_setup, amg_tag & tag)
    {
      //typedef typename InternalType1::value_type MatrixType;

      // Resize internal data structures to actual size.
      A.resize(tag.get_coarselevels()+1);
      P.resize(tag.get_coarselevels());
      R.resize(tag.get_coarselevels());

      // Transform into matrix type.
      for (unsigned int i=0; i<tag.get_coarselevels()+1; ++i)
      {
        A[i].resize(A_setup[i].size1(),A_setup[i].size2(),false);
        A[i] = A_setup[i];
      }
      for (unsigned int i=0; i<tag.get_coarselevels(); ++i)
      {
        P[i].resize(P_setup[i].size1(),P_setup[i].size2(),false);
        P[i] = P_setup[i];
      }
      for (unsigned int i=0; i<tag.get_coarselevels(); ++i)
      {
        R[i].resize(P_setup[i].size2(),P_setup[i].size1(),false);
        P_setup[i].set_trans(true);
        R[i] = P_setup[i];
        P_setup[i].set_trans(false);
      }
    }

    /** @brief Save operators after setup phase for GPU computation.
    *
    * @param A      Operator matrices on all levels on the GPU
    * @param P      Prolongation/Interpolation operators on all levels on the GPU
    * @param R      Restriction operators on all levels on the GPU
    * @param A_setup    Operators matrices on all levels from setup phase
    * @param P_setup    Prolongation/Interpolation operators on all levels from setup phase
    * @param tag    AMG preconditioner tag
    * @param ctx      Optional context in which the auxiliary objects are created (one out of multiple OpenCL contexts, CUDA, host)
    */
    template<typename InternalType1, typename InternalType2>
    void amg_transform_gpu (InternalType1 & A, InternalType1 & P, InternalType1 & R, InternalType2 & A_setup, InternalType2 & P_setup, amg_tag & tag, viennacl::context ctx)
    {
      // Resize internal data structures to actual size.
      A.resize(tag.get_coarselevels()+1);
      P.resize(tag.get_coarselevels());
      R.resize(tag.get_coarselevels());

      // Copy to GPU using the internal sparse matrix structure: std::vector<std::map>.
      for (unsigned int i=0; i<tag.get_coarselevels()+1; ++i)
      {
        viennacl::switch_memory_context(A[i], ctx);
        //A[i].resize(A_setup[i].size1(),A_setup[i].size2(),false);
        viennacl::copy(*(A_setup[i].get_internal_pointer()),A[i]);
      }
      for (unsigned int i=0; i<tag.get_coarselevels(); ++i)
      {
        viennacl::switch_memory_context(P[i], ctx);
        //P[i].resize(P_setup[i].size1(),P_setup[i].size2(),false);
        viennacl::copy(*(P_setup[i].get_internal_pointer()),P[i]);
        //viennacl::copy((boost::numeric::ublas::compressed_matrix<ScalarType>)P_setup[i],P[i]);
      }
      for (unsigned int i=0; i<tag.get_coarselevels(); ++i)
      {
        viennacl::switch_memory_context(R[i], ctx);
        //R[i].resize(P_setup[i].size2(),P_setup[i].size1(),false);
        P_setup[i].set_trans(true);
        viennacl::copy(*(P_setup[i].get_internal_pointer()),R[i]);
        P_setup[i].set_trans(false);
      }
    }

    /** @brief Setup data structures for precondition phase.
    *
    * @param result    Result vector on all levels
    * @param rhs    RHS vector on all levels
    * @param residual    Residual vector on all levels
    * @param A      Operators matrices on all levels from setup phase
    * @param tag    AMG preconditioner tag
    */
    template<typename InternalVectorType, typename SparseMatrixType>
    void amg_setup_apply (InternalVectorType & result, InternalVectorType & rhs, InternalVectorType & residual, SparseMatrixType const & A, amg_tag const & tag)
    {
      typedef typename InternalVectorType::value_type VectorType;

      result.resize(tag.get_coarselevels()+1);
      rhs.resize(tag.get_coarselevels()+1);
      residual.resize(tag.get_coarselevels());

      for (unsigned int level=0; level < tag.get_coarselevels()+1; ++level)
      {
        result[level] = VectorType(A[level].size1());
        result[level].clear();
        rhs[level] = VectorType(A[level].size1());
        rhs[level].clear();
      }
      for (unsigned int level=0; level < tag.get_coarselevels(); ++level)
      {
        residual[level] = VectorType(A[level].size1());
        residual[level].clear();
      }
    }

    /** @brief Setup data structures for precondition phase for later use on the GPU
    *
    * @param result    Result vector on all levels
    * @param rhs    RHS vector on all levels
    * @param residual    Residual vector on all levels
    * @param A      Operators matrices on all levels from setup phase
    * @param tag    AMG preconditioner tag
    * @param ctx      Optional context in which the auxiliary objects are created (one out of multiple OpenCL contexts, CUDA, host)
    */
    template<typename InternalVectorType, typename SparseMatrixType>
    void amg_setup_apply (InternalVectorType & result, InternalVectorType & rhs, InternalVectorType & residual, SparseMatrixType const & A, amg_tag const & tag, viennacl::context ctx)
    {
      typedef typename InternalVectorType::value_type VectorType;

      result.resize(tag.get_coarselevels()+1);
      rhs.resize(tag.get_coarselevels()+1);
      residual.resize(tag.get_coarselevels());

      for (unsigned int level=0; level < tag.get_coarselevels()+1; ++level)
      {
        result[level] = VectorType(A[level].size1(), ctx);
        rhs[level] = VectorType(A[level].size1(), ctx);
      }
      for (unsigned int level=0; level < tag.get_coarselevels(); ++level)
      {
        residual[level] = VectorType(A[level].size1(), ctx);
      }
    }

    /** @brief Pre-compute LU factorization for direct solve (ublas library).
     *  @brief Speeds up precondition phase as this is computed only once overall instead of once per iteration.
    *
    * @param op      Operator matrix for direct solve
    * @param Permutation  Permutation matrix which saves the factorization result
    * @param A      Operator matrix on coarsest level
    */
    template<typename ScalarType, typename SparseMatrixType>
    void amg_lu(boost::numeric::ublas::compressed_matrix<ScalarType> & op, boost::numeric::ublas::permutation_matrix<> & Permutation, SparseMatrixType const & A)
    {
      typedef typename SparseMatrixType::const_iterator1 ConstRowIterator;
      typedef typename SparseMatrixType::const_iterator2 ConstColIterator;

      // Copy to operator matrix. Needed
      op.resize(A.size1(),A.size2(),false);
      for (ConstRowIterator row_iter = A.begin1(); row_iter != A.end1(); ++row_iter)
        for (ConstColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
          op (col_iter.index1(), col_iter.index2()) = *col_iter;

      // Permutation matrix has to be reinitialized with actual size. Do not clear() or resize()!
      Permutation = boost::numeric::ublas::permutation_matrix<> (op.size1());
      boost::numeric::ublas::lu_factorize(op,Permutation);
    }

    /** @brief AMG preconditioner class, can be supplied to solve()-routines
    */
    template<typename MatrixType>
    class amg_precond
    {
      typedef typename MatrixType::value_type ScalarType;
      typedef boost::numeric::ublas::vector<ScalarType> VectorType;
      typedef detail::amg::amg_sparsematrix<ScalarType> SparseMatrixType;
      typedef detail::amg::amg_pointvector PointVectorType;

      typedef typename SparseMatrixType::const_iterator1 InternalConstRowIterator;
      typedef typename SparseMatrixType::const_iterator2 InternalConstColIterator;
      typedef typename SparseMatrixType::iterator1 InternalRowIterator;
      typedef typename SparseMatrixType::iterator2 InternalColIterator;

      boost::numeric::ublas::vector <SparseMatrixType> A_setup;
      boost::numeric::ublas::vector <SparseMatrixType> P_setup;
      boost::numeric::ublas::vector <MatrixType> A;
      boost::numeric::ublas::vector <MatrixType> P;
      boost::numeric::ublas::vector <MatrixType> R;
      boost::numeric::ublas::vector <PointVectorType> Pointvector;

      mutable boost::numeric::ublas::compressed_matrix<ScalarType> op;
      mutable boost::numeric::ublas::permutation_matrix<> Permutation;

      mutable boost::numeric::ublas::vector <VectorType> result;
      mutable boost::numeric::ublas::vector <VectorType> rhs;
      mutable boost::numeric::ublas::vector <VectorType> residual;

      mutable bool done_init_apply;

      amg_tag tag_;
    public:

      amg_precond(): Permutation(0) {}
      /** @brief The constructor. Saves system matrix, tag and builds data structures for setup.
      *
      * @param mat  System matrix
      * @param tag  The AMG tag
      */
      amg_precond(MatrixType const & mat, amg_tag const & tag): Permutation(0)
      {
        tag_ = tag;
        // Initialize data structures.
        amg_init (mat,A_setup,P_setup,Pointvector,tag_);

        done_init_apply = false;
      }

      /** @brief Start setup phase for this class and copy data structures.
      */
      void setup()
      {
        // Start setup phase.
        amg_setup(A_setup,P_setup,Pointvector,tag_);
        // Transform to CPU-Matrixtype for precondition phase.
        amg_transform_cpu(A,P,R,A_setup,P_setup,tag_);

        done_init_apply = false;
      }

      /** @brief Prepare data structures for preconditioning:
       *  Build data structures for precondition phase.
       *  Do LU factorization on coarsest level.
      */
      void init_apply() const
      {
        // Setup precondition phase (Data structures).
        amg_setup_apply(result,rhs,residual,A_setup,tag_);
        // Do LU factorization for direct solve.
        amg_lu(op,Permutation,A_setup[tag_.get_coarselevels()]);

        done_init_apply = true;
      }

      /** @brief Returns complexity measures.
      *
      * @param avgstencil  Average stencil sizes on all levels
      * @return     Operator complexity of AMG method
      */
      template<typename VectorType>
      ScalarType calc_complexity(VectorType & avgstencil)
      {
        avgstencil = VectorType (tag_.get_coarselevels()+1);
        unsigned int nonzero=0, systemmat_nonzero=0, level_coefficients=0;

        for (unsigned int level=0; level < tag_.get_coarselevels()+1; ++level)
        {
          level_coefficients = 0;
          for (InternalRowIterator row_iter = A_setup[level].begin1(); row_iter != A_setup[level].end1(); ++row_iter)
          {
            for (InternalColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
            {
              if (level == 0)
                systemmat_nonzero++;
              nonzero++;
              level_coefficients++;
            }
          }
          avgstencil[level] = level_coefficients/static_cast<ScalarType>(A_setup[level].size1());
        }
        return nonzero/static_cast<ScalarType>(systemmat_nonzero);
      }

      /** @brief Precondition Operation
      *
      * @param vec The vector to which preconditioning is applied to (ublas version)
      */
      template<typename VectorType>
      void apply(VectorType & vec) const
      {
        // Build data structures and do lu factorization before first iteration step.
        if (!done_init_apply)
          init_apply();

        int level;

        // Precondition operation (Yang, p.3)
        rhs[0] = vec;
        for (level=0; level <static_cast<int>(tag_.get_coarselevels()); level++)
        {
          result[level].clear();

          // Apply Smoother presmooth_ times.
          smooth_jacobi (level, tag_.get_presmooth(), result[level], rhs[level]);

          #ifdef VIENNACL_AMG_DEBUG
          std::cout << "After presmooth:" << std::endl;
          printvector(result[level]);
          #endif

          // Compute residual.
          residual[level] = rhs[level] - boost::numeric::ublas::prod (A[level],result[level]);

          #ifdef VIENNACL_AMG_DEBUG
          std::cout << "Residual:" << std::endl;
          printvector(residual[level]);
          #endif

          // Restrict to coarse level. Restricted residual is RHS of coarse level.
          rhs[level+1] = boost::numeric::ublas::prod (R[level],residual[level]);

          #ifdef VIENNACL_AMG_DEBUG
          std::cout << "Restricted Residual: " << std::endl;
          printvector(rhs[level+1]);
          #endif
        }

        // On highest level use direct solve to solve equation.
        result[level] = rhs[level];
        boost::numeric::ublas::lu_substitute(op,Permutation,result[level]);

        #ifdef VIENNACL_AMG_DEBUG
        std::cout << "After direct solve: " << std::endl;
        printvector (result[level]);
        #endif

        for (level=tag_.get_coarselevels()-1; level >= 0; level--)
        {
          #ifdef VIENNACL_AMG_DEBUG
          std::cout << "Coarse Error: " << std::endl;
          printvector(result[level+1]);
          #endif

          // Interpolate error to fine level. Correct solution by adding error.
          result[level] += boost::numeric::ublas::prod (P[level], result[level+1]);

          #ifdef VIENNACL_AMG_DEBUG
          std::cout << "Corrected Result: " << std::endl;
          printvector (result[level]);
          #endif

          // Apply Smoother postsmooth_ times.
          smooth_jacobi (level, tag_.get_postsmooth(), result[level], rhs[level]);

          #ifdef VIENNACL_AMG_DEBUG
          std::cout << "After postsmooth: " << std::endl;
          printvector (result[level]);
          #endif
        }
        vec = result[0];
      }

      /** @brief (Weighted) Jacobi Smoother (CPU version)
      * @param level    Coarse level to which smoother is applied to
      * @param iterations  Number of smoother iterations
      * @param x     The vector smoothing is applied to
      * @param rhs    The right hand side of the equation for the smoother
      */
      template<typename VectorType>
      void smooth_jacobi(int level, int const iterations, VectorType & x, VectorType const & rhs) const
      {
        VectorType old_result (x.size());
        long index;
        ScalarType sum = 0, diag = 1;

        for (int i=0; i<iterations; ++i)
        {
          old_result = x;
          x.clear();
#ifdef VIENNACL_WITH_OPENMP
          #pragma omp parallel for private (sum,diag) shared (rhs,x)
#endif
          for (index=0; index < static_cast<long>(A_setup[level].size1()); ++index)
          {
            InternalConstRowIterator row_iter = A_setup[level].begin1();
            row_iter += index;
            sum = 0;
            diag = 1;
            for (InternalConstColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
            {
              if (col_iter.index1() == col_iter.index2())
                diag = *col_iter;
              else
                sum += *col_iter * old_result[col_iter.index2()];
            }
            x[index]= static_cast<ScalarType>(tag_.get_jacobiweight()) * (rhs[index] - sum) / diag + (1-static_cast<ScalarType>(tag_.get_jacobiweight())) * old_result[index];
          }
        }
      }

      amg_tag & tag() { return tag_; }
    };

    /** @brief AMG preconditioner class, can be supplied to solve()-routines.
    *
    *  Specialization for compressed_matrix
    */
    template<typename ScalarType, unsigned int MAT_ALIGNMENT>
    class amg_precond< compressed_matrix<ScalarType, MAT_ALIGNMENT> >
    {
      typedef viennacl::compressed_matrix<ScalarType, MAT_ALIGNMENT> MatrixType;
      typedef viennacl::vector<ScalarType> VectorType;
      typedef detail::amg::amg_sparsematrix<ScalarType> SparseMatrixType;
      typedef detail::amg::amg_pointvector PointVectorType;

      typedef typename SparseMatrixType::const_iterator1 InternalConstRowIterator;
      typedef typename SparseMatrixType::const_iterator2 InternalConstColIterator;
      typedef typename SparseMatrixType::iterator1 InternalRowIterator;
      typedef typename SparseMatrixType::iterator2 InternalColIterator;

      boost::numeric::ublas::vector <SparseMatrixType> A_setup;
      boost::numeric::ublas::vector <SparseMatrixType> P_setup;
      boost::numeric::ublas::vector <MatrixType> A;
      boost::numeric::ublas::vector <MatrixType> P;
      boost::numeric::ublas::vector <MatrixType> R;
      boost::numeric::ublas::vector <PointVectorType> Pointvector;

      mutable boost::numeric::ublas::compressed_matrix<ScalarType> op;
      mutable boost::numeric::ublas::permutation_matrix<> Permutation;

      mutable boost::numeric::ublas::vector <VectorType> result;
      mutable boost::numeric::ublas::vector <VectorType> rhs;
      mutable boost::numeric::ublas::vector <VectorType> residual;

      viennacl::context ctx_;

      mutable bool done_init_apply;

      amg_tag tag_;

    public:

      amg_precond(): Permutation(0) {}

      /** @brief The constructor. Builds data structures.
      *
      * @param mat  System matrix
      * @param tag  The AMG tag
      */
      amg_precond(compressed_matrix<ScalarType, MAT_ALIGNMENT> const & mat, amg_tag const & tag): Permutation(0), ctx_(viennacl::traits::context(mat))
      {
        tag_ = tag;

        // Copy to CPU. Internal structure of sparse matrix is used for copy operation.
        std::vector<std::map<unsigned int, ScalarType> > mat2 = std::vector<std::map<unsigned int, ScalarType> >(mat.size1());
        viennacl::copy(mat, mat2);

        // Initialize data structures.
        amg_init (mat2,A_setup,P_setup,Pointvector,tag_);

        done_init_apply = false;
      }

      /** @brief Start setup phase for this class and copy data structures.
      */
      void setup()
      {
        // Start setup phase.
        amg_setup(A_setup,P_setup,Pointvector, tag_);
        // Transform to GPU-Matrixtype for precondition phase.
        amg_transform_gpu(A,P,R,A_setup,P_setup, tag_, ctx_);

        done_init_apply = false;
      }

      /** @brief Prepare data structures for preconditioning:
       *  Build data structures for precondition phase.
       *  Do LU factorization on coarsest level.
      */
      void init_apply() const
      {
        // Setup precondition phase (Data structures).
        amg_setup_apply(result,rhs,residual,A_setup,tag_, ctx_);
        // Do LU factorization for direct solve.
        amg_lu(op,Permutation,A_setup[tag_.get_coarselevels()]);

        done_init_apply = true;
      }

      /** @brief Returns complexity measures
      *
      * @param avgstencil  Average stencil sizes on all levels
      * @return     Operator complexity of AMG method
      */
      template<typename VectorType>
      ScalarType calc_complexity(VectorType & avgstencil)
      {
        avgstencil = VectorType (tag_.get_coarselevels()+1);
        unsigned int nonzero=0, systemmat_nonzero=0, level_coefficients=0;

        for (unsigned int level=0; level < tag_.get_coarselevels()+1; ++level)
        {
          level_coefficients = 0;
          for (InternalRowIterator row_iter = A_setup[level].begin1(); row_iter != A_setup[level].end1(); ++row_iter)
          {
            for (InternalColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
            {
              if (level == 0)
                systemmat_nonzero++;
              nonzero++;
              level_coefficients++;
            }
          }
          avgstencil[level] = level_coefficients/(double)A[level].size1();
        }
        return nonzero/static_cast<double>(systemmat_nonzero);
      }

      /** @brief Precondition Operation
      *
      * @param vec The vector to which preconditioning is applied to
      */
      template<typename VectorType>
      void apply(VectorType & vec) const
      {
        if (!done_init_apply)
          init_apply();

        int level;

        // Precondition operation (Yang, p.3).
        rhs[0] = vec;
        for (level=0; level <static_cast<int>(tag_.get_coarselevels()); level++)
        {
          result[level].clear();

          // Apply Smoother presmooth_ times.
          smooth_jacobi (level, tag_.get_presmooth(), result[level], rhs[level]);

          #ifdef VIENNACL_AMG_DEBUG
          std::cout << "After presmooth: " << std::endl;
          printvector(result[level]);
          #endif

          // Compute residual.
          //residual[level] = rhs[level] - viennacl::linalg::prod (A[level],result[level]);
          residual[level] = viennacl::linalg::prod (A[level],result[level]);
          residual[level] = rhs[level] - residual[level];

          #ifdef VIENNACL_AMG_DEBUG
          std::cout << "Residual: " << std::endl;
          printvector(residual[level]);
          #endif

          // Restrict to coarse level. Result is RHS of coarse level equation.
          //residual_coarse[level] = viennacl::linalg::prod(R[level],residual[level]);
          rhs[level+1] = viennacl::linalg::prod(R[level],residual[level]);

          #ifdef VIENNACL_AMG_DEBUG
          std::cout << "Restricted Residual: " << std::endl;
          printvector(rhs[level+1]);
          #endif
        }

        // On highest level use direct solve to solve equation (on the CPU)
        //TODO: Use GPU direct solve!
        result[level] = rhs[level];
        boost::numeric::ublas::vector <ScalarType> result_cpu (result[level].size());

        copy (result[level],result_cpu);
        boost::numeric::ublas::lu_substitute(op,Permutation,result_cpu);
        copy (result_cpu, result[level]);

        #ifdef VIENNACL_AMG_DEBUG
        std::cout << "After direct solve: " << std::endl;
        printvector (result[level]);
        #endif

        for (level=tag_.get_coarselevels()-1; level >= 0; level--)
        {
          #ifdef VIENNACL_AMG_DEBUG
          std::cout << "Coarse Error: " << std::endl;
          printvector(result[level+1]);
          #endif

          // Interpolate error to fine level and correct solution.
          result[level] += viennacl::linalg::prod(P[level],result[level+1]);

          #ifdef VIENNACL_AMG_DEBUG
          std::cout << "Corrected Result: " << std::endl;
          printvector (result[level]);
          #endif

          // Apply Smoother postsmooth_ times.
          smooth_jacobi (level, tag_.get_postsmooth(), result[level], rhs[level]);

          #ifdef VIENNACL_AMG_DEBUG
          std::cout << "After postsmooth: " << std::endl;
          printvector (result[level]);
          #endif
        }
        vec = result[0];
      }

      /** @brief Jacobi Smoother (GPU version)
      * @param level       Coarse level to which smoother is applied to
      * @param iterations  Number of smoother iterations
      * @param x           The vector smoothing is applied to
      * @param rhs         The right hand side of the equation for the smoother
      */
      template<typename VectorType>
      void smooth_jacobi(int level, unsigned int iterations, VectorType & x, VectorType const & rhs) const
      {
        VectorType old_result = x;

        viennacl::ocl::context & ctx = const_cast<viennacl::ocl::context &>(viennacl::traits::opencl_handle(x).context());
        viennacl::linalg::opencl::kernels::compressed_matrix<ScalarType>::init(ctx);
        viennacl::ocl::kernel & k = ctx.get_kernel(viennacl::linalg::opencl::kernels::compressed_matrix<ScalarType>::program_name(), "jacobi");

        for (unsigned int i=0; i<iterations; ++i)
        {
          if (i > 0)
            old_result = x;
          x.clear();
          viennacl::ocl::enqueue(k(A[level].handle1().opencl_handle(), A[level].handle2().opencl_handle(), A[level].handle().opencl_handle(),
                                  static_cast<ScalarType>(tag_.get_jacobiweight()),
                                  viennacl::traits::opencl_handle(old_result),
                                  viennacl::traits::opencl_handle(x),
                                  viennacl::traits::opencl_handle(rhs),
                                  static_cast<cl_uint>(rhs.size())));

        }
      }

      amg_tag & tag() { return tag_; }
    };

  }
}

#endif


#include <Rcpp.h>

#include <RcppEigen.h>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>

#include "evolutionaryAlgorithm.hpp"
#include "AuxiliaryFunctions.hpp"

//[[Rcpp::export(.runningSinglePopulationEvolutionaryAlgorithm)]]
Rcpp::List runningSinglePopulationEvolutionaryAlgorithm(const std::size_t & numberOfMarkers,
                                                        const Eigen::VectorXd & numberOfAlleles,
                                                        const std::size_t & numberOfContributors,
                                                        const std::size_t & numberOfKnownContributors,
                                                        const Eigen::MatrixXd & knownProfiles,
                                                        const Eigen::MatrixXd & allKnownProfiles,
                                                        const Eigen::VectorXd & coverage,
                                                        const std::vector< std::vector < Eigen::MatrixXd > > & potentialParents,
                                                        const Eigen::VectorXd & markerImbalances,
                                                        const double & convexMarkerImbalanceInterpolation,
                                                        const Eigen::VectorXd & noiseParameters,
                                                        const Eigen::VectorXd & tolerance,
                                                        const double & theta,
                                                        const Eigen::VectorXd & alleleFrequencies,
                                                        const std::size_t & populationSize,
                                                        const std::size_t & numberOfIterations,
                                                        const std::size_t & numberOfIterationsEqualMinMax,
                                                        const std::size_t & numberOfFittestIndividuals,
                                                        const int & parentSelectionWindowSize,
                                                        const bool & allowParentSurvival,
                                                        const double & fractionEnsuredSurvival,
                                                        const double & crossoverProbability,
                                                        const double & mutationProbabilityLowerLimit,
                                                        const std::size_t & mutationIterations,
                                                        const double & mutationDegreesOfFreedom,
                                                        const Eigen::VectorXd mutationDecay,
                                                        const std::size_t hillClimbingIterations,
                                                        const std::size_t & seed,
                                                        const bool & trace,
                                                        const std::size_t & levelsOfStutterRecursion,
                                                        const bool & dualEstimation)
{
    ExperimentalSetup ES(numberOfMarkers, numberOfAlleles, numberOfContributors, numberOfKnownContributors, knownProfiles, allKnownProfiles,
                         coverage, potentialParents, markerImbalances, convexMarkerImbalanceInterpolation, noiseParameters, tolerance, theta,
                         alleleFrequencies, levelsOfStutterRecursion, dualEstimation);

    InitialPopulationRandomVariates InitialRV(numberOfAlleles, seed);

    EvolutionaryAlgorithm EA(ES, populationSize, numberOfIterations, numberOfIterationsEqualMinMax, numberOfFittestIndividuals, parentSelectionWindowSize, allowParentSurvival,
                             crossoverProbability, mutationProbabilityLowerLimit, mutationIterations, mutationDegreesOfFreedom, mutationDecay, fractionEnsuredSurvival,
                             hillClimbingIterations, InitialRV);

    RandomVariates RV(numberOfAlleles, numberOfContributors - numberOfKnownContributors, seed + 1);
    EA.Run(ES, RV, trace);

    Population FittestIndividuals = EA.FittestMembersOfEntireRun;
    Rcpp::List RL = FittestIndividuals.ReturnRcppList(ES);

    return Rcpp::List::create(Rcpp::Named("U") = RL,
                              Rcpp::Named("UMaxSize") = numberOfFittestIndividuals,
                              Rcpp::Named("NumberOfIterations") = EA.Iteration,
                              Rcpp::Named("NumberOfIterationsEqualMax") = numberOfIterationsEqualMinMax,
                              Rcpp::Named("NumberOfPopulations") = 1);
}

//[[Rcpp::export(.initialisingParallelEvolutionaryAlgorithm)]]
Rcpp::List initialisingParallelEvolutionaryAlgorithm(const std::size_t & numberOfMarkers,
                                                     const Eigen::VectorXd & numberOfAlleles,
                                                     const std::size_t & numberOfContributors,
                                                     const std::size_t & numberOfKnownContributors,
                                                     const Eigen::MatrixXd & knownProfiles,
                                                     const Eigen::MatrixXd & allKnownProfiles,
                                                     const Eigen::VectorXd & coverage,
                                                     const std::vector< std::vector < Eigen::MatrixXd > > & potentialParents,
                                                     const Eigen::VectorXd & markerImbalances,
                                                     const double & convexMarkerImbalanceInterpolation,
                                                     const Eigen::VectorXd & noiseParameters,
                                                     const Eigen::VectorXd & tolerance,
                                                     const double & theta,
                                                     const Eigen::VectorXd & alleleFrequencies,
                                                     const std::size_t & populationSize,
                                                     const std::size_t & seed,
                                                     const std::size_t & levelsOfStutterRecursion,
                                                     const bool & dualEstimation)
{
    ExperimentalSetup ES(numberOfMarkers, numberOfAlleles, numberOfContributors, numberOfKnownContributors, knownProfiles, allKnownProfiles,
                         coverage, potentialParents, markerImbalances, convexMarkerImbalanceInterpolation, noiseParameters, tolerance,
                         theta, alleleFrequencies, levelsOfStutterRecursion, dualEstimation);

    InitialPopulationRandomVariates InitialRV(numberOfAlleles, seed);
    EvolutionaryAlgorithm EA(ES, populationSize, InitialRV);

    Rcpp::List RL = EA.CurrentPopulation.ReturnCompressedRcppList();
    return RL;
}

//[[Rcpp::export(.runningParallelEvolutionaryAlgorithm)]]
Rcpp::List runningParallelEvolutionaryAlgorithm(const std::size_t & numberOfMarkers,
                                                const Eigen::VectorXd & numberOfAlleles,
                                                const std::size_t & numberOfContributors,
                                                const std::size_t & numberOfKnownContributors,
                                                const Eigen::MatrixXd & knownProfiles,
                                                const Eigen::MatrixXd & allKnownProfiles,
                                                const Eigen::VectorXd & coverage,
                                                const std::vector< std::vector < Eigen::MatrixXd > > & potentialParents,
                                                const Eigen::VectorXd & markerImbalances,
                                                const double & convexMarkerImbalanceInterpolation,
                                                const Eigen::VectorXd & noiseParameters,
                                                const Eigen::VectorXd & tolerance,
                                                const double & theta,
                                                const Eigen::VectorXd & alleleFrequencies,
                                                const std::size_t & numberOfIterations,
                                                const std::size_t & numberOfIterationsEqualMinMax,
                                                const std::size_t & numberOfFittestIndividuals,
                                                const int & parentSelectionWindowSize,
                                                const bool allowParentSurvival,
                                                const double & fractionEnsuredSurvival,
                                                const double & crossoverProbability,
                                                const double & mutationProbabilityLowerLimit,
                                                const std::size_t & mutationIterations,
                                                const double & mutationDegreesOfFreedom,
                                                const Eigen::VectorXd & mutationDecay,
                                                const std::size_t & hillClimbingIterations,
                                                const std::size_t & seed,
                                                const bool & trace,
                                                const Eigen::MatrixXd encodedPopulationList,
                                                const Eigen::MatrixXd sampleParametersList,
                                                const Eigen::MatrixXd noiseParametersList,
                                                const Eigen::MatrixXd mixtureParametersList,
                                                const Eigen::MatrixXd markerParametersList,
                                                const Eigen::VectorXd fitnessList,
                                                const std::size_t & levelsOfStutterRecursion,
                                                const bool & dualEstimation)
{
    ExperimentalSetup ES(numberOfMarkers, numberOfAlleles, numberOfContributors, numberOfKnownContributors, knownProfiles, allKnownProfiles,
                         coverage, potentialParents, markerImbalances, convexMarkerImbalanceInterpolation, noiseParameters, tolerance,
                         theta, alleleFrequencies, levelsOfStutterRecursion, dualEstimation);

    Population currentPopulation(encodedPopulationList, sampleParametersList, noiseParametersList, mixtureParametersList, markerParametersList, fitnessList, ES);

    EvolutionaryAlgorithm EA(ES, currentPopulation,
                             numberOfIterations, numberOfIterationsEqualMinMax, numberOfFittestIndividuals,
                             parentSelectionWindowSize, allowParentSurvival,
                             crossoverProbability, mutationProbabilityLowerLimit, mutationIterations,
                             mutationDegreesOfFreedom, mutationDecay, fractionEnsuredSurvival, hillClimbingIterations);

    RandomVariates RV(numberOfAlleles, numberOfContributors - numberOfKnownContributors, seed);
    EA.Run(ES, RV, trace);

    Population FittestIndividuals = EA.FittestMembersOfEntireRun;

    Rcpp::List RL = EA.CurrentPopulation.ReturnCompressedRcppList();
    Rcpp::List FL = FittestIndividuals.ReturnRcppList(ES);

    return Rcpp::List::create(Rcpp::Named("LastPopulation") = RL,
                              Rcpp::Named("FittestIndividuals") = FL);
}


#define BOOST_TEST_MODULE BASIC_TESTS

#include "../Sources/Internal/Crypto/Base62/Base62.hpp"
#include "../Sources/Internal/Crypto/SHA256/SHA256.hpp"
#include "../Sources/Internal/Crypto/CRC32/CRC32.hpp"
#include "../Sources/Internal/URL/URL.hpp"

#include <unistd.h>

#include <chrono>
#include <ctime>
#include <random>
#include <string>

#include <boost/test/unit_test.hpp>
#include <string>

namespace Helpers {
	namespace {
		inline uint64_t generateRandomNumber() {
			std::random_device rd;
			std::mt19937::result_type seed =
				rd() ^
				((std::mt19937::result_type)std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count() +
				 (std::mt19937::result_type)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now().time_since_epoch())
					 .count());

			std::mt19937 gen(seed);
			std::uniform_int_distribution<unsigned> distrib(1, 6);

			return static_cast<uint64_t>(distrib(gen));
		}

		inline std::string generateRandomString() {
			static const char base62CharSet[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
			std::string randomString;
			randomString.reserve(20);

			for (int i = 0; i < 20; ++i) {
				randomString += base62CharSet[rand() % (sizeof(base62CharSet) - 1)];
			}

			return randomString;
		}
	} /* namespace */
} /* namespace Helpers */

BOOST_AUTO_TEST_CASE(CRC32_ENCODER_RANDOM_TEST_SUCCESS) {
	CRC32 crc32;
	const auto randomStr = Helpers::generateRandomString();
	const auto res = crc32.encode(randomStr);
	BOOST_CHECK(res > 0);
}

BOOST_AUTO_TEST_CASE(Base62_DECODER_RANDOM_TEST_SUCCESS) {
	Base62 base62;
	const auto randomStr = Helpers::generateRandomString();
	const auto res = base62.decode(randomStr);
	BOOST_CHECK(res > 0);
}

BOOST_AUTO_TEST_CASE(Base62_ENCODER_RANDOM_TEST_SUCCESS) {
	Base62 base62;
	const auto randomNum = Helpers::generateRandomNumber();
	const auto res = base62.encode(randomNum);
	BOOST_CHECK(!res.empty());
}

BOOST_AUTO_TEST_CASE(Base62_BACKWARD_TEST_SUCCESS) {
	Base62 base62;
	bool conversionSuccess = true;

	for (int i = 0; i < 1e5; ++i) {
		const auto randomNum = Helpers::generateRandomNumber();
		const auto enc = base62.encode(randomNum);
		const auto dec = base62.decode(enc);
		conversionSuccess = dec == randomNum;
		if (!conversionSuccess) {
			break;
		}
	}

	BOOST_CHECK(conversionSuccess);
}

BOOST_AUTO_TEST_CASE(SHA256_ENCODER_RANDOM_TEST_SUCCESS) {
	SHA256 sha256;
	const auto randomStr = Helpers::generateRandomString();
	const auto res = sha256.encode(randomStr);
	BOOST_CHECK(!res.empty());
}

BOOST_AUTO_TEST_CASE(URL_ENCODER_RANDOM_TEST_SUCCESS) {
	URL url;
	const auto randomStr = Helpers::generateRandomString();
	const auto res = url.encode(randomStr);
	BOOST_CHECK(!res.empty());
}

BOOST_AUTO_TEST_CASE(URL_DECODER_RANDOM_TEST_SUCCESS) {
	URL url;
	const auto randomStr = Helpers::generateRandomString();

	BOOST_CHECK_THROW(url.decode(randomStr), boost::wrapexcept<std::range_error>);
}

BOOST_AUTO_TEST_CASE(URL_BACKWARD_TEST_SUCCESS) {
	URL url;
	bool conversionSuccess = true;

	for (int i = 0; i < 1e5; ++i) {
		const auto randomStr = Helpers::generateRandomString();
		const auto enc = url.encode(randomStr);
		const auto dec = url.encode(randomStr);
		conversionSuccess = enc == dec;
		if (!conversionSuccess) {
			break;
		}
	}

	BOOST_CHECK(conversionSuccess);
}


#include <Eigen/Dense>

int main()

{
	Eigen::MatrixXd d;
	return 0;
}


#include <random>
#include "gtest/gtest.h"
#include "gflags/gflags.h"
#include "boost/filesystem.hpp"
#include <tbb/tbb.h>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include "bakuage/sound_quality2.h"

DEFINE_string(sound_quality2_analysis_data, "../bakuage_dataset1/analysis", "sound quality2 analysis data dir (bakuage_dataset1/analysis)");
DEFINE_int32(sound_quality2_max_test_data, 0, "max test data count for sound quality2 test");
DEFINE_int32(sound_quality2_cv_folds, 5, "cv folds for sound quality2 test");
DEFINE_int32(sound_quality2_cv_count, 0, "cv count for sound quality2 test");
DEFINE_int32(sound_quality2_worker_count, 0, "worker count for sound quality2 test");

namespace {
    struct Reference {
        Eigen::VectorXd mean;
        Eigen::MatrixXd covariance;
    };

    class Stats {
    public:
        void Add(double sound_quality, double base_sound_quality) {
            change_statistics_.Add(sound_quality - base_sound_quality);
            sound_qualities_.push_back(sound_quality);
            for (auto parent: parents_) {
                parent->Add(sound_quality, base_sound_quality);
            }
        }
        void Add(const Stats &other) {
            sound_qualities_.insert(sound_qualities_.end(), other.sound_qualities_.begin(), other.sound_qualities_.end());
            change_statistics_.Add(other.change_statistics_);
            for (auto parent: parents_) {
                parent->Add(other);
            }
        }
        void AddParent(Stats *parent) {
            parents_.push_back(parent);
        }

        const std::string summary(const Stats &base_stats) const {
            std::stringstream ss;
            ss << "auc:" << auc(base_stats) << "\tmean:" << change_statistics_.mean() << "\tstddev:" << change_statistics_.stddev();
            return ss.str();
        }
        const double auc(const Stats &base_stats) const {
            return bakuage::CalcAUC(sound_qualities_.begin(), sound_qualities_.end(), base_stats.sound_qualities_.begin(), base_stats.sound_qualities_.end());
        }
        const bakuage::Statistics &change_statistics() const { return change_statistics_; }
    private:
        std::vector<float> sound_qualities_;
        bakuage::Statistics change_statistics_;
        std::vector<Stats *> parents_;
    };
}

TEST(SoundQuality2Calculator, ReferenceVectorIndex) {
    Eigen::VectorXd mean = Eigen::VectorXd::Zero(4);
    Eigen::MatrixXd covariance = Eigen::MatrixXd::Zero(4, 4);
    bakuage::MasteringReference2 reference(mean, covariance);
    for (int i = 0; i < 4; i++) {
        EXPECT_EQ(reference.mean_idx(i), i);
    }
    int k = 4;
    for (int i = 0; i < 4; i++) {
        for (int j = i; j < 4; j++) {
            EXPECT_EQ(reference.covariance_idx(i, j), k);
            k++;
        }
    }
}

TEST(SoundQuality2Calculator, SerializationCalculate) {
    using boost::filesystem::recursive_directory_iterator;

    std::stringstream ss;
    std::string first_reference_json;
    {
        bakuage::SoundQuality2Calculator calculator;
        recursive_directory_iterator last;
        std::vector<std::string> paths;
        for (recursive_directory_iterator itr("resource/analysis_data"); itr != last; ++itr) {
            const std::string path = itr->path().string();
            if (!bakuage::StrEndsWith(path, ".json")) continue;
            paths.emplace_back(path);

            if (first_reference_json.empty()) {
                first_reference_json = bakuage::LoadStrFromFile(path.c_str());
            }
        }
        calculator.PrepareFromPaths(paths.begin(), paths.end());
        boost::archive::binary_oarchive oa(ss);
        oa << calculator;
    }

    bakuage::SoundQuality2Calculator calculator2;
    {
        boost::archive::binary_iarchive ia(ss);
        ia >> calculator2;
    }

    Eigen::VectorXd mean;
    Eigen::MatrixXd covariance;
    EXPECT_TRUE(bakuage::SoundQuality2CalculatorUnit::ParseReference(first_reference_json.c_str(), &mean, &covariance));

    float sound_quality, lof;
    calculator2.CalculateSoundQuality(mean, covariance, &sound_quality, &lof);

    EXPECT_GE(sound_quality, 0);
    EXPECT_LE(sound_quality, 1);
}

// windowsでファイル経由でキャッシュが読み込めないバグの再現テスト
TEST(SoundQuality2Calculator, SerializationFile) {
	using boost::filesystem::recursive_directory_iterator;

	const auto temp_path = bakuage::NormalizeToString((boost::filesystem::temp_directory_path() / boost::filesystem::unique_path()).native());
	{
		bakuage::SoundQuality2Calculator calculator;
		recursive_directory_iterator last;
		std::vector<std::string> paths;
		for (recursive_directory_iterator itr("resource/analysis_data"); itr != last; ++itr) {
			const std::string path = itr->path().string();
			if (!bakuage::StrEndsWith(path, ".json")) continue;
			paths.emplace_back(path);
		}
		calculator.PrepareFromPaths(paths.begin(), paths.end());
		std::ofstream ofs(temp_path, std::ios::binary);
		boost::archive::binary_oarchive oa(ofs);
		oa << calculator;
	}

	bakuage::SoundQuality2Calculator calculator2;
	{
		std::ifstream ifs(temp_path, std::ios::binary);
		boost::archive::binary_iarchive ia(ifs);
		ia >> calculator2;
	}
}

// オプションが必要なテスト
// heavy test
TEST(SoundQuality2Calculator, Validation) {
    typedef bakuage::SoundQuality2Calculator Calculator;
    using boost::filesystem::recursive_directory_iterator;

    std::vector<Reference> references;
    std::vector<bakuage::SoundQuality2CalculatorUnit::Band> bands;
    {
        recursive_directory_iterator last;
        std::vector<std::string> paths;
        for (recursive_directory_iterator itr(FLAGS_sound_quality2_analysis_data); itr != last; ++itr) {
            const std::string path = itr->path().string();
            if (!bakuage::StrEndsWith(path, ".json")) continue;
            paths.emplace_back(path);
            if (FLAGS_sound_quality2_max_test_data && paths.size() >= FLAGS_sound_quality2_max_test_data) {
                break;
            }
        }
        references.resize(paths.size());
        tbb::parallel_for<int>(0, references.size(), [&paths, &references, &bands](int i) {
            std::ifstream ifs(paths[i].c_str());
            const std::string json_str((std::istreambuf_iterator<char>(ifs)),
                                       std::istreambuf_iterator<char>());
            Eigen::VectorXd mean;
            Eigen::MatrixXd covariance;
            Reference reference;
            EXPECT_TRUE(bakuage::SoundQuality2CalculatorUnit::ParseReference(json_str.c_str(), &reference.mean, &reference.covariance, i == 0 ? &bands : nullptr));
            references[i] = reference;
        });
    }

    // データセットを用意
    std::mt19937 engine(1); // seed固定
    std::shuffle(references.begin(), references.end(), engine);

    // CVを行う
    const int cv_folds = FLAGS_sound_quality2_cv_folds;
    const int cv_count = FLAGS_sound_quality2_cv_count ? FLAGS_sound_quality2_cv_count : cv_folds;
    Stats total_stats;
    Stats base_stats;
    Stats mid_mean_stats;
    Stats side_mean_stats;
    Stats mid_cov_diag_stats;
    Stats side_cov_diag_stats;
    Stats mid_cov_non_diag_stats;
    Stats other_cov_non_diag_stats;
    mid_mean_stats.AddParent(&total_stats);
    side_mean_stats.AddParent(&total_stats);
    mid_cov_diag_stats.AddParent(&total_stats);
    side_cov_diag_stats.AddParent(&total_stats);
    mid_cov_non_diag_stats.AddParent(&total_stats);
    other_cov_non_diag_stats.AddParent(&total_stats);

    // CircleCIでメモリ使いすぎで落ちるので並列数を制限する
    tbb::task_scheduler_init tbb_init(FLAGS_sound_quality2_worker_count ? FLAGS_sound_quality2_worker_count : tbb::task_scheduler_init::default_num_threads());
    for (int cv_i = 0; cv_i < cv_count; cv_i++) {
        // 全てのreference、全ての特徴量に対して、1dB変化させたときのsound_qualityを計算する
        // スペクトル平均、共分散(対角)、共分散(非対角)に分類して統計を取り
        // 平均と標準偏差をvalidationする

        const int test_start = (int64_t)cv_i * references.size() / cv_folds;
        const int test_end = (int64_t)(cv_i + 1) * references.size() / cv_folds;

        Calculator calculator;
        {
            std::vector<bakuage::MasteringReference2> train_references;
            for (int i = 0; i < references.size(); i++) {
                if (i < test_start || test_end <= i) {
                    train_references.emplace_back(references[i].mean, references[i].covariance);
                }
            }
            calculator.Prepare(train_references.begin(), train_references.end(), bands.begin(), bands.end());
        }

        const bakuage::AlignedPodVector<int> changes({-1, 1});

        std::mutex mtx;
        tbb::parallel_for(test_start, test_end, [&changes, &references, &calculator, &base_stats, &mid_mean_stats, &side_mean_stats, &mid_cov_diag_stats, &side_cov_diag_stats, &mid_cov_non_diag_stats, &other_cov_non_diag_stats, &mtx](int i) {
            Stats local_base_stats;
            Stats local_mid_mean_stats;
            Stats local_side_mean_stats;
            Stats local_mid_cov_diag_stats;
            Stats local_side_cov_diag_stats;
            Stats local_mid_cov_non_diag_stats;
            Stats local_other_cov_non_diag_stats;

            for (int k = 0; k < changes.size(); k++) {
                const bakuage::MasteringReference2 reference(references[i].mean, references[i].covariance);
                float base_sound_quality;
                calculator.CalculateSoundQuality(reference, &base_sound_quality, nullptr);
                local_base_stats.Add(base_sound_quality, base_sound_quality);
                for (int mean_idx = 0; mean_idx < reference.mean_count(); mean_idx++) {
                    auto changed_mean = references[i].mean;
                    changed_mean[mean_idx] += changes[k];
                    const bakuage::MasteringReference2 changed_reference(changed_mean, references[i].covariance);
                    float sound_quality;
                    calculator.CalculateSoundQuality(changed_reference, &sound_quality, nullptr);
                    if (mean_idx % 2 == 0) {
                        local_mid_mean_stats.Add(sound_quality, base_sound_quality);
                    } else {
                        local_side_mean_stats.Add(sound_quality, base_sound_quality);
                    }
                }
                for (int cov_idx1 = 0; cov_idx1 < reference.mean_count(); cov_idx1++) {
                    for (int cov_idx2 = cov_idx1; cov_idx2 < reference.mean_count(); cov_idx2++) {
                        auto changed_cov = references[i].covariance;
                        changed_cov(cov_idx1, cov_idx2) = bakuage::SignedSqr(bakuage::SignedSqrt(changed_cov(cov_idx1, cov_idx2)) + changes[k]);
                        changed_cov(cov_idx2, cov_idx1) = changed_cov(cov_idx1, cov_idx2);
                        const bakuage::MasteringReference2 changed_reference(references[i].mean, changed_cov);
                        float sound_quality;
                        calculator.CalculateSoundQuality(changed_reference, &sound_quality, nullptr);
                        if (cov_idx1 == cov_idx2) {
                            if (cov_idx1 % 2 == 0) {
                                local_mid_cov_diag_stats.Add(sound_quality, base_sound_quality);
                            } else {
                                local_side_cov_diag_stats.Add(sound_quality, base_sound_quality);
                            }
                        } else {
                            if (cov_idx1 % 2 == 0 && cov_idx2 % 2 == 0) {
                                local_mid_cov_non_diag_stats.Add(sound_quality, base_sound_quality);
                            } else {
                                local_other_cov_non_diag_stats.Add(sound_quality, base_sound_quality);
                            }
                        }
                    }
                }
            }

            {
                std::lock_guard<std::mutex> lock(mtx);
                base_stats.Add(local_base_stats);
                mid_mean_stats.Add(local_mid_mean_stats);
                side_mean_stats.Add(local_side_mean_stats);
                mid_cov_diag_stats.Add(local_mid_cov_diag_stats);
                side_cov_diag_stats.Add(local_side_cov_diag_stats);
                mid_cov_non_diag_stats.Add(local_mid_cov_non_diag_stats);
                other_cov_non_diag_stats.Add(local_other_cov_non_diag_stats);
            }
        });
    }

    EXPECT_GE(mid_mean_stats.auc(base_stats), 0.505);
    EXPECT_LE(mid_mean_stats.change_statistics().mean(), -0.007);
    EXPECT_LE(mid_mean_stats.change_statistics().stddev(), 0.023);

    EXPECT_GE(side_mean_stats.auc(base_stats), 0.507);
    EXPECT_LE(side_mean_stats.change_statistics().mean(), -0.009);
    EXPECT_LE(side_mean_stats.change_statistics().stddev(), 0.0283);

    EXPECT_GE(mid_cov_diag_stats.auc(base_stats), 0.51);
    EXPECT_LE(mid_cov_diag_stats.change_statistics().mean(), -0.0138);
    EXPECT_LE(mid_cov_diag_stats.change_statistics().stddev(), 0.028);

    EXPECT_GE(side_cov_diag_stats.auc(base_stats), 0.516);
    EXPECT_LE(side_cov_diag_stats.change_statistics().mean(), -0.0167);
    EXPECT_LE(side_cov_diag_stats.change_statistics().stddev(), 0.026);

    EXPECT_GE(mid_cov_non_diag_stats.auc(base_stats), 0.519);
    EXPECT_LE(mid_cov_non_diag_stats.change_statistics().mean(), -0.0192);
    EXPECT_LE(mid_cov_non_diag_stats.change_statistics().stddev(), 0.039);

    EXPECT_GE(other_cov_non_diag_stats.auc(base_stats), 0.524);
    EXPECT_LE(other_cov_non_diag_stats.change_statistics().mean(), -0.0245);
    EXPECT_LE(other_cov_non_diag_stats.change_statistics().stddev(), 0.043);

    EXPECT_GE(total_stats.auc(base_stats), 0.521);
    EXPECT_LE(total_stats.change_statistics().mean(), -0.0212);
    EXPECT_LE(total_stats.change_statistics().stddev(), 0.04);

    std::cerr << "mid mean\t" << mid_mean_stats.summary(base_stats) << std::endl;
    std::cerr << "side mean\t" << side_mean_stats.summary(base_stats) << std::endl;
    std::cerr << "mid cov_diag\t" << mid_cov_diag_stats.summary(base_stats) << std::endl;
    std::cerr << "side cov_diag\t" << side_cov_diag_stats.summary(base_stats) << std::endl;
    std::cerr << "mid cov_non_diag\t" << mid_cov_non_diag_stats.summary(base_stats) << std::endl;
    std::cerr << "other cov_non_diag\t" << other_cov_non_diag_stats.summary(base_stats) << std::endl;
    std::cerr << "total\t" << total_stats.summary(base_stats) << std::endl;
}