/*
 * This file is part of So-bogus, a C++ sparse block matrix library and
 * Second Order Cone solver.
 *
 * Copyright 2013 Gilles Daviet <gdaviet@gmail.com>
 *
 * So-bogus is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.

 * So-bogus 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 So-bogus.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "MecheInterface.hpp"

#include "FrictionProblem.hpp"

#include "../Core/Block.impl.hpp"
#include "../Core/Block.io.hpp"

#include "../Core/BlockSolvers/GaussSeidel.hpp"
#include "../Core/BlockSolvers/ProductGaussSeidel.hpp"
#include "../Core/BlockSolvers/ProjectedGradient.hpp"
#include "../Core/BlockSolvers/Coloring.impl.hpp"

#include "../Core/BlockSolvers/ADMM.hpp"
#include "../Extra/SecondOrder.impl.hpp"

#include "../Core/Utils/Timer.hpp"

#include <algorithm>

#ifdef BOGUS_WITH_BOOST_SERIALIZATION
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>

#include <fstream>
#endif

namespace bogus
{

MecheFrictionProblem::Options::Options()
    : maxThreads(0), maxIters(0), cadouxIters(0),
      tolerance(0), useInfinityNorm( false ),
      algorithm( GaussSeidel ),
      gsRegularization( 0 ), gsColoring( false ),
      gsSkipIters( -1 ), // -1 means default
      tryZeroAsWell( true ),
      pgVariant( projected_gradient::SPG ),
      admmProjStepSize( 1 ), admmFpStepSize(1.e-3)
{
}

MecheFrictionProblem::MecheFrictionProblem()
    : m_primal( BOGUS_NULL_PTR(PrimalFrictionProblem<3u>) ),
      m_dual( BOGUS_NULL_PTR(DualFrictionProblem<3u>) ),
        m_lastSolveTime( 0 ),
        m_f( BOGUS_NULL_PTR(double) ),
        m_w( BOGUS_NULL_PTR(double) ),
        m_mu( BOGUS_NULL_PTR(double) ),
        m_out( &std::cout )
{
}

MecheFrictionProblem::~MecheFrictionProblem()
{
	destroy() ;
}

void MecheFrictionProblem::destroy()
{
	delete[] m_f ;
	m_f = BOGUS_NULL_PTR(double) ;
	delete[] m_w ;
	m_w = BOGUS_NULL_PTR(double) ;
	delete[] m_mu ;
	m_mu = BOGUS_NULL_PTR(double) ;
	delete m_primal ;
	m_primal = BOGUS_NULL_PTR(PrimalFrictionProblem<3u>) ;
	delete m_dual ;
	m_dual =  BOGUS_NULL_PTR(DualFrictionProblem<3u>) ;
}

void MecheFrictionProblem::ackCurrentResidual( unsigned GSIter, double err )
{
	if( m_out )
	{
		*m_out << "Finished iteration " << GSIter
		         << " with residual " << err
		         << std::endl ;
	}
	m_callback.trigger( GSIter, err, m_timer.elapsed() );
}

void MecheFrictionProblem::reset ()
{
	destroy() ;

	m_primal = new PrimalFrictionProblem<3u>() ;
	m_lastSolveTime = 0 ;
}

void MecheFrictionProblem::fromPrimal (
        unsigned int NObj, //!< number of subsystems
        const unsigned int * ndof, //!< array of size \a NObj, the number of degree of freedom of each subsystem
        const double *const * MassMat, //!< array of pointers to the mass matrix of each subsystem
        const double * f_in, //!< the constant term in \f$ M v + f= {}^t \! H r \f$
        unsigned int n_in, //!< number of contact points
        const double * mu_in, //!< array of size \a n giving the friction coeffs
        const double * E_in, //!< array of size \f$ n \times d \times d \f$ giving the \a n normals followed by the \a n tangent vectors (and by again \a n tangent vectors if \a d is 3). Said otherwise, \a E is a \f$ (nd) \times d \f$ matrix, stored column-major, formed by \a n blocks of size \f$ d \times d \f$ with each block being an orthogonal matrix (the transition matrix from the world space coordinates \f$ (x_1, x_2, x_3) \f$ to the local coordinates \f$ (x_N, x_{T1}, x_{T2}) \f$
        const double * w_in, //!< array of size \a nd, the constant term in \f$ u = H v + w \f$
        const int * const ObjA, //!< array of size \a n, the first object involved in the \a i-th contact (must be an internal object) (counted from 0)
        const int * const ObjB, //!< array of size \a n, the second object involved in the \a i-th contact (-1 for an external object) (counted from 0)
        const double *const HA[], //!< array of size \a n, containing pointers to a dense, colum-major matrix of size <c> d*ndof[ObjA[i]] </c> corresponding to the H-matrix of <c> ObjA[i] </c>
        const double *const HB[] //!< array of size \a n, containing pointers to a dense, colum-major matrix of size <c> d*ndof[ObjA[i]] </c> corresponding to the H-matrix of <c> ObjB[i] </c> (\c NULL for an external object)
        )
{
	reset() ;

	// Copy M
	// We don't actually need it after having computed a factorization of M, but we keep it around
	// in case we want to use dumpToFile()

	m_primal->M.reserve( NObj ) ;
	m_primal->M.setRows( NObj, ndof ) ;
	m_primal->M.setCols( NObj, ndof ) ;

	for( unsigned i = 0 ; i < NObj ; ++i )
	{
		m_primal->M.insertBack( i, i ) = Eigen::MatrixXd::Map( MassMat[i], ndof[i], ndof[i] ) ;
	}
	m_primal->M.finalize() ;

	// E
	Eigen::Map< const Eigen::Matrix< double, Eigen::Dynamic, 3 > > E_flat( E_in, 3*n_in, 3 ) ;

	m_primal->E.reserve( n_in ) ;
	m_primal->E.setRows( n_in ) ;
	m_primal->E.setCols( n_in ) ;
	for( unsigned i = 0 ; i < n_in ; ++i )
	{
		m_primal->E.insertBack( i, i ) = E_flat.block< 3,3 > ( 3*i, 0 ) ;
	}
	m_primal->E.finalize() ;
	m_primal->E.cacheTranspose() ;

	// Build H

	std::size_t Hnnz = 0 ;
#ifndef BOGUS_DONT_PARALLELIZE
#pragma omp parallel for reduction( +:Hnnz )
#endif
	for( std::ptrdiff_t i = 0 ; i < (std::ptrdiff_t) n_in ; ++i )
	{
		Hnnz += 3*ndof[ObjA[i]] ;
		if( ObjB[i] != -1 && ObjB[i] == ObjA[i] )
		{
			Hnnz += 3*ndof[ObjB[i]] ;
		}
	}

	m_primal->H.setRows( n_in ) ;
	m_primal->H.setCols( NObj, ndof ) ;
	m_primal->H.reserve( 2*n_in, Hnnz ) ;

#ifndef BOGUS_DONT_PARALLELIZE
#pragma omp parallel for
#endif
	for( std::ptrdiff_t i = 0 ; i < (std::ptrdiff_t) n_in ; ++i )
	{
		const Eigen::Matrix3d Et = m_primal->E.diagonal(i).transpose() ;
		if( ObjB[i] == -1 )
		{
			m_primal->H.insert( i, ObjA[i] ) =  Et *
			        Eigen::MatrixXd::Map( HA[i], 3, ndof[ ObjA[i] ] ) ;
		} else if( ObjB[i] == ObjA[i] )
		{
			m_primal->H.insert( i, ObjA[i] ) =  Et *
			        ( Eigen::MatrixXd::Map( HA[i], 3, ndof[ ObjA[i] ] ) -
			        Eigen::MatrixXd::Map( HB[i], 3, ndof[ ObjA[i] ] ) ) ;
		} else {
			m_primal->H.insert( i, ObjA[i] ) =  Et *
			        Eigen::MatrixXd::Map( HA[i], 3, ndof[ ObjA[i] ] ) ;
			m_primal->H.insert( i, ObjB[i] ) =  - Et *
			        Eigen::MatrixXd::Map( HB[i], 3, ndof[ ObjB[i] ] ) ;
		}
	}
	m_primal->H.finalize() ;

	m_primal->f = f_in ;
	m_primal->w = w_in ;
	m_primal->mu = mu_in ;

	m_primal->computeMInv();
}

unsigned MecheFrictionProblem::nDegreesOfFreedom() const
{
	return m_primal ? m_primal->M.rows() : 0u ;
}

unsigned MecheFrictionProblem::nContacts() const
{
	return m_primal ? m_primal->H.rowsOfBlocks() : 0u ;
}

void MecheFrictionProblem::computeDual( double regularization )
{
	delete m_dual ;
	m_dual = new DualFrictionProblem<3u>() ;
	m_dual->computeFrom( *m_primal );

	if( regularization > 0. )
	{
		m_dual->W += regularization * m_dual->W.Identity() ;
	}
}

double MecheFrictionProblem::solve(
        double *r,
        double *v,
        int maxThreads,
        double tol,
        unsigned maxIters,
        bool staticProblem,
        double regularization,
        bool useInfinityNorm,
        bool useProjectedGradient,
        unsigned cadouxIters
                                     )
{
	Options options ;
	double problemRegularization = 0 ;

	options.maxThreads = maxThreads ;
	options.maxIters = maxIters ;
	options.cadouxIters = cadouxIters ;

	options.tolerance = tol ;
	options.useInfinityNorm = useInfinityNorm ;

	if( useProjectedGradient )
		options.algorithm = ProjectedGradient ;

	if( staticProblem ) {
		problemRegularization = regularization ;
	} else {
		options.gsRegularization = regularization ;
	}

	return solve( r, v, options, staticProblem, problemRegularization ) ;
}

double MecheFrictionProblem::solve(
        double *r,
        double *v,
        const Options& options,
        bool staticProblem,
        double problemRegularization
        )
{
	assert( m_primal ) ;
	const unsigned m = m_primal->H.cols() ;
	const unsigned n = m_primal->H.rowsOfBlocks() ;

	// r to local coords
	Eigen::VectorXd r_loc = m_primal->E.transpose() * Eigen::VectorXd::Map( r, 3*n ) ;

	double res ;

	if( options.algorithm == ADMM || options.algorithm == DualAMA )
	{
		// Primal-dual solve

		Eigen::VectorXd v_data  ;
		if( !v ) {
			v_data = m_primal->MInv * ( m_primal->H.transpose() * r_loc -
			                            Eigen::VectorXd::Map( m_primal->f, m_primal->H.cols() ) ) ;
			v = v_data.data() ;
		}

		m_timer.reset();

		if( options.algorithm == DualAMA )
		{

			bogus::DualAMA< bogus::PrimalFrictionProblem<3u>::HType > dama ;
			dama.callback().connect( *this, &MecheFrictionProblem::ackCurrentResidual ) ;

			if( options.tolerance != 0. ) dama.setTol( options.tolerance );
			if( options.maxIters  != 0  ) dama.setMaxIters( options.maxIters );
			dama.useInfinityNorm( options.useInfinityNorm );

			dama.setLineSearchIterations( 0 );
			dama.setFpStepSize( options.admmFpStepSize );
			dama.setProjStepSize( options.admmProjStepSize );

			res = m_primal->solveWith( dama, v, r_loc.data(), staticProblem ) ;
		} else {
			bogus::ADMM< bogus::PrimalFrictionProblem<3u>::HType > admm ;
			admm.callback().connect( *this, &MecheFrictionProblem::ackCurrentResidual ) ;

			admm.setStepSize( options.admmProjStepSize );

			if( options.tolerance != 0. ) admm.setTol( options.tolerance );
			if( options.maxIters  != 0  ) admm.setMaxIters( options.maxIters );
			admm.useInfinityNorm( options.useInfinityNorm );

			res = m_primal->solveWith( admm, 0., v, r_loc.data() ) ;
		}

	} else {

		Signal< unsigned, double > callback ;
		callback.connect( *this, &MecheFrictionProblem::ackCurrentResidual );

		if( options.algorithm == MatrixFreeGaussSeidel )
		{
			PrimalFrictionProblem< 3u >::ProductGaussSeidelType gs ;
			if( options.tolerance   != 0. ) gs.setTol( options.tolerance );
			if( options.maxIters    != 0  ) gs.setMaxIters( options.maxIters );
			if( options.gsSkipIters >= 0  ) gs.setSkipIters( options.gsSkipIters );

			gs.useInfinityNorm( options.useInfinityNorm ) ;
			gs.setMaxThreads( options.maxThreads );
			gs.setAutoRegularization( options.gsRegularization ) ;
			gs.doTryZeroAsWell( options.tryZeroAsWell );

			gs.callback().connect( callback );
			res = m_primal->solveWith( gs, r_loc.data(), staticProblem ) ;

		} else {

			// If dual has not been computed yet
			if( !m_dual )
			{
				computeDual( problemRegularization );
			}

			// Proper solving

			m_timer.reset();
			if( options.algorithm == ProjectedGradient ) {

				DualFrictionProblem< 3u >::ProjectedGradientType pg ;
				if( options.tolerance != 0. ) pg.setTol( options.tolerance );
				if( options.maxIters  != 0  ) pg.setMaxIters( options.maxIters );

				pg.useInfinityNorm( options.useInfinityNorm ) ;
				pg.setDefaultVariant( options.pgVariant  );

				if( staticProblem || options.cadouxIters == 0 )
				{
					pg.callback().connect( callback );
					res = m_dual->solveWith( pg, r_loc.data(), staticProblem ) ;
				} else {
					res = m_dual->solveCadoux( pg, r_loc.data(), options.cadouxIters, &callback ) ;
				}
			} else {
				// Setup GS parameters
				bogus::DualFrictionProblem<3u>::GaussSeidelType gs ;

				if( options.tolerance   != 0. ) gs.setTol( options.tolerance );
				if( options.maxIters    != 0  ) gs.setMaxIters( options.maxIters );
				if( options.gsSkipIters >= 0  ) gs.setSkipIters( options.gsSkipIters );

				gs.setMaxThreads( options.maxThreads );
				gs.setAutoRegularization( options.gsRegularization ) ;
				gs.useInfinityNorm( options.useInfinityNorm ) ;

				m_dual->undoPermutation() ;

				const bool useColoring =
				        options.maxThreads != 1 && options.gsColoring ;
				gs.coloring().update( useColoring, m_dual->W );

				if( useColoring )
				{
					m_dual->applyPermutation( gs.coloring().permutation ) ;
					gs.coloring().resetPermutation();
				}
				m_dual->W.cacheTranspose() ;

				if( staticProblem || options.cadouxIters == 0 )
				{
					gs.doTryZeroAsWell( options.tryZeroAsWell );
					gs.callback().connect( callback );
					res = m_dual->solveWith( gs, r_loc.data(), staticProblem ) ;
				} else {
					gs.doTryZeroAsWell( false );
					res = m_dual->solveCadoux( gs, r_loc.data(), options.cadouxIters, &callback ) ;
				}

			}
		}

		// compute v
		if( v )
		{
			Eigen::VectorXd::Map( v, m ) = m_primal->MInv * (
			            m_primal->H.transpose() * r_loc -
			            Eigen::VectorXd::Map( m_primal->f, m_primal->H.cols() ) ) ;
		}
	}

	m_lastSolveTime = m_timer.elapsed() ;

	if( m_out && n != 0 )
	{
		*m_out << "Max coeff: " << r_loc.lpNorm< Eigen::Infinity >() << std::endl ;
	}

	// r to world coords
	Eigen::VectorXd::Map( r, 3*n ) = m_primal->E * r_loc ;

	return res ;
}

void MecheFrictionProblem::setOutStream( std::ostream *out )
{
	m_out = out ;
}

#ifdef BOGUS_WITH_BOOST_SERIALIZATION
bool MecheFrictionProblem::dumpToFile( const char* fileName, const double * r0 ) const
{
	static const int version = 1 ;

	if( !m_primal ) return false ;

	std::ofstream ofs( fileName );
	boost::archive::binary_oarchive oa(ofs);

	try{
		oa << version ;
		oa << m_primal->M << m_primal->H << m_primal->E ;
		oa << boost::serialization::make_array( m_primal->f , nDegreesOfFreedom() ) ;
		oa << boost::serialization::make_array( m_primal->w , 3 * nContacts() ) ;
		oa << boost::serialization::make_array( m_primal->mu, nContacts() ) ;
		bool has_r0 = r0 != 0 ;
		oa << has_r0 ; ;
		if( r0 )
		{
			oa << boost::serialization::make_array( r0, 3*nContacts() ) ;
		}

	} catch (std::exception &e) {
		std::cerr << "Error writing MecheFrictionProblem to "<< fileName << ":\n> " << e.what() << std::endl ;
		return false ;
	}

	return true ;
}

bool MecheFrictionProblem::fromFile( const char* fileName, double *& r0, bool old )
{
	std::ifstream ifs( fileName );
	if( !ifs.is_open() ) return false ;

	reset() ;

	boost::archive::binary_iarchive ia(ifs);

	try{
		if( old ) {
			bogus::SparseBlockMatrix< PrimalFrictionProblem<3>::HBlock, UNCOMPRESSED > Hold ;
			ia >> m_primal->M >> Hold >> m_primal->E ;
			m_primal->H = Hold ;
		} else {
			int version ;
			ia >> version ;
			ia >> m_primal->M >> m_primal->H >> m_primal->E ;
		}
	} catch (std::exception &e) {
		std::cerr << "Error reading MecheFrictionProblem from "<< fileName << ":\n> " << e.what() << std::endl ;
		return false ;
	}

	m_f  = new double[ nDegreesOfFreedom() ] ;
	m_w  = new double[ 3 * nContacts() ] ;
	m_mu = new double[ nContacts() ] ;

	r0 = new double[ 3 * nContacts() ] ;

	std::cout << fileName << ": " << nDegreesOfFreedom() << " dofs, " << nContacts() << " contacts" << std::endl ;

	try{
		ia >> boost::serialization::make_array( m_f , nDegreesOfFreedom() ) ;
		ia >> boost::serialization::make_array( m_w , 3 * nContacts() ) ;
		ia >> boost::serialization::make_array( m_mu, nContacts() ) ;

		bool has_r0 ;
		ia >> has_r0 ;
		if ( has_r0 ) {
			ia >> boost::serialization::make_array( r0, 3*nContacts() ) ;
		} else {
			Eigen::VectorXd::Map( r0, 3*nContacts() ).setZero() ;
		}

	} catch (std::exception &e) {
		std::cerr << "Error reading MecheFrictionProblem from "<< fileName << ":\n> " << e.what() << std::endl ;
		delete m_f ;
		delete m_w ;
		delete m_mu ;
		delete r0 ;
		r0 = m_f = m_w = m_mu = BOGUS_NULL_PTR( double ) ;
		return false ;
	}

	m_primal->f  = m_f ;
	m_primal->w  = m_w ;
	m_primal->mu = m_mu ;

	m_primal->computeMInv();

	return true ;
}

#else
bool MecheFrictionProblem::dumpToFile( const char*, const double* ) const
{
	std::cerr << "MecheInterface::dumpToFile: Error, bogus compiled without serialization capabilities" ;
	return false ;
}
bool MecheFrictionProblem::fromFile( const char* , double *& , bool )
{
	std::cerr << "MecheInterface::fromFile: Error, bogus compiled without serialization capabilities" ;
	return false ;
}
#endif

}


// Copyright 2004, 2005 The Trustees of Indiana University.

// Use, modification and distribution is 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)

//  Authors: Nick Edmonds
//           Andrew Lumsdaine
#ifndef BOOST_GRAPH_DISTRIBUTED_RMAT_GENERATOR_HPP
#define BOOST_GRAPH_DISTRIBUTED_RMAT_GENERATOR_HPP

#ifndef BOOST_GRAPH_USE_MPI
#error "Parallel BGL files should not be included unless <boost/graph/use_mpi.hpp> has been included"
#endif

#include <boost/graph/parallel/algorithm.hpp>
#include <boost/graph/parallel/process_group.hpp>
#include <math.h>

namespace boost {

  // Memory-scalable (amount of memory required will scale down
  // linearly as the number of processes increases) generator, which
  // requires an MPI process group.  Run-time is slightly worse than
  // the unique rmat generator.  Edge list generated is sorted and
  // unique.
  template<typename ProcessGroup, typename Distribution,
           typename RandomGenerator, typename Graph>
  class scalable_rmat_iterator
  {
      typedef typename graph_traits<Graph>::directed_category directed_category;
      typedef typename graph_traits<Graph>::vertices_size_type vertices_size_type;
      typedef typename graph_traits<Graph>::edges_size_type edges_size_type;

  public:
      typedef std::input_iterator_tag iterator_category;
      typedef std::pair<vertices_size_type, vertices_size_type> value_type;
      typedef const value_type& reference;
      typedef const value_type* pointer;
      typedef void difference_type;

      // No argument constructor, set to terminating condition
      scalable_rmat_iterator()
        : gen(), done(true)
      { }

      // Initialize for edge generation
      scalable_rmat_iterator(ProcessGroup pg, Distribution distrib,
                             RandomGenerator& gen, vertices_size_type n,
                             edges_size_type m, double a, double b, double c,
                             double d, bool permute_vertices = true)
          : gen(), done(false)
      {
          assert(a + b + c + d == 1);
          int id = process_id(pg);

          this->gen.reset(new uniform_01<RandomGenerator>(gen));

          std::vector<vertices_size_type> vertexPermutation;
          if (permute_vertices)
              generate_permutation_vector(gen, vertexPermutation, n);

          int SCALE = int(floor(log(double(n))/log(2.)));
          boost::uniform_01<RandomGenerator> prob(gen);

          std::map<value_type, bool> edge_map;

          edges_size_type generated = 0, local_edges = 0;
          do {
              edges_size_type tossed = 0;
              do {
                  vertices_size_type u, v;
                  boost::tie(u, v) = generate_edge(this->gen, n, SCALE, a, b, c, d);

                  if (permute_vertices) {
                      u = vertexPermutation[u];
                      v = vertexPermutation[v];
                  }

                  // Lowest vertex number always comes first (this
                  // means we don't have to worry about i->j and j->i
                  // being in the edge list)
                  if (u > v && is_same<directed_category, undirected_tag>::value)
                      std::swap(u, v);

                  if (distrib(u) == id || distrib(v) == id) {
                      if (edge_map.find(std::make_pair(u, v)) == edge_map.end()) {
                          edge_map[std::make_pair(u, v)] = true;
                          local_edges++;
                      } else {
                          tossed++;

                          // special case - if both u and v are on same
                          // proc, ++ twice, since we divide by two (to
                          // cover the two process case)
                          if (distrib(u) == id && distrib(v) == id)
                              tossed++;
                      }
                  }
                  generated++;

              } while (generated < m);
              tossed = all_reduce(pg, tossed, boost::parallel::sum<vertices_size_type>());
              generated -= (tossed / 2);
          } while (generated < m);
          // NGE - Asking for more than n^2 edges will result in an infinite loop here
          //       Asking for a value too close to n^2 edges may as well

          values.reserve(local_edges);
          typename std::map<value_type, bool>::reverse_iterator em_end = edge_map.rend();
          for (typename std::map<value_type, bool>::reverse_iterator em_i = edge_map.rbegin();
               em_i != em_end ;
               ++em_i) {
              values.push_back(em_i->first);
          }

          current = values.back();
          values.pop_back();
      }

      reference operator*() const { return current; }
      pointer operator->() const { return &current; }

      scalable_rmat_iterator& operator++()
      {
          if (!values.empty()) {
              current = values.back();
              values.pop_back();
          } else
              done = true;

          return *this;
      }

      scalable_rmat_iterator operator++(int)
      {
          scalable_rmat_iterator temp(*this);
          ++(*this);
          return temp;
      }

      bool operator==(const scalable_rmat_iterator& other) const
      {
          return values.empty() && other.values.empty() && done && other.done;
      }

      bool operator!=(const scalable_rmat_iterator& other) const
      { return !(*this == other); }

  private:

      // Parameters
      shared_ptr<uniform_01<RandomGenerator> > gen;

      // Internal data structures
      std::vector<value_type> values;
      value_type              current;
      bool                    done;
  };

} // end namespace boost

#endif // BOOST_GRAPH_DISTRIBUTED_RMAT_GENERATOR_HPP


/*
 * Copyright 2012 Matthew Harvey
 *
 * 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 "decimal.hpp"
#include "assert.hpp"
#include "checked_arithmetic.hpp"
#include "decimal_exceptions.hpp"
#include "exception.hpp"
#include "num_digits.hpp"
#include <boost/numeric/conversion/cast.hpp>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <limits>
#include <numeric>
#include <vector>

using boost::numeric_cast;
using std::accumulate;
using std::find;
using std::numeric_limits;
using std::max_element;
using std::max;
using std::pow;
using std::vector;

namespace jewel
{

namespace
{

    /*
     * Hack: using this instead of static_cast because there appears to be a
     * bug in GCC 4.6.1 (at least on MinGW on Windows) when casting to
     * long long using static_cast or implicit cast.
     */
    template <typename Target>
    struct Converter
    {
        template <typename Source>
        static Target convert(Source p_source)
        {
            return p_source;
        }
    };

    /*
     * Specialize for conversions to long long to use boost::numeric cast to
     * avoid the bug.
     *
     * NOTE: Don't rely on the exceptions thrown by boost::numeric_cast.
     * If boost::numeric_cast is throws here, there's something wrong
     * with the program!
     */
    template <>
    struct Converter<long long>
    {
        template <typename Source>
        static long long convert(Source p_source)
        {
            return numeric_cast<long long>(p_source);
        }
    };

    template <typename Target, typename Source>
    Target num_cast(Source p_source);

    template <typename Target, typename Source>
    inline
    Target num_cast(Source p_source)
    {
        return Converter<Target>::template convert(p_source);
    }

}  // end anonymous namespace

/*
 * NOTE: Don't rely on the exceptions thrown by boost::numeric_cast.
 * If boost::numeric_cast is throws here, there's something wrong
 * with the program!
 */
#ifndef NDEBUG
#   define JEWEL_NUMERIC_CAST numeric_cast
#else
#   define JEWEL_NUMERIC_CAST num_cast      // faster
#endif

// initialize static data members

size_t const
Decimal::s_max_places = NumDigits::num_digits
(   numeric_limits<int_type>::min()
);

Decimal const
Decimal::s_maximum = Decimal(numeric_limits<int_type>::max(), 0);

Decimal const
Decimal::s_minimum = Decimal(numeric_limits<int_type>::min(), 0);

// static member functions

void Decimal::co_normalize(Decimal& x, Decimal& y)
{
    if (x.m_places == y.m_places)
    {
        // do nothing
    }
    else if (x.m_places < y.m_places)
    {
        if (x.rescale(y.m_places) != 0)
        {
            JEWEL_THROW
            (   DecimalRangeException,
                "Unsafe attempt to set fractional precision in course "
                "of co-normalization attempt."
            );
        }
    }
    else
    {
        JEWEL_ASSERT (y.m_places < x.m_places);
        if (y.rescale(x.m_places) != 0)
        {
            JEWEL_THROW
            (   DecimalRangeException,
                "Unsafe attempt to set fractional precision in course "
                "of co-normalization attempt."
            );
        }
    }
    return;
}

// some member functions

Decimal::Decimal(): m_places(0), m_intval(0)
{
}

Decimal::Decimal(int_type p_intval, places_type p_places):
    m_places(p_places),
    m_intval(p_intval)
{
    JEWEL_ASSERT (s_max_places == maximum_precision());
    if (m_places > s_max_places)
    {
        // There is no point setting m_intval and m_places to 0 (or any other
        // other valid value) here, since the Decimal instance is not going
        // to be created anyway - nothing will be able to refer to it after
        // this exception is thrown.
        JEWEL_THROW
        (   DecimalRangeException,
            "Attempt to construct Decimal with precision greater"
            " than maximum precision."
        );
    }
}

void
Decimal::rationalize(places_type min_places)
{
    JEWEL_ASSERT (s_base > 0);
    JEWEL_ASSERT (!remainder_is_unsafe(m_intval, s_base));
    while ((m_places > min_places) && (m_intval % s_base == 0))
    {
        JEWEL_ASSERT (!division_is_unsafe(m_intval, s_base));
        m_intval /= s_base;
        JEWEL_ASSERT (m_places > 0);
        --m_places;
    }
    return;
}

int Decimal::rescale(places_type p_places)
{
    #ifndef NDEBUG
        places_type const DEBUGVARIABLE_orig_places = m_places;
        int_type const DEBUGVARIABLE_orig_intval = m_intval;
    #endif

    if (m_places == p_places)
    {
        return 0;
    }

    // remember sign
    bool const is_positive = (m_intval > 0);
    #ifndef NDEBUG
        bool const is_zero = (m_intval == 0);
        bool const is_negative = (m_intval < 0);
    #endif

    if (m_places < p_places)
    {
        if (p_places > s_max_places)
        {
            JEWEL_ASSERT (m_places == DEBUGVARIABLE_orig_places);
            JEWEL_ASSERT (m_intval == DEBUGVARIABLE_orig_intval);
            return 1;
        }
        JEWEL_ASSERT (p_places <= s_max_places);
        // This should never cause overflow, as p_places is never greater
        // than s_max_places, and s_base raised to a number equal to or
        // greater than p_places will always be less than
        // numeric_limits<int_type>::max(), given that s_max_places is
        // equal to the number of digits in numeric_limits<int_type>::min().
        JEWEL_ASSERT (!subtraction_is_unsafe(p_places, m_places));
        int_type multiplier = JEWEL_NUMERIC_CAST<int_type>
        (   pow(s_base, p_places - m_places)
        );

        if (multiplication_is_unsafe(m_intval, multiplier))
        {
            JEWEL_ASSERT (m_places == DEBUGVARIABLE_orig_places);
            JEWEL_ASSERT (m_intval == DEBUGVARIABLE_orig_intval);
            return 1;
         }
        m_intval *= multiplier;
    }
    else
    {
        JEWEL_ASSERT (p_places < m_places);

        // truncate all but one of the required places
        JEWEL_ASSERT (m_places > 0);
        for (unsigned int j = m_places - 1; j != p_places; --j)
        {
            JEWEL_ASSERT (!division_is_unsafe(m_intval, s_base));
            m_intval /= s_base;
        }

        // with one more place still to eliminate, we calculate
        // whether rounding is required
        JEWEL_ASSERT (!remainder_is_unsafe(m_intval, s_base));
        bool remainder =
        (   std::abs(m_intval % s_base) >=
            s_rounding_threshold
        );

        // now remove the remaining place
        JEWEL_ASSERT (!division_is_unsafe(m_intval, s_base));
        JEWEL_ASSERT (s_base > 1);
        m_intval /= s_base;
        JEWEL_ASSERT (m_intval < numeric_limits<int_type>::max());
        JEWEL_ASSERT (m_intval > numeric_limits<int_type>::min());

        // and add rounding if required
        if (remainder)
        {
            if (is_positive)
            {
                JEWEL_ASSERT
                (   !addition_is_unsafe(m_intval, static_cast<int_type>(1))
                );
                ++m_intval;
            }
            else
            {
                JEWEL_ASSERT (is_negative);
                JEWEL_ASSERT (!is_zero);
                JEWEL_ASSERT
                (   !subtraction_is_unsafe(m_intval, static_cast<int_type>(1))
                );
                --m_intval;
            }
        }
    }
    m_places = p_places;
    return 0;
}

Decimal::int_type
Decimal::implicit_divisor() const
{
    static bool calculated_already = false;
    static vector<int_type> divisor_lookup(1, 1);
    while (!calculated_already)
    {
        while (divisor_lookup.size() != s_max_places)
        {
            JEWEL_ASSERT (!divisor_lookup.empty());
            JEWEL_ASSERT
            (   !multiplication_is_unsafe(divisor_lookup.back(), s_base)
            );
            divisor_lookup.push_back(divisor_lookup.back() * s_base);
        }
        calculated_already = true;
    }
    JEWEL_ASSERT (m_places < divisor_lookup.size());
    return divisor_lookup[m_places];
}

// operators

Decimal const& Decimal::operator++()
{
    #ifndef NDEBUG
        places_type const benchmark_places = m_places;
        Decimal const orig = *this;
    #endif
    if (addition_is_unsafe(m_intval, implicit_divisor()))
    {
        JEWEL_ASSERT (*this == orig);
        JEWEL_THROW
        (   DecimalIncrementationException,
            "Incrementation may cause overflow."
        );
    }
    m_intval += implicit_divisor();
    JEWEL_ASSERT (m_places >= benchmark_places);
    return *this;
}

Decimal Decimal::operator++(int)
{
    Decimal const ret(*this);
    ++*this;
    return ret;
}

Decimal const& Decimal::operator--()
{
    #ifndef NDEBUG
        places_type const benchmark_places = m_places;
        Decimal const orig = *this;
    #endif
    if (subtraction_is_unsafe(m_intval, implicit_divisor()))
    {
        JEWEL_ASSERT (*this == orig);
        JEWEL_THROW
        (   DecimalDecrementationException,
            "Decrementation may cause "
            "overflow."
        );
    }
    m_intval -= implicit_divisor();
    JEWEL_ASSERT (m_places >= benchmark_places);
    return *this;
}

Decimal Decimal::operator--(int)
{
    Decimal const ret(*this);
    --*this;
    return ret;
}

Decimal& Decimal::operator+=(Decimal rhs)
{
    #ifndef NDEBUG
        places_type const benchmark_places = max(m_places, rhs.m_places);
    #endif
    Decimal orig = *this;
    co_normalize(*this, rhs);
    if (addition_is_unsafe(m_intval, rhs.m_intval))
    {
        *this = orig;
        JEWEL_THROW(DecimalAdditionException, "Addition may cause overflow.");
    }
    m_intval += rhs.m_intval;
    JEWEL_ASSERT (m_places >= benchmark_places);
    return *this;
}

Decimal& Decimal::operator-=(Decimal rhs)
{
    #ifndef NDEBUG
        places_type const benchmark_places = max(m_places, rhs.m_places);
    #endif
    Decimal orig = *this;
    co_normalize(*this, rhs);
    if (subtraction_is_unsafe(m_intval, rhs.m_intval))
    {
        *this = orig;
        JEWEL_THROW
        (   DecimalSubtractionException,
            "Subtraction may cause overflow."
        );
    }
    m_intval -= rhs.m_intval;
    JEWEL_ASSERT (m_places >= benchmark_places);
    return *this;
}

Decimal& Decimal::operator*=(Decimal rhs)
{
    Decimal orig = *this;
    rationalize();
    rhs.rationalize();

    // Rule out problematic smallest Decimal
    JEWEL_ASSERT (minimum() == Decimal(numeric_limits<int_type>::min(), 0));
    JEWEL_ASSERT (maximum() == Decimal(numeric_limits<int_type>::max(), 0));
    if (*this == minimum() || rhs == minimum())
    {
        JEWEL_ASSERT (*this == orig);
        JEWEL_THROW
        (   DecimalMultiplicationException,
            "Cannot multiply smallest possible "
            "Decimal safely."
        );
    }

    // Make absolute and remember signs
    bool const signs_differ =
    (   (m_intval < 0 && rhs.m_intval > 0) ||
        (m_intval > 0 && rhs.m_intval < 0)
    );
    if (m_intval < 0)
    {
        JEWEL_ASSERT
        (   !multiplication_is_unsafe(m_intval, static_cast<int_type>(-1))
        );
        m_intval *= -1;
    }
    if (rhs.m_intval < 0)
    {
        JEWEL_ASSERT
        (   !multiplication_is_unsafe(rhs.m_intval, static_cast<int_type>(-1))
        );
        rhs.m_intval *= -1;
    }

    // Do "unchecked multiply" if we can
    JEWEL_ASSERT (m_intval >= 0 && rhs.m_intval >= 0);
    if (!multiplication_is_unsafe(m_intval, rhs.m_intval))
    {
        JEWEL_ASSERT (!addition_is_unsafe(m_places, rhs.m_places));
        m_intval *= rhs.m_intval;
        m_places += rhs.m_places;
        while (m_places > s_max_places)
        {
            JEWEL_ASSERT (m_places > 0);
            #ifndef NDEBUG
                int const check = rescale(m_places - 1);
                JEWEL_ASSERT (check == 0);
            #else
                rescale(m_places - 1);
            #endif
        }
        if (signs_differ)
        {
            JEWEL_ASSERT (m_intval != numeric_limits<int_type>::min());
            JEWEL_ASSERT
            (   !multiplication_is_unsafe
                (   m_intval,
                    static_cast<int_type>(-1)
                )
            );
            m_intval *= -1;
        }
        rationalize();
        return *this;
    }

    *this = orig;
    JEWEL_THROW(DecimalMultiplicationException, "Unsafe multiplication.");
    JEWEL_HARD_ASSERT (false);  // Execution should never reach here.
    return *this;    // Silence compiler re. return from non-void function.

}

Decimal& Decimal::operator/=(Decimal rhs)
{
    // Record original dividend and divisor
    Decimal const orig = *this;

    rhs.rationalize();

    // Capture division by zero
    if (rhs.m_intval == 0)
    {
        JEWEL_ASSERT (*this == orig);
        JEWEL_THROW(DecimalDivisionByZeroException, "Division by zero.");
    }

    // To prevent complications
    if ( m_intval == numeric_limits<int_type>::min() ||
      rhs.m_intval == numeric_limits<int_type>::min() )
    {
        JEWEL_ASSERT (*this == orig);
        JEWEL_THROW
        (   DecimalDivisionException,
            "Smallest possible Decimal cannot "
            "feature in division operation."
        );
    }
    JEWEL_ASSERT (NumDigits::num_digits(rhs.m_intval) <= maximum_precision());
    if (NumDigits::num_digits(rhs.m_intval) == maximum_precision())
    {
        JEWEL_ASSERT (*this == orig);
        JEWEL_THROW
        (   DecimalDivisionException,
            "Dividend has a number of significant"
             "digits that is greater than or equal to the return value of "
            "Decimal::maximum_precision(); as a result, division cannot be "
            "performed safely."
        );
    }

    // Remember required sign of product
    bool const diff_signs =
    (   ( m_intval > 0 && rhs.m_intval < 0) ||
        ( m_intval < 0 && rhs.m_intval > 0)
    );

    // Make absolute
    JEWEL_ASSERT
    (   !multiplication_is_unsafe(m_intval, static_cast<int_type>(-1))
    );
    JEWEL_ASSERT
    (   !multiplication_is_unsafe(rhs.m_intval, static_cast<int_type>(-1))
    );
    if (m_intval < 0) m_intval *= -1;
    if (rhs.m_intval < 0) rhs.m_intval *= -1;

    // Rescale the dividend as high as we can
    while (m_places < rhs.m_places && rescale(m_places + 1) == 0)
    {
    }
    if (rhs.m_places > m_places)
    {
        // We can't rescale high enough to proceed, so reset and throw
        *this = orig;
        JEWEL_THROW(DecimalDivisionException, "Unsafe division.");
    }

    // Proceed with basic division algorithm
    JEWEL_ASSERT (m_places >= rhs.m_places);
    JEWEL_ASSERT (!subtraction_is_unsafe(m_places, rhs.m_places));
    m_places -= rhs.m_places;
    JEWEL_ASSERT (rhs.m_intval != 0);
    JEWEL_ASSERT (m_intval != numeric_limits<int_type>::min());
    JEWEL_ASSERT (!remainder_is_unsafe(m_intval, rhs.m_intval));
    int_type remainder = m_intval % rhs.m_intval;
    JEWEL_ASSERT (!division_is_unsafe(m_intval, rhs.m_intval));
    m_intval /= rhs.m_intval;

    // Deal with any remainder using "long division"
    while (remainder != 0 && rescale(m_places + 1) == 0)
    {
        JEWEL_ASSERT (!multiplication_is_unsafe(remainder, s_base));

        /*
         * Previously this commented-out section of code dealt
         * with the case where it was unsafe to multiply remainder
         * and s_base. However, this is now dealt with by the fact that
         * an exception is thrown if the number of significant digits
         * in the dividend is equal to Decimal::maximum_precision().
         * This makes for a more straightforward API, since the
         * below code caused loss of precision under difficult-to-explain
         * circumstances.
        if (multiplication_is_unsafe(remainder, s_base))
        {
            // Then we can't proceed with ordinary "long division" safely,
            // and need to "scale down" first

            bool add_rounding_right = false;
            if (rhs.m_intval % s_base >= s_rounding_threshold)
            {
                add_rounding_right = true;
            }
            rhs.m_intval /= s_base;
            if (add_rounding_right)
            {
                JEWEL_ASSERT (!addition_is_unsafe(rhs.m_intval,
                  JEWEL_NUMERIC_CAST<int_type>(1)));
                ++(rhs.m_intval);
            }

            // Redo the Decimal division on a "safe scale"
            Decimal lhs = orig;
            if (lhs.m_intval < 0) lhs.m_intval *= -1;
            JEWEL_ASSERT (rhs.m_intval >= 0);
            lhs /= rhs;
            bool add_rounding_left = false;
            if (lhs.m_intval % s_base >= s_rounding_threshold)
            {
                add_rounding_left = true;
            }
            lhs.m_intval /= s_base;
            if (add_rounding_left)
            {
                JEWEL_ASSERT (!addition_is_unsafe(lhs.m_intval,
                  JEWEL_NUMERIC_CAST<int_type>(1)));
                ++(lhs.m_intval);
            }
            if (diff_signs) lhs.m_intval *= -1;
            *this = lhs;

            return *this;
        }
        */

        // It's safe to proceed with ordinary "long division"
        JEWEL_ASSERT (!multiplication_is_unsafe(remainder, s_base));
        remainder *= s_base;

        JEWEL_ASSERT (rhs.m_intval > 0);
        JEWEL_ASSERT (!remainder_is_unsafe(remainder, rhs.m_intval));
        int_type const temp_remainder = remainder % rhs.m_intval;
        JEWEL_ASSERT (!division_is_unsafe(remainder, rhs.m_intval));
        m_intval += remainder / rhs.m_intval;
        remainder = temp_remainder;
    }

    // Do rounding if required
    JEWEL_ASSERT (rhs.m_intval >= remainder);
    JEWEL_ASSERT (!subtraction_is_unsafe(rhs.m_intval, remainder));
    if (rhs.m_intval - remainder <= remainder)
    {
        // If the required rounding would be unsafe, we throw
        if (addition_is_unsafe(m_intval, JEWEL_NUMERIC_CAST<int_type>(1)))
        {
            *this = orig;
            JEWEL_THROW(DecimalDivisionException, "Unsafe division.");
        }
        // Do the rounding, it's safe
        ++m_intval;
    }

    // Put the correct sign
    JEWEL_ASSERT (m_intval >= 0);
    JEWEL_ASSERT
    (   !multiplication_is_unsafe(m_intval, static_cast<int_type>(-1))
    );
    if (diff_signs) m_intval *= -1;
    rationalize();
    return *this;
}

bool Decimal::operator<(Decimal rhs) const
{
    Decimal lhs = *this;
    lhs.rationalize();
    rhs.rationalize();
    if (lhs.m_places == rhs.m_places)
    {
        return lhs.m_intval < rhs.m_intval;
    }
    bool const left_is_longer = (lhs.m_places > rhs.m_places);
    Decimal const *const shorter = (left_is_longer? &rhs: &lhs);
    Decimal const *const longer = (left_is_longer? &lhs: &rhs);
    places_type const target_places = shorter->m_places;
    int_type longers_revised_intval = longer->m_intval;
    int_type const shorters_intval = shorter->m_intval;
    bool const longer_is_negative = (longers_revised_intval < 0);
    for
    (   places_type longers_places = longer->m_places;
        longers_places != target_places;
        --longers_places
    )
    {
        JEWEL_ASSERT (s_base > 0);
        JEWEL_ASSERT (!division_is_unsafe(longers_revised_intval, s_base));
        longers_revised_intval /= s_base;
        JEWEL_ASSERT (longers_places > 0);
    }
    bool longer_is_smaller =
    (   longer_is_negative?
        (longers_revised_intval <= shorters_intval):
        (longers_revised_intval < shorters_intval)
    );
    return ( &lhs == (longer_is_smaller? longer: shorter) );
}

bool Decimal::operator==(Decimal rhs) const
{
    Decimal temp_lhs = *this;
    temp_lhs.rationalize();
    rhs.rationalize();
    return
    (   temp_lhs.m_intval == rhs.m_intval &&
        temp_lhs.m_places == rhs.m_places
    );
}

Decimal round(Decimal const& x, Decimal::places_type decimal_places)
{
    Decimal ret = x;
    if (ret.rescale(decimal_places) != 0)
    {
        JEWEL_THROW
        (   DecimalRangeException,
            "Decimal number cannot safely be rounded to "
            "this number of places."
        );
    }
    return ret;
}

Decimal operator-(Decimal const& d)
{
    typedef Decimal::int_type int_type;
    if (d.m_intval == numeric_limits<int_type>::min())
    {
        JEWEL_THROW
        (   DecimalUnaryMinusException,
            "Unsafe arithmetic operation (unary minus)."
        );
    }
    JEWEL_ASSERT (d.m_intval != numeric_limits<int_type>::min());
    Decimal ret = d;
    JEWEL_ASSERT
    (   !multiplication_is_unsafe(ret.m_intval, static_cast<int_type>(-1))
    );
    ret.m_intval *= -1;
    return ret;
}

}  // namespace jewel


#include <iostream>
#include <cmath>
#include <cpp_gala/integrate/integrate.h>
#include <cpp_gala/simulation/simulation.h>
#include <cpp_gala/utils.h>

#include <boost/numeric/odeint.hpp>
#include <cpp_gala/extern/nested_range_algebra.hpp>
#include <cpp_gala/extern/vector_vector_resize.hpp>

// TODO: remove! just for printing...s
#include <pybind11/pybind11.h>
namespace py = pybind11;
using namespace pybind11::literals;

using namespace boost::numeric;

using namespace gala::utils;
using namespace gala::integrate;

/* ------------------------------------------------------------------------------------------------
    Base class

    TODO:
    - Redundant methods below! integrate/integrate_save_all...

*/
BaseIntegrator::BaseIntegrator(gala::simulation::Simulation &sim) {
    this->sim = ∼
}

void BaseIntegrator::integrate(const vector_1d t, double *result_w) {
    int i, j, k, idx, ps_ndim = 2 * this->sim->n_dim;
    int ntimes = t.size();
    int nparticles = this->sim->get_n_particles();

    // Call any custom setup needed before starting to step the integrator:
    this->setup_integrate(t);
    this->sim->step_callback(0, t[0]);

    for (int n=1; n < ntimes; n++) {
        this->step(t[n-1], t[n] - t[n-1]);
        this->sim->step_callback(n, t[n]);
    }

    // Store the w vector at the final timestep
    k = 0;
    for (auto &pair : this->sim->particles) {
        for (i=0; i < pair.second.n_particles; i++) {
            for (j=0; j < ps_ndim; j++) {
                idx = i2d(k, j, nparticles, ps_ndim);
                result_w[idx] = pair.second.w[i][j];
            }
            k++;
        }
    }
}

void BaseIntegrator::integrate_save_all(const vector_1d t, double *result_w) {
    int i, j, k, idx, ps_ndim = 2 * this->sim->n_dim;
    int ntimes = t.size();
    int nparticles = this->sim->get_n_particles();

    // Store the initial values in the first block of the result array:
    k = 0;
    for (auto &pair : this->sim->particles) {
        for (i=0; i < pair.second.n_particles; i++) {
            for (j=0; j < ps_ndim; j++)
                result_w[j + ps_ndim * k] = pair.second.w[i][j];
            k++;
        }
    }

    // Call any custom setup needed before starting to step the integrator:
    this->setup_integrate(t);
    this->sim->step_callback(0, t[0]);

    for (int n=1; n < ntimes; n++) {
        this->step(t[n-1], t[n] - t[n-1]);
        this->sim->step_callback(n, t[n]);

        // Store the w vector at this timestep
        k = 0;
        for (auto &pair : this->sim->particles) {
            for (i=0; i < pair.second.n_particles; i++) {
                for (j=0; j < ps_ndim; j++) {
                    idx = i3d(n, k, j, ntimes, nparticles, ps_ndim);
                    result_w[idx] = pair.second.w[i][j];
                }
                k++;
            }
        }
    }
}

// vector_2d BaseIntegrator::integrate(const vector_1d t) {
//     // Call any custom setup needed before starting to step the integrator:
//     this->setup_integrate(t);

//     for (int n=1; n < t.size(); n++)
//         this->step(t[n-1], t[n] - t[n-1]);

//     return this->sim->state_w;
// }

// vector_3d BaseIntegrator::integrate_save_all(const vector_1d t) {
//     // The return array:
//     vector_3d result_w;

//     // Store the initial values in the first block of the result array:
//     result_w.push_back(this->sim->state_w);

//     // Call any custom setup needed before starting to step the integrator:
//     this->setup_integrate(t);

//     for (int n=1; n < t.size(); n++) {
//         this->step(t[n-1], t[n] - t[n-1]);

//         // Store the w vector at this timestep
//         result_w.push_back(this->sim->state_w);
//     }

//     return result_w;
// }

// These are the methods that are overridden by subclasses
// Note: If I don't include these, I get a "Symbol not found" error on import of cpp_gala._integrate
void BaseIntegrator::setup_integrate(const vector_1d &t) {
    if (t.size() < 2)
        throw std::runtime_error("Input time array must have > 1 element.");

    this->tmp_w = this->sim->get_state_w();
    this->tmp_dwdt = this->sim->get_dwdt(t[0]);
}

void BaseIntegrator::step(const double t, const double dt) { }

/* ------------------------------------------------------------------------------------------------
    Leapfrog
*/
LeapfrogIntegrator::LeapfrogIntegrator(gala::simulation::Simulation &sim)
: BaseIntegrator(sim) {
    for (int i=0; i < sim.get_n_particles(); i++)
        this->v_ip1_2.push_back(vector_1d(sim.n_dim, NAN));
}

void LeapfrogIntegrator::setup_integrate(const vector_1d &t) {
    BaseIntegrator::setup_integrate(t);
    // In BaseIntegrator::setup_integrate, the initial state_w and dwdt is stored in tmp_w and
    // tmp_dwdt, so we can use them below to initialize the array of velocities (1/2 step forward)

    double dt = t[1] - t[0];

    // First step all of the velocities by 1/2 step to initialize
    for (int i=0; i < this->tmp_w.size(); i++)
        for (int j=0; j < this->sim->n_dim; j++) {
            this->v_ip1_2[i][j] = this->tmp_w[i][this->sim->n_dim + j]
                + dt/2 * this->tmp_dwdt[i][this->sim->n_dim + j];
        }
}

void LeapfrogIntegrator::step(const double t, const double dt) {
    int i, j;

    // TODO: if (this->sim->fixed_n_particles == false), need to resize and recompute
    // tmp_w, tmp_dwdt, v_ip1_2 at the beginning of each step
    if (this->sim->fixed_n_particles == false) {
        this->tmp_w = this->sim->get_state_w();
        this->tmp_dwdt = this->sim->get_dwdt(t);
        for (int i=0; i < this->tmp_w.size(); i++)
            for (int j=0; j < this->sim->n_dim; j++) {
                this->v_ip1_2[i][j] = this->tmp_w[i][this->sim->n_dim + j]
                    + dt/2 * this->tmp_dwdt[i][this->sim->n_dim + j];
            }
    }

    // print_vector_2d(this->tmp_w);
    // print_vector_2d(this->tmp_dwdt);
    // print_vector_2d(this->v_ip1_2);

    // Evolve the positions forward by a full step, using the retarded velocities
    for (i=0; i < this->tmp_w.size(); i++)
        for (j=0; j < this->sim->n_dim; j++)
            this->tmp_w[i][j] += dt * this->v_ip1_2[i][j];

    // Set the simulation state where now the velocity values are 1/2 step behind
    this->sim->set_state_w(this->tmp_w);

    // Compute the acceleration at the new positions:
    this->sim->get_dwdt(t, this->tmp_dwdt);

    // Evolve the velocity forward first by a half step, snapshot the time-aligned position and
    // velocity, then finish the full step to leapfrog over the positions
    for (i=0; i < this->tmp_w.size(); i++) {
        for (j=0; j < this->sim->n_dim; j++) {
            this->tmp_w[i][this->sim->n_dim + j] = this->v_ip1_2[i][j]
                + dt/2 * this->tmp_dwdt[i][this->sim->n_dim + j];
            this->v_ip1_2[i][j] = this->tmp_w[i][this->sim->n_dim + j]
                + dt/2 * this->tmp_dwdt[i][this->sim->n_dim + j];
        }
    }

    // Set the simulation state: the position and velocity should be synced, so ready for output
    this->sim->set_state_w(this->tmp_w);
}

/* ------------------------------------------------------------------------------------------------
    Boost
*/

BoostIntegrator::BoostIntegrator(gala::simulation::Simulation &sim,
                                 std::string choice, int sub_steps)
: BaseIntegrator(sim) {
    /*
    choice
        Can be one of:
            rk4 (4th order Runge-Kutta)
            dopri5 (5th order Runge-Kutta)
            rk78 (8th order Runge-Kutta)
            adm (Adam-Bashforth-M)
    sub_steps
        The number of sub-steps to take (used in the Adam-Bashforth method).
    */
    this->sub_steps = sub_steps;
    this->choice = choice;
}

// Built from example here:
// https://github.com/headmyshoulder/odeint-v2/blob/master/examples/2d_lattice/spreading.cpp

// TODO: shit - will this work if w size changes for each step?

template <typename T>
void BoostIntegrator::step_worker(T stepper, const double t, const double dt) {
    stepper.do_step([this](const vector_2d &w, vector_2d &dw, const double t) {
        // TODO: this currently just gets the 3-acceleration, but need to compute the 6-acc
        this->sim->set_state_w(w);
        this->sim->get_dwdt(t, dw);
    }, this->tmp_w, t, dt);
    this->sim->set_state_w(this->tmp_w);
}

// List of steppers here:
// https://www.boost.org/doc/libs/1_63_0/libs/numeric/odeint/doc/html/boost_numeric_odeint/odeint_in_detail/steppers.html

// ---
void BoostIntegrator::step_rk4(const double t, const double dt) {
    // https://www.boost.org/doc/libs/1_60_0/libs/numeric/odeint/doc/html/boost/numeric/odeint/runge_kutta4.html
    typedef odeint::runge_kutta4 <
        vector_2d, double, vector_2d, double,
        nested_range_algebra <odeint::range_algebra>,
        odeint::default_operations
    > stepper_type_rk4;

    stepper_type_rk4 stepper;
    this->step_worker(stepper, t, dt);
}

void BoostIntegrator::step_dopri5(const double t, const double dt) {
    // https://www.boost.org/doc/libs/1_60_0/libs/numeric/odeint/doc/html/boost/numeric/odeint/runge_kutta_dopri5.html
    typedef odeint::runge_kutta_dopri5<
        vector_2d, double, vector_2d, double,
        nested_range_algebra< odeint::range_algebra >,
        odeint::default_operations
    > stepper_type_dopri5;

    stepper_type_dopri5 stepper;
    this->step_worker(stepper, t, dt);
}

void BoostIntegrator::step_rk78(const double t, const double dt) {
    // https://www.boost.org/doc/libs/1_60_0/libs/numeric/odeint/doc/html/boost/numeric/odeint/runge_kutta_fehlberg78.html
    typedef odeint::runge_kutta_fehlberg78 <
        vector_2d, double, vector_2d, double,
        nested_range_algebra <odeint::range_algebra>,
        odeint::default_operations
    > stepper_type_rk78;

    stepper_type_rk78 stepper;
    this->step_worker(stepper, t, dt);
}

template <const int substeps>
void BoostIntegrator::step_adm(const double t, const double dt) {
    // https://www.boost.org/doc/libs/1_60_0/libs/numeric/odeint/doc/html/boost/numeric/odeint/adams_bashforth_moulton.html
    typedef odeint::adams_bashforth_moulton <
        substeps,
        vector_2d, double, vector_2d, double,
        nested_range_algebra <odeint::range_algebra>,
        odeint::default_operations
    > stepper_type_adm;

    stepper_type_adm stepper;
    this->step_worker(stepper, t, dt);
}

void BoostIntegrator::step(const double t, const double dt) {
    if (this->choice == "rk4") {
        step_rk4(t, dt);
    } else if (this->choice == "rk78") {
        step_rk78(t, dt);
    } else if (this->choice == "dopri5") {
        step_dopri5(t, dt);
    } else if (this->choice == "adm") {
        switch (this->sub_steps) {
            case 2:
                step_adm<2>(t, dt);
            case 4:
                step_adm<4>(t, dt);
            case 6:
                step_adm<6>(t, dt);
            case 8:
                step_adm<8>(t, dt);
            default:
                throw std::invalid_argument("Invalid setting of sub_steps");
        }
    } else {
        throw std::invalid_argument("Invalid integrator choice");
    }

}


#include <boost/program_options.hpp>
using namespace boost::program_options;

#include <iostream>
using namespace std;

#include <OpenImageIO/imageio.h>
OIIO_NAMESPACE_USING

#include <algorithm>

#include "Perlin.h"

int main(int argc, char* argv[])
{
	try
	{
		options_description desc("Allowed options");
		desc.add_options()
			("help,h", "produce help message")
			("xres,x", value<int>()->default_value(255),
				"x resolution")
			("yres,y", value<int>()->default_value(255),
				"y resolution")
			("sample-size,s", value<int>()->default_value(256),
				"sample size")
			("seed,r", value<int>()->default_value(0),
				"psuedo-random seed")
			("persistence,p", value<float>()->default_value(0.5f),
				"persistence value")
			("octaves,o", value<int>()->default_value(2),
				"number of octaves")
		;

		options_description hidden("Hidden options");
		hidden.add_options()
			("output-file", value<string>()->required(), "output file")
		;

		options_description all("Allowed options");
		all.add(desc).add(hidden);

		positional_options_description p;
		p.add("output-file", 1);

		variables_map vm;
		store(command_line_parser(argc, argv).options(all)
			.positional(p).run(), vm);

		if (vm.count("help"))
		{
			cout << "Usage: " << argv[0] << " [options] output-file" << endl;
			cout << desc << endl;
			return 0;
		}

		notify(vm);

		string outputfile = vm["output-file"].as<string>();
		ImageOutput* out = ImageOutput::create(outputfile);
		if (!out)
		{
			cerr << "Could not create an ImageOutput for "
				<< outputfile << ", error = "
				<< OpenImageIO::geterror() << endl;
			return 0;
		}

		const int xres = vm["xres"].as<int>();
		const int yres = vm["yres"].as<int>();
		const int channels = 3; // RGB
		ImageSpec outspec(xres, yres, channels, TypeDesc::UINT8);

		if (!out->open(outputfile, outspec))
		{
			cerr << "Could not open " << outputfile
				<< ", error = " << out->geterror() << endl;
			ImageOutput::destroy(out);
			return 0;
		}

		const int sample_size = vm["sample-size"].as<int>();
		const int seed = vm["seed"].as<int>();
		Perlin perlin(sample_size, seed);

		float persistence = vm["persistence"].as<float>();
		int octaves = vm["octaves"].as<int>();

		unsigned char pixels[xres * yres * channels];
		for (int y = 0; y < yres; y++)
		{
			for (int x = 0; x < xres; x++)
			{
				float frequency, amplitude;
				float total = 0.0f;

				for (int i = 1; i <= octaves; ++i)
				{
					frequency = pow(2.0f, i);
					amplitude = pow(persistence, i);

					total += (perlin.Noise2(frequency * x / sample_size,
						frequency * y / sample_size) + 1)/ 2.0f * amplitude;
				}

				total = min<float>(1.0f, max<float>(0.0f, total));
				unsigned int noise = (unsigned int) (total * 255);

				pixels[y * xres * channels + x * channels] = noise;
				pixels[y * xres * channels + x * channels + 1] = noise;
				pixels[y * xres * channels + x * channels + 2] = noise;
			}
		}

		if (!out->write_image(TypeDesc::UINT8, pixels))
		{
			cerr << "Could not write pixels to " << outputfile
				<< ", error = " << out->geterror() << endl;
			ImageOutput::destroy(out);
			return 0;
		}

		ImageOutput::destroy(out);
	}
	catch (exception& e)
	{
		cerr << e.what() << endl;
	}
}


/*
 * Copyright (c) 2011, Mattia Penati <mattia.penati@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *     * Neither the name of the Politecnico di Milano nor the names of its
 *       contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef AMA_TENSOR_MUL_MUL_FACTORY_HPP
#define AMA_TENSOR_MUL_MUL_FACTORY_HPP 1

#include <ama/tensor/mul/mul_outer.hpp>
#include <ama/tensor/mul/mul_reducer.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/mpl/equal.hpp>
#include <boost/mpl/vector/vector0_c.hpp>

namespace ama
{
  namespace tensor_
  {

    /* reduction over all indices */
    template <typename WHAT>
    struct mul_factory
    {
      template <typename LEFT, typename RIGHT>
      static
      WHAT apply(LEFT const & left, RIGHT const & right)
      {
        BOOST_MPL_ASSERT_MSG(
              (mpl::equal<WHAT, typename LEFT::value_type>::type::value)
            , THIS_STRUCT_CAN_BE_CALLED_ONLY_TO_REDUCE_OVER_ALL_INDICES
            , (WHAT));

        return mul_reducer<LEFT,RIGHT>(left, right).template at< ::boost::mpl::vector0_c<size_t> >();
      }
    };

    /* specialization for reduction of product */
    template <typename TENSOR, typename CTLIST, typename COLIST>
    struct mul_factory< iexp_temporary<TENSOR,CTLIST,COLIST> >
    {
      template <typename LEFT, typename RIGHT>
      static
      iexp_temporary<TENSOR,CTLIST,COLIST> apply(LEFT const & left,
                                                 RIGHT const & right)
      {
        return iexp_temporary<TENSOR,CTLIST,COLIST>(mul_reducer<LEFT,RIGHT>(left,right));
      }
    };

    /* specialization for outer product */
    template <typename LEFT, typename RIGHT>
    struct mul_factory< mul_outer<LEFT, RIGHT> >
    {
      template <typename T1, typename T2>
      static
      mul_outer<LEFT, RIGHT> apply(LEFT const & left,
                                   RIGHT const & right)
      {
        return mul_outer<LEFT, RIGHT>(left,right);
      }
    };

  }
}

#endif /* AMA_TENSOR_MUL_MUL_FACTORY_HPP */


/*
  Copyright (C) 2020 Skandinaviska Enskilda Banken AB (publ)
  All rights reserved.
  This file is part of ORE, a free-software/open-source library
  for transparent pricing and risk analysis - http://opensourcerisk.org
  ORE is free software: you can redistribute it and/or modify it
  under the terms of the Modified BSD License.  You should have received a
  copy of the license along with this program.
  The license is also available online at <http://opensourcerisk.org>
  This program is distributed on the basis that it will form a useful
  contribution to risk analytics and model standardisation, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
 */

 /*! \file portfolio/builders/asianoption.hpp
     \brief Abstract engine builders for European Asian Options
     \ingroup builders
 */

 #pragma once

 #include <ored/portfolio/builders/vanillaoption.hpp>
 #include <boost/make_shared.hpp>
 #include <ored/portfolio/builders/cachingenginebuilder.hpp>
 #include <ored/utilities/log.hpp>
 #include <ored/utilities/to_string.hpp>
 #include <ored/utilities/parsers.hpp>
 #include <ored/utilities/indexparser.hpp>
 #include <ql/pricingengines/asian/analytic_cont_geom_av_price.hpp>
 #include <ql/pricingengines/asian/analytic_discr_geom_av_price.hpp>
 #include <ql/pricingengines/asian/analytic_discr_geom_av_strike.hpp>
 #include <ql/pricingengines/asian/mc_discr_arith_av_price.hpp>
 #include <ql/pricingengines/asian/mc_discr_arith_av_strike.hpp>
 #include <ql/utilities/null.hpp>

 namespace ore {
 namespace data {

 //! Abstract Engine Builder for Asian Options
 /*! Pricing engines are cached by asset/currency/expiry, where
     expiry is null (Date()) if irrelevant.
     \ingroup builders
  */
 class AsianOptionEngineBuilder
     : public CachingOptionEngineBuilder<string, const string&, const Currency&, const AssetClass&, const Date&> {
 public:
     AsianOptionEngineBuilder(const string& model, const string& engine, const set<string>& tradeTypes,
                              const AssetClass& assetClass, const Date& expiryDate)
         : CachingOptionEngineBuilder(model, engine, tradeTypes, assetClass), expiryDate_(expiryDate) {}

     boost::shared_ptr<PricingEngine> engine(const string& assetName, const Currency& ccy, const Date& expiryDate) {
         return CachingPricingEngineBuilder<string, const string&, const Currency&, const AssetClass&,
                                            const Date&>::engine(assetName, ccy, assetClass_, expiryDate);
     }

     boost::shared_ptr<PricingEngine> engine(const Currency& ccy1, const Currency& ccy2, const Date& expiryDate) {
         return CachingPricingEngineBuilder<string, const string&, const Currency&, const AssetClass&,
                                            const Date&>::engine(ccy1.code(), ccy2, assetClass_, expiryDate);
     }

     //! This is used in building the option to select between Discrete- and ContinuousAveragingAsianOption
     virtual std::string processType() { return ""; }

 protected:
     virtual string keyImpl(const string& assetName, const Currency& ccy, const AssetClass& assetClassUnderlying,
                            const Date& expiryDate) override {
         return assetName + "/" + ccy.code() + "/" + to_string(expiryDate);
     }

     Date expiryDate_;
 };

 //! Discrete Monte Carlo Engine Builder for European Asian Arithmetic Average Price Options
 /*! Pricing engines are cached by asset/currency/expiry, where
     expiry is null (Date()) if irrelevant.
     \ingroup builders
  */
 class EuropeanAsianOptionMCDAAPEngineBuilder : public AsianOptionEngineBuilder {
 public:
     EuropeanAsianOptionMCDAAPEngineBuilder(const string& model, const set<string>& tradeTypes,
                                            const AssetClass& assetClass, const Date& expiryDate)
         : AsianOptionEngineBuilder(model, "MCDiscreteArithmeticAPEngine", tradeTypes, assetClass, expiryDate) {}

     std::string processType() override { return "Discrete"; }

 protected:
     virtual boost::shared_ptr<PricingEngine> engineImpl(const string& assetName, const Currency& ccy,
                                                         const AssetClass& assetClassUnderlying,
                                                         const Date& expiryDate) override {
         bool brownianBridge = ore::data::parseBool(engineParameter("BrownianBridge", "", false, "true"));
         bool antitheticVariate = ore::data::parseBool(engineParameter("AntitheticVariate", "", false, "true"));
         bool controlVariate = ore::data::parseBool(engineParameter("ControlVariate", "", false, "true"));
         Size requiredSamples = ore::data::parseInteger(engineParameter("RequiredSamples", "", false, "0"));
         Real requiredTolerance = ore::data::parseReal(engineParameter("RequiredTolerance", "", false, "0"));
         Size maxSamples = ore::data::parseInteger(engineParameter("MaxSamples", "", false, "0"));
         BigNatural seed = ore::data::parseInteger(engineParameter("Seed", "", false, "123456"));

         // Check if values defaulted to 0, if so replace by Null<T>().
         if (requiredSamples == 0)
             requiredSamples = Null<Size>();
         if (requiredTolerance == 0)
             requiredTolerance = Null<Real>();
         if (maxSamples == 0)
             maxSamples = Null<Size>();
         QL_REQUIRE(requiredSamples != QuantLib::Null<Size>() || requiredTolerance != QuantLib::Null<Real>(),
                    "RequiredSamples or RequiredTolerance must be set for engine MCDiscreteArithmeticAPEngine.");

         boost::shared_ptr<GeneralizedBlackScholesProcess> gbsp =
             getBlackScholesProcess(assetName, ccy, assetClassUnderlying);
         return boost::make_shared<MCDiscreteArithmeticAPEngine<LowDiscrepancy>>(gbsp, brownianBridge, antitheticVariate,
                                                                                controlVariate, requiredSamples,
                                                                                requiredTolerance, maxSamples, seed);
     }
 };

 //! Discrete Monte Carlo Engine Builder for European Asian Arithmetic Average Strike Options
 /*! Pricing engines are cached by asset/currency/expiry, where
     expiry is null (Date()) if irrelevant.
     \ingroup builders
  */
 class EuropeanAsianOptionMCDAASEngineBuilder : public AsianOptionEngineBuilder {
 public:
     EuropeanAsianOptionMCDAASEngineBuilder(const string& model, const set<string>& tradeTypes,
                                            const AssetClass& assetClass, const Date& expiryDate)
         : AsianOptionEngineBuilder(model, "MCDiscreteArithmeticASEngine", tradeTypes, assetClass, expiryDate) {}

     std::string processType() override { return "Discrete"; }

 protected:
     virtual boost::shared_ptr<PricingEngine> engineImpl(const string& assetName, const Currency& ccy,
                                                         const AssetClass& assetClassUnderlying,
                                                         const Date& expiryDate) override {
         bool brownianBridge = ore::data::parseBool(engineParameter("BrownianBridge", "", false, "true"));
         bool antitheticVariate = ore::data::parseBool(engineParameter("AntitheticVariate", "", false, "true"));
         Size requiredSamples = ore::data::parseInteger(engineParameter("RequiredSamples", "", false, "0"));
         Real requiredTolerance = ore::data::parseReal(engineParameter("RequiredTolerance", "", false, "0"));
         Size maxSamples = ore::data::parseInteger(engineParameter("MaxSamples", "", false, "0"));
         BigNatural seed = ore::data::parseInteger(engineParameter("Seed", "", false, "123456"));

         // Check if values defaulted to 0, if so replace by Null<T>().
         if (requiredSamples == 0)
             requiredSamples = Null<Size>();
         if (requiredTolerance == 0)
             requiredTolerance = Null<Real>();
         if (maxSamples == 0)
             maxSamples = Null<Size>();
         QL_REQUIRE(requiredSamples != QuantLib::Null<Size>() || requiredTolerance != QuantLib::Null<Real>(),
                    "RequiredSamples or RequiredTolerance must be set for engine MCDiscreteArithmeticASEngine.");

         boost::shared_ptr<GeneralizedBlackScholesProcess> gbsp =
             getBlackScholesProcess(assetName, ccy, assetClassUnderlying);
         return boost::make_shared<MCDiscreteArithmeticASEngine<LowDiscrepancy>>(
             gbsp, brownianBridge, antitheticVariate, requiredSamples, requiredTolerance, maxSamples, seed);
     }
 };

 //! Discrete Monte Carlo Engine Builder for European Asian Geometric Average Price Options
 /*! Pricing engines are cached by asset/currency/expiry, where
     expiry is null (Date()) if irrelevant.
     \ingroup builders
  */
 class EuropeanAsianOptionMCDGAPEngineBuilder : public AsianOptionEngineBuilder {
 public:
     EuropeanAsianOptionMCDGAPEngineBuilder(const string& model, const set<string>& tradeTypes,
                                            const AssetClass& assetClass, const Date& expiryDate)
         : AsianOptionEngineBuilder(model, "MCDiscreteGeometricAPEngine", tradeTypes, assetClass, expiryDate) {}

     std::string processType() override { return "Discrete"; }

 protected:
     virtual boost::shared_ptr<PricingEngine> engineImpl(const string& assetName, const Currency& ccy,
                                                         const AssetClass& assetClassUnderlying,
                                                         const Date& expiryDate) override {
         bool brownianBridge = ore::data::parseBool(engineParameter("BrownianBridge", "", false, "true"));
         bool antitheticVariate = ore::data::parseBool(engineParameter("AntitheticVariate", "", false, "true"));
         Size requiredSamples = ore::data::parseInteger(engineParameter("RequiredSamples", "", false, "0"));
         Real requiredTolerance = ore::data::parseReal(engineParameter("RequiredTolerance", "", false, "0"));
         Size maxSamples = ore::data::parseInteger(engineParameter("MaxSamples", "", false, "0"));
         BigNatural seed = ore::data::parseInteger(engineParameter("Seed", "", false, "123456"));

         // Check if values defaulted to 0, if so replace by Null<T>().
         if (requiredSamples == 0)
             requiredSamples = Null<Size>();
         if (requiredTolerance == 0)
             requiredTolerance = Null<Real>();
         if (maxSamples == 0)
             maxSamples = Null<Size>();
         QL_REQUIRE(requiredSamples != QuantLib::Null<Size>() || requiredTolerance != QuantLib::Null<Real>(),
                    "RequiredSamples or RequiredTolerance must be set for engine MCDiscreteGeometricAPEngine.");

         boost::shared_ptr<GeneralizedBlackScholesProcess> gbsp =
             getBlackScholesProcess(assetName, ccy, assetClassUnderlying);
         return boost::make_shared<MCDiscreteGeometricAPEngine<LowDiscrepancy>>(
             gbsp, brownianBridge, antitheticVariate, requiredSamples, requiredTolerance, maxSamples, seed);
     }
 };

 //! Discrete Analytic Engine Builder for European Asian Geometric Average Price Options
 /*! Pricing engines are cached by asset/currency
     \ingroup builders
  */
 class EuropeanAsianOptionADGAPEngineBuilder : public AsianOptionEngineBuilder {
 public:
     EuropeanAsianOptionADGAPEngineBuilder(const string& model, const set<string>& tradeTypes,
                                           const AssetClass& assetClass)
         : AsianOptionEngineBuilder(model, "AnalyticDiscreteGeometricAPEngine", tradeTypes, assetClass, Date()) {}

     std::string processType() override { return "Discrete"; }

 protected:
     virtual boost::shared_ptr<PricingEngine> engineImpl(const string& assetName, const Currency& ccy,
                                                         const AssetClass& assetClassUnderlying,
                                                         const Date& expiryDate) override {
         boost::shared_ptr<GeneralizedBlackScholesProcess> gbsp =
             getBlackScholesProcess(assetName, ccy, assetClassUnderlying);
         return boost::make_shared<AnalyticDiscreteGeometricAveragePriceAsianEngine>(gbsp);
     }
 };

 //! Discrete Analytic Engine Builder for European Asian Geometric Average Strike Options
 /*! Pricing engines are cached by asset/currency
     \ingroup builders
  */
 class EuropeanAsianOptionADGASEngineBuilder : public AsianOptionEngineBuilder {
 public:
     EuropeanAsianOptionADGASEngineBuilder(const string& model, const set<string>& tradeTypes,
                                           const AssetClass& assetClass)
         : AsianOptionEngineBuilder(model, "AnalyticDiscreteGeometricASEngine", tradeTypes, assetClass, Date()) {}

     std::string processType() override { return "Discrete"; }

 protected:
     virtual boost::shared_ptr<PricingEngine> engineImpl(const string& assetName, const Currency& ccy,
                                                         const AssetClass& assetClassUnderlying,
                                                         const Date& expiryDate) override {
         boost::shared_ptr<GeneralizedBlackScholesProcess> gbsp =
             getBlackScholesProcess(assetName, ccy, assetClassUnderlying);
         return boost::make_shared<AnalyticDiscreteGeometricAverageStrikeAsianEngine>(gbsp);
     }
 };

 //! Continuous Analytic Engine Builder for European Asian Geometric Average Price Options
 /*! Pricing engines are cached by asset/currency
     Note that this engine disregards fixing dates, i.e. it utilizes continuous averaging and is mainly for testing.
     \ingroup builders
  */
 class EuropeanAsianOptionACGAPEngineBuilder : public AsianOptionEngineBuilder {
 public:
     EuropeanAsianOptionACGAPEngineBuilder(const string& model, const set<string>& tradeTypes,
                                           const AssetClass& assetClass)
         : AsianOptionEngineBuilder(model, "AnalyticContinuousGeometricAPEngine", tradeTypes, assetClass, Date()) {}

     std::string processType() override { return "Continuous"; }

 protected:
     virtual boost::shared_ptr<PricingEngine> engineImpl(const string& assetName, const Currency& ccy,
                                                         const AssetClass& assetClassUnderlying,
                                                         const Date& expiryDate) override {
         boost::shared_ptr<GeneralizedBlackScholesProcess> gbsp =
             getBlackScholesProcess(assetName, ccy, assetClassUnderlying);
         return boost::make_shared<AnalyticContinuousGeometricAveragePriceAsianEngine>(gbsp);
     }
 };

 } // namespace data
 } // namespace ore


// Copyright 2005 The Trustees of Indiana University.

// Use, modification and distribution is 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)

//  Authors: Douglas Gregor
//           Andrew Lumsdaine
#include "graph_types.hpp"
#include <boost/graph/betweenness_centrality.hpp>
#include <boost/graph/bc_clustering.hpp>

namespace boost { namespace graph { namespace python {

template<typename Graph>
void
brandes_betweenness_centrality
  (const Graph& g,
   vector_property_map<
     float,
     typename property_map<Graph, vertex_index_t>::const_type
   >* in_vertex_centrality,
   vector_property_map<
     float,
     typename property_map<Graph, edge_index_t>::const_type
   >* in_edge_centrality,
   vector_property_map<
     float,
     typename property_map<Graph, edge_index_t>::const_type>* weight)
{
  typedef vector_property_map<
            float, typename property_map<Graph, vertex_index_t>::const_type>
    VertexCentralityMap;

  typedef vector_property_map<
            float, typename property_map<Graph, edge_index_t>::const_type>
    EdgeCentralityMap;

  VertexCentralityMap vertex_centrality =
    in_vertex_centrality? *in_vertex_centrality
    : VertexCentralityMap(num_vertices(g), get(vertex_index, g));

  EdgeCentralityMap edge_centrality =
    in_edge_centrality? *in_edge_centrality
    : EdgeCentralityMap(num_edges(g), get(edge_index, g));

  if (weight) {
    boost::brandes_betweenness_centrality
      (g,
       weight_map(*weight).
       centrality_map(vertex_centrality).
       edge_centrality_map(edge_centrality));
  } else {
    boost::brandes_betweenness_centrality
      (g,
       centrality_map(vertex_centrality).
       edge_centrality_map(edge_centrality));
  }
}

template<typename Graph>
void
relative_betweenness_centrality
  (const Graph& g,
   vector_property_map<
     float,
     typename property_map<Graph, vertex_index_t>::const_type>& centrality)
{
  relative_betweenness_centrality(g, centrality);
}

template<typename Graph>
float
central_point_dominance
  (const Graph& g,
   vector_property_map<
     float,
     typename property_map<Graph, vertex_index_t>::const_type>& centrality)
{
  return boost::central_point_dominance(g, centrality);
}

struct bc_clustering_done_python
{
  explicit bc_clustering_done_python(boost::python::object done)
    : done(done) { }

  template<typename Graph>
  bool
  operator()(float max_centrality,
             typename graph_traits<Graph>::edge_descriptor e,
             const Graph& g)
  {
    using boost::python::extract;
    return extract<bool>(done(max_centrality, e, ref(g)));
  }

private:
  boost::python::object done;
};

template<typename Graph>
void
betweenness_centrality_clustering
  (Graph& g, boost::python::object done,
   vector_property_map<
     float,
     typename property_map<Graph, edge_index_t>::const_type
   >* in_edge_centrality)
{
  typedef vector_property_map<
            float, typename property_map<Graph, edge_index_t>::const_type>
    EdgeCentralityMap;

  EdgeCentralityMap edge_centrality =
    in_edge_centrality? *in_edge_centrality
    : EdgeCentralityMap(num_edges(g), get(edge_index, g));

  boost::betweenness_centrality_clustering(g,
                                           bc_clustering_done_python(done),
                                           edge_centrality);
}

void export_betweenness_centrality()
{
  using boost::python::arg;
  using boost::python::def;

#define UNDIRECTED_GRAPH(Name,Type)                                     \
  {                                                                     \
    typedef property_map<Type, vertex_index_t>::const_type VertexIndexMap;  \
    typedef property_map<Type, edge_index_t>::const_type EdgeIndexMap;      \
    typedef vector_property_map<float, VertexIndexMap> VertexCentralityMap; \
    typedef vector_property_map<float, EdgeIndexMap> EdgeCentralityMap; \
                                                                        \
    def("brandes_betweenness_centrality",                               \
        &brandes_betweenness_centrality<Type>,                          \
        (arg("graph"),                                                  \
         arg("vertex_centrality_map") = static_cast<VertexCentralityMap*>(0), \
         arg("edge_centrality_map") = static_cast<EdgeCentralityMap*>(0), \
         arg("weight_map") = static_cast<EdgeCentralityMap*>(0)));      \
    def("relative_betweenness_centrality",                              \
        &relative_betweenness_centrality<Type>,                         \
        (arg("graph"), arg("vertex_centrality_map")));                  \
    def("central_point_dominance",                                      \
        &central_point_dominance<Type>,                                 \
        (arg("graph"), arg("vertex_centrality_map")));                  \
    def("betweenness_centrality_clustering",                            \
        &betweenness_centrality_clustering<Type>,                       \
        (arg("graph"), arg("done"),                                     \
         arg("edge_centrality_map") = static_cast<EdgeCentralityMap*>(0))); \
  }
#include "graphs.hpp"
}

} } } // end namespace boost::graph::python


/* Siconos is a program dedicated to modeling, simulation and control
 * of non smooth dynamical systems.
 *
 * Copyright 2016 INRIA.
 *
 * 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 "CSparseMatrix_internal.h"
#include "SiconosMatrix.hpp"
#include "SiconosAlgebra.hpp"
#include <boost/numeric/ublas/matrix_sparse.hpp>
#include "BlockMatrix.hpp"

// Constructor with the type-number
SiconosMatrix::SiconosMatrix(unsigned int type): _num(type)
{}

const SP::Index SiconosMatrix::tabRow() const
{
  SiconosMatrixException::selfThrow("SiconosMatrix::tabRow() : not implemented for this type of matrix (Simple?) reserved to BlockMatrix.");
  // fake to avoid error on warning.
  return SP::Index();
}

const SP::Index SiconosMatrix::tabCol() const
{
  SiconosMatrixException::selfThrow("SiconosMatrix::tabCol() : not implemented for this type of matrix (Simple?) reserved to BlockMatrix.");
  // fake to avoid error on warning.
  return SP::Index();
}

//=====================
// matrices comparison
//=====================
bool isComparableTo(const  SiconosMatrix& m1, const  SiconosMatrix& m2)
{
  // return:
  // - true if one of the matrices is a Simple and if they have the same dimensions.
  // - true if both are block but with blocks which are facing each other of the same size.
  // - false in other cases

  if ((!m1.isBlock() || !m2.isBlock()) && (m1.size(0) == m2.size(0)) && (m1.size(1) == m2.size(1)))
    return true;

  const SP::Index I1R = m1.tabRow();
  const SP::Index I2R = m2.tabRow();
  const SP::Index I1C = m1.tabCol();
  const SP::Index I2C = m2.tabCol();

  return ((*I1R == *I2R) && (*I1C == *I2C));
}

SiconosMatrix& operator *=(SiconosMatrix& m, const double& s)
{
  if (m._num == 0) // BlockMatrix
  {
    BlockMatrix& mB = static_cast<BlockMatrix&>(m);
    BlocksMat::iterator1 it;
    BlocksMat::iterator2 it2;
    for (it = mB._mat->begin1(); it != mB._mat->end1(); ++it)
    {
      for (it2 = it.begin(); it2 != it.end(); ++it2)
        (**it2) *= s;
    }
  }
  else if (m._num == 1)
    *m.dense() *= s;
  else if (m._num == 2)
    *m.triang() *= s;
  else if (m._num == 3)
    *m.sym() *= s;
  else if (m._num == 4)
    *m.sparse() *= s;
  else if (m._num == 5)
    *m.banded() *= s;
  else if (m._num == 6) {} // nothing!
  else //if(_num == 7)
    SiconosMatrixException::selfThrow(" SP::SiconosMatrix = (double) : invalid type of matrix");

  return m;
}

SiconosMatrix& operator /=(SiconosMatrix& m, const double& s)
{
  if (m._num == 0) // BlockMatrix
  {
    BlockMatrix& mB = static_cast<BlockMatrix&>(m);
    BlocksMat::iterator1 it;
    BlocksMat::iterator2 it2;
    for (it = mB._mat->begin1(); it != mB._mat->end1(); ++it)
    {
      for (it2 = it.begin(); it2 != it.end(); ++it2)
        (**it2) /= s;
    }
  }
  else if (m._num == 1)
    *m.dense() /= s;
  else if (m._num == 2)
    *m.triang() /= s;
  else if (m._num == 3)
    *m.sym() /= s;
  else if (m._num == 4)
    *m.sparse() /= s;
  else if (m._num == 5)
    *m.banded() /= s;
  else if (m._num == 6) {} // nothing!
  else //if(_num == 7)
    SiconosMatrixException::selfThrow(" SiconosMatrix *= (double) : invalid type of matrix");

  return m;
}

size_t SiconosMatrix::nnz(double tol)
{
  size_t nnz = 0;
  if (_num == 1) //dense
  {
    double* arr = getArray();
    for (size_t i = 0; i < size(0)*size(1); ++i)
    {
      if (fabs(arr[i]) > tol) { nnz++; }
    }
  }
  else if (_num == 4)
  {
    nnz = sparse()->nnz();
  }
  else
  {
     SiconosMatrixException::selfThrow("SiconosMatrix::nnz not implemented for the given matrix type");
  }

  return nnz;

}

bool SiconosMatrix::fillCSC(CSparseMatrix* csc, size_t row_off, size_t col_off, double tol)
{
  assert(csc);
  double* Mx = csc->x; // data
  CS_INT* Mi = csc->i; // row indx
  CS_INT* Mp = csc->p; // column pointers

  assert(Mp[col_off] >= 0);
  size_t nz = csc->p[col_off];

  size_t nrow = size(0);
  size_t ncol = size(1);

  CS_INT pval = Mp[col_off];

  if (_num == 1) //dense
  {
    double* arr = getArray();
    for (size_t j = 0, joff = col_off; j < ncol; ++j)
    {
      for (size_t i = 0; i < nrow; ++i)
      {
        // col-major
        double elt_val = arr[i + j*nrow];
        // std::cout << " a(i=" << i << ",j=" << j << ") = "<< elt_val << std::endl;
        if (fabs(elt_val) > tol)
        {
          Mx[pval] = elt_val;
          Mi[pval] = i + row_off;
          // std::cout << "Mx[" <<pval <<"] = " << Mx[pval]<<   std::endl;
          // std::cout << "Mp[" <<pval <<"] = " << Mi[pval]<<   std::endl;
          ++pval;
        }
      }
      // std::cout << "joff" << joff << std::endl;
      Mp[++joff] = pval;

    }
  }
  else if (_num == 4)
  {
    const Index& ptr = sparse()->index1_data();
    const Index& indx = sparse()->index2_data();
    const ublas::unbounded_array<double>& vals = sparse()->value_data();

    size_t nnz =  sparse()->nnz();

    assert(ptr.size() == ncol + 1);
    assert(indx.size() == nnz);
    assert(vals.size() == nnz);

    for (size_t i = 0; i < nnz; ++i)
    {
      Mx[pval] = vals[i];
      Mi[pval++] = row_off + indx[i];
    }
    for (size_t j = 1, joff = col_off + 1; j < ncol+1; ++j, ++joff)
    {
      Mp[joff] = nz + ptr[j];
    }
  }
  else
  {
     SiconosMatrixException::selfThrow("SiconosMatrix::fillCSC not implemented for the given matrix type");
  }

  return true;
}

bool SiconosMatrix::fillTriplet(CSparseMatrix* triplet, size_t row_off, size_t col_off, double tol)
{
  assert(triplet);
  size_t nrow = size(0);
  size_t ncol = size(1);

  if (_num == 1) //dense
  {
    double* arr = getArray();
    for (size_t j = 0; j < ncol; ++j)
    {
      for (size_t i = 0; i < nrow; ++i)
      {
        // col-major

        CSparseMatrix_zentry(triplet, i + row_off, j + col_off, arr[i + j*nrow] );
      }
    }
  }
  else
  {
     SiconosMatrixException::selfThrow("SiconosMatrix::fillCSC not implemented for the given matrix type");
  }

  return true;
}

std::ostream& operator<<(std::ostream& os, const SiconosMatrix& sm)
{
  os << sm.toString();
  return os;
}