//==================================================================================================
/*!
  @file

  @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_FUNCTION_ACSCPI_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_ACSCPI_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

 /*!

    @ingroup group-trigonometric
    Function object implementing acscpi capabilities

     inverse secant in degree: \f$(1/\pi) \arcsin(1/x)\f$.

    @par Semantic:

    For every parameter of floating type

    @code
    auto r = acscpi(x);
    @endcode

    @see acsc, acscd, asinpi, sinpi

  **/
  Value acscpi(Value const & v0);
} }
#endif

#include <boost/simd/function/scalar/acscpi.hpp>
#include <boost/simd/function/simd/acscpi.hpp>

#endif


#define BOOST_TEST_MODULE
#include <boost/test/unit_test.hpp>
#include <util/test_macros.hpp>
#include <string>
#include <random>
#include <set>
#include <sstream>
#include <vector>
#include <algorithm>
#include <util/cityhash_tc.hpp>

// Eigen
#include <Eigen/Core>
#include <Eigen/SparseCore>

// Optimization Interface
#include <optimization/optimization_interface.hpp>

// Solvers
#include <optimization/utils.hpp>
#include <optimization/newton_method-inl.hpp>
#include <optimization/gradient_descent-inl.hpp>
#include <optimization/accelerated_gradient-inl.hpp>
#include <optimization/lbfgs.hpp>

using namespace turi;
typedef Eigen::Matrix<double,Eigen::Dynamic,1>  DenseVector;
typedef Eigen::SparseVector<double> SparseVector;

/**
 * Solver interface for a sample problem. (Logistic regression)
 *
 * In this test case, we check all the algorithms using logistic regression
 * as a sample problem.
 *
 * Algorithms tested:
 *
 * (1) LBFGS
 * (2) FISTA
 * (3) GD
 * (4) Newton
 *
 *
 */
class opt_interface: public optimization::second_order_opt_interface
  {

  protected:

  size_t examples;                /**< Number of examples */
  size_t variables;               /**< Number of variables */
  size_t n_threads=4;             /** < Num threads */
  size_t it = 0;                  /** < Current example being used in SGD */
  size_t coordinate = 0;          /** < Current coordinate being used in SCD */

  DenseMatrix A;
  DenseVector b;

  public:

  /**
   * Default constructor.
   */
  opt_interface(DenseMatrix _A, DenseVector _b){
    examples = _A.rows();
    variables = _A.cols();
    A = _A;
    b = _b;
  }

  /**
   * Default destructor.
   */
  ~opt_interface(){
  }

  /**
   * Get the number of examples in the model
   *
   * \returns Number of examples
   */
  size_t num_examples() const{
    return examples;
  }

  /**
   * Get the number of variables in the model
   *
   * \returns Number of examples
   */
  size_t num_variables() const{
    return variables;
  }

  /**
   * Reset the source of randomness in the model.
   *
   * \param[in]  seed  Currently, this seed is the "start_index"
   */
  void reset(int seed){
    it = 0;
    coordinate = 0;
  }

  /**
   * Compute the hessian (for logistic regression) at the given point
   *
   * \param[in]  point   Point at which we are computing the function value.
   * \param[out] hessian Returns a dense hessian matrix.
   *
   * See logistic_regression.cpp for details
   */
  void compute_hessian(const DenseVector& point, DenseMatrix& hessian){
    // Code copied from logistic_regression.cpp
    hessian.setZero();
    for (size_t i=0; i < examples; i++){
      DenseVector x = A.row(i);
      double kernel = exp(-1.0 * (x.dot(point)));
      double row_prob = exp(-log1p(kernel));
      if (row_prob > optimization::OPTIMIZATION_ZERO &&
        row_prob < (1 - optimization::OPTIMIZATION_ZERO)) {
        DenseMatrix H = ((row_prob * (1.0 - row_prob)) * x) * x.transpose();
        hessian += H;
      }
    }
  }

  /**
   * Compute the first order stats at a random-coordinate (for
   * logistic_regression)
   *
   * \param[in]  point    Point at which we are computing the gradient.
   * \param[out] gradient Dense gradient
   *
   * \warning: This implementation is STUPID and is only for testing purposes.
   * Do not copy it over for logistic_regression
   *
   */
  void compute_first_order_stats_per_coordinate(const DenseVector &point, const
      size_t block_size, DenseVector& gradient, DenseVector& hessian_diag,
      size_t& block_start){

    // Code copied from logistic_regression.cpp
    DenseVector full_grad(variables);
    DenseVector full_diag_hessian(variables);
    full_grad.setZero();
    full_diag_hessian.setZero();

    for (size_t i=0; i < examples; i++){
      DenseVector x = A.row(i);
      double kernel = exp(-1.0 * (x.dot(point)));
      double row_prob = exp(-log1p(kernel));
      DenseVector G = x * (row_prob - b(i));
      full_grad += G;
      if (row_prob > optimization::OPTIMIZATION_ZERO &&
        row_prob < (1 - optimization::OPTIMIZATION_ZERO)) {
        full_diag_hessian  += ((row_prob * (1.0 - row_prob))
            * x).cwiseProduct(x);
      }
    }

    gradient = full_grad.segment(coordinate, block_size);
    hessian_diag = full_diag_hessian.segment(coordinate, block_size);

    block_start = coordinate;
    coordinate  = std::min(coordinate + block_size, variables);
    if (coordinate == variables){
      reset(0);
    }
  }

  /**
   * Compute the gradient at a "random" point. (Not tested yet)
   *
   * \param[in]  point    Point at which we are computing the gradient.
   * \param[out] gradient Dense gradient
   *
   * \warning: Currently, we do not test SGD in this test case. Will add
   * these ASAP.
   */
  void compute_gradient_per_example(const DenseVector& point, DenseVector&
      gradient){
  }

  /**
   * Compute the gradient at the given point. (for logistic_regression)
   *
   * \param[in]  point    Point at which we are computing the gradient.
   * \param[out] gradient Dense gradient
   * \param[in]  mbStart  Minibatch start index
   * \param[in]  mbSize   Minibatch size (-1 implies all)
   *
   * See logistic_regression.cpp for details
   */
  void compute_gradient(const DenseVector& point, DenseVector& gradient, const
      size_t mbStart=0, const size_t mbSize=-1){
    gradient.setZero();
    // Code copied from logistic_regression.cpp
    for (size_t i=0; i < examples; i++){
      DenseVector x = A.row(i);
      double kernel = exp(-1.0 * (x.dot(point)));
      double row_prob = exp(-log1p(kernel));
      DenseVector G = x * (row_prob - b(i));
      gradient += G;
    }
  }

  /**
   * Compute the function value at the given point. (for logistic_regression)
   *
   * \param[in]  point   Point at which we are computing the gradient.
   * \param[in]  mbStart Minibatch start index
   * \param[in]  mbSize  Minibatch size (-1 implies all)
   * \returns            Function value at "point"
   *
   * \note Code copied from logistic_regression.cpp
   */
  double compute_function_value(const DenseVector& point, const size_t
      mbStart=0, const size_t mbSize =-1){
    double func = 0;

    // Code copied from logistic_regression.cpp
    for (size_t i=0; i < examples; i++){
      DenseVector x = A.row(i);
      double margin = x.dot(point);
      double row_func;

      if (margin >= 0.0) {
        row_func = (1.0 - b(i)) * margin + log1p(exp(-margin));
      }
      else {
        row_func = log1p(exp(margin)) - b(i) * margin;
      }
      func += row_func;
    }
    return func;
  }

  /**
   * Compute first order statistics at the given point.
   *
   * \param[in]  point   Point at which we are computing the gradient.
   * \param[in,out]  gradient Gradient value
   * \param[in,out]  func_value   Function value.
   * \param[in]  mbStart Minibatch start index
   * \param[in]  mbSize  Minibatch size (-1 implies all)
   * \returns            Function value at "point"
   */
  void compute_first_order_statistics( const DenseVector& point, DenseVector&
      gradient, double& func, const size_t mbStart, const size_t
      mbSize) {
    func = 0;
    gradient.setZero();

    // Code copied from logistic_regression.cpp
    for (size_t i=0; i < examples; i++){
      DenseVector x = A.row(i);
      double margin = x.dot(point);
      double row_func;

      if (margin >= 0.0) {
        row_func = (1.0 - b(i)) * margin + log1p(exp(-margin));
      }
      else {
        row_func = log1p(exp(margin)) - b(i) * margin;
      }
      double kernel = exp(-1.0 * margin);
      double row_prob = exp(-log1p(kernel));
      func += row_func;
      DenseVector G = x * (row_prob - b(i));
      gradient += G;
    }
  }

  /**
   * Compute second order statistics at the given point.
   *
   * \param[in]  point        Point at which we are computing the gradient.
   * \param[in,out]  hessian  Hessian value
   * \param[in,out]  gradient Gradient value
   * \param[in,out]  func_value   Function value.
   * \param[in]  mbStart Minisizebatch start index
   * \param[in]  mbSize  Minibatch size (-1 implies all)
   * \returns            Function value at "point"
   */
  void compute_second_order_statistics(const DenseVector& point, DenseMatrix&
      hessian, DenseVector& gradient, double& func) {
    func = 0;
    gradient.setZero();
    hessian.setZero();

    // Code copied from logistic_regression.cpp
    for (size_t i=0; i < examples; i++){
      DenseVector x = A.row(i);
      double margin = x.dot(point);
      double row_func;

      if (margin >= 0.0) {
        row_func = (1.0 - b(i)) * margin + log1p(exp(-margin));
      }
      else {
        row_func = log1p(exp(margin)) - b(i) * margin;
      }
      func += row_func;
      double kernel = exp(-1.0 * (margin));
      double row_prob = exp(-log1p(kernel));
      gradient += x * (row_prob - b(i));
      if (row_prob > optimization::OPTIMIZATION_ZERO &&
        row_prob < (1 - optimization::OPTIMIZATION_ZERO)) {
        DenseMatrix H = ((row_prob * (1.0 - row_prob)) * x) * x.transpose();
        hessian += H;
      }
    }
  }

};

/**
 * Solver interface for a sample problem. (Logistic regression)
 *
 * In this test case, we check all the algorithms using logistic regression
 * as a sample problem.
 *
 * Algorithms tested:
 * ------------------
 *
 * (1) LBFGS
 * (2) FISTA
 * (3) GD
 * (4) Newton
 *
 * Utils tested:
 * ------------------
 *
 * (1) Gradient checker
 * (2) Hessian checker
 *
 *
 * Things not tested:
 * ------------------
 *
 * (1) Solvers + Regularizers
 * (2) Solvers + Constraints
 * (3)
 *
 *
 */

struct optimization_interface_test  {

  size_t examples;
  size_t variables;
  DenseMatrix A;
  DenseVector b;
  DenseVector init_point;
  DenseVector solution;
  std::shared_ptr<opt_interface> solver_interface;
  std::map<std::string, flexible_type> opts;

  public:

    optimization_interface_test() {
      size_t examples = 100;
      size_t variables = 10;

      DenseMatrix A(examples, variables);
      DenseVector b(examples);
      DenseVector init_point(variables);

      srand(1);
      A.setRandom();
      b.setRandom();
      init_point.setZero();

      std::shared_ptr<opt_interface> solver_interface;
      solver_interface.reset(new opt_interface(A, b));
      std::map<std::string, flexible_type> opts =  {
        {"convergence_threshold", 1e-5},
        {"step_size", 1.0},
        {"lbfgs_memory_level", 3},
        {"mini_batch_size", 1},
        {"max_iterations", 100},
        {"auto_tuning", true},
        {"solver", "newton"},
        {"l1_penalty", 0.0},
        {"l2_penalty", 0.0},
      };

      // Use Newton method to catch the solution
      optimization::solver_return stats;
      stats = turi::optimization::newton_method(*solver_interface,
          init_point, opts);

      this->opts = opts;
      this->A = A;
      this->b = b;
      this->examples = examples;
      this->variables = variables;
      this->solution = stats.solution;
      this->init_point = init_point;
      this->solver_interface = solver_interface;
    }

    /**
    * Tests with no-regularizer.
    * ------------------------------------------------------------------------
    */
    void test_gd(){
      optimization::solver_return stats;
      stats = turi::optimization::gradient_descent(*solver_interface,
          init_point, opts);
      TS_ASSERT(stats.solution.isApprox(solution, 1e-2));
    }

    void test_newton(){
      optimization::solver_return stats;
      stats = turi::optimization::newton_method(*solver_interface,
          init_point, opts);

      check_gradient_checker();
      check_hessian_checker();

      TS_ASSERT(std::abs(stats.residual) < 1e-5);
    }

    void test_lbfgs(){
      optimization::solver_return stats;
      stats = turi::optimization::lbfgs_compat(solver_interface,
          init_point, opts);
      TS_ASSERT(stats.solution.isApprox(solution, 1e-2));
    }

    void test_fista(){
      optimization::solver_return stats;
      stats = turi::optimization::accelerated_gradient(*solver_interface,
          init_point, opts);
      TS_ASSERT(stats.solution.isApprox(solution, 1e-2));
    }

    /**
    * Tests optimization utils.
    * ------------------------------------------------------------------------
    */

    void check_gradient_checker(){
      for(size_t i=0; i < 10; i++){
        DenseVector point(variables);
        point.setRandom();
        DenseVector gradient(variables);
        solver_interface->compute_gradient(point, gradient);
        TS_ASSERT(check_gradient(*solver_interface, point, gradient));
      }
    }

    void check_hessian_checker(){
      for(size_t i=0; i < 10; i++){
        DenseVector point(variables);
        point.setRandom();
        DenseMatrix hessian(variables, variables);
        solver_interface->compute_hessian(point, hessian);
        TS_ASSERT(check_hessian(*solver_interface, point, hessian));
      }
    }

};

BOOST_FIXTURE_TEST_SUITE(_optimization_interface_test, optimization_interface_test)
BOOST_AUTO_TEST_CASE(test_gd) {
  optimization_interface_test::test_gd();
}
BOOST_AUTO_TEST_CASE(test_newton) {
  optimization_interface_test::test_newton();
}
BOOST_AUTO_TEST_CASE(test_lbfgs) {
  optimization_interface_test::test_lbfgs();
}
BOOST_AUTO_TEST_CASE(test_fista) {
  optimization_interface_test::test_fista();
}
BOOST_AUTO_TEST_SUITE_END()


// #define R_BUILD
#ifdef R_BUILD

#include <Rcpp.h>
#include <RcppEigen.h>
// [[Rcpp::depends(RcppEigen)]]
using namespace Rcpp;

#else

#include <Eigen/Eigen>

#include "List.h"

#endif

#include <iostream>
#include <vector>

#include "Algorithm.h"
#include "AlgorithmGLM.h"
#include "AlgorithmPCA.h"
#include "utilities.h"
#include "workflow.h"

typedef Eigen::Triplet<double> triplet;

using namespace Eigen;
using namespace std;

// [[Rcpp::export]]
List abessGLM_API(Eigen::MatrixXd x, Eigen::MatrixXd y, int n, int p, int normalize_type, Eigen::VectorXd weight,
                  int algorithm_type, int model_type, int max_iter, int exchange_num, int path_type, bool is_warm_start,
                  int ic_type, double ic_coef, int Kfold, Eigen::VectorXi sequence, Eigen::VectorXd lambda_seq,
                  int s_min, int s_max, double lambda_min, double lambda_max, int nlambda, int screening_size,
                  Eigen::VectorXi g_index, Eigen::VectorXi always_select, int primary_model_fit_max_iter,
                  double primary_model_fit_epsilon, bool early_stop, bool approximate_Newton, int thread,
                  bool covariance_update, bool sparse_matrix, int splicing_type, int sub_search,
                  Eigen::VectorXi cv_fold_id, Eigen::VectorXi A_init) {
#ifdef _OPENMP
    // Eigen::initParallel();
    int max_thread = omp_get_max_threads();
    if (thread == 0 || thread > max_thread) {
        thread = max_thread;
    }

    Eigen::setNbThreads(thread);
    omp_set_num_threads(thread);

#endif
    int algorithm_list_size = max(thread, Kfold);
    vector<Algorithm<Eigen::VectorXd, Eigen::VectorXd, double, Eigen::MatrixXd> *> algorithm_list_uni_dense(
        algorithm_list_size);
    vector<Algorithm<Eigen::MatrixXd, Eigen::MatrixXd, Eigen::VectorXd, Eigen::MatrixXd> *> algorithm_list_mul_dense(
        algorithm_list_size);
    vector<Algorithm<Eigen::VectorXd, Eigen::VectorXd, double, Eigen::SparseMatrix<double>> *>
        algorithm_list_uni_sparse(algorithm_list_size);
    vector<Algorithm<Eigen::MatrixXd, Eigen::MatrixXd, Eigen::VectorXd, Eigen::SparseMatrix<double>> *>
        algorithm_list_mul_sparse(algorithm_list_size);

    for (int i = 0; i < algorithm_list_size; i++) {
        if (!sparse_matrix) {
            if (model_type == 1) {
                abessLm<Eigen::MatrixXd> *temp = new abessLm<Eigen::MatrixXd>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                temp->covariance_update = covariance_update;
                algorithm_list_uni_dense[i] = temp;
            } else if (model_type == 2) {
                abessLogistic<Eigen::MatrixXd> *temp = new abessLogistic<Eigen::MatrixXd>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                temp->approximate_Newton = approximate_Newton;
                algorithm_list_uni_dense[i] = temp;
            } else if (model_type == 3) {
                abessPoisson<Eigen::MatrixXd> *temp = new abessPoisson<Eigen::MatrixXd>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                temp->approximate_Newton = approximate_Newton;
                algorithm_list_uni_dense[i] = temp;
            } else if (model_type == 4) {
                abessCox<Eigen::MatrixXd> *temp = new abessCox<Eigen::MatrixXd>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                temp->approximate_Newton = approximate_Newton;
                algorithm_list_uni_dense[i] = temp;
            } else if (model_type == 5) {
                abessMLm<Eigen::MatrixXd> *temp = new abessMLm<Eigen::MatrixXd>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                temp->covariance_update = covariance_update;
                algorithm_list_mul_dense[i] = temp;
            } else if (model_type == 6) {
                abessMultinomial<Eigen::MatrixXd> *temp = new abessMultinomial<Eigen::MatrixXd>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                temp->approximate_Newton = approximate_Newton;
                algorithm_list_mul_dense[i] = temp;
            } else if (model_type == 8) {
                abessGamma<Eigen::MatrixXd> *temp = new abessGamma<Eigen::MatrixXd>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                temp->approximate_Newton = approximate_Newton;
                algorithm_list_uni_dense[i] = temp;
            } else if (model_type == 9) {
                abessOrdinal<Eigen::MatrixXd> *temp = new abessOrdinal<Eigen::MatrixXd>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                algorithm_list_mul_dense[i] = temp;
            }
        } else {
            if (model_type == 1) {
                abessLm<Eigen::SparseMatrix<double>> *temp = new abessLm<Eigen::SparseMatrix<double>>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                temp->covariance_update = covariance_update;
                algorithm_list_uni_sparse[i] = temp;
            } else if (model_type == 2) {
                abessLogistic<Eigen::SparseMatrix<double>> *temp = new abessLogistic<Eigen::SparseMatrix<double>>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                temp->approximate_Newton = approximate_Newton;
                algorithm_list_uni_sparse[i] = temp;
            } else if (model_type == 3) {
                abessPoisson<Eigen::SparseMatrix<double>> *temp = new abessPoisson<Eigen::SparseMatrix<double>>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                temp->approximate_Newton = approximate_Newton;
                algorithm_list_uni_sparse[i] = temp;
            } else if (model_type == 4) {
                abessCox<Eigen::SparseMatrix<double>> *temp = new abessCox<Eigen::SparseMatrix<double>>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                temp->approximate_Newton = approximate_Newton;
                algorithm_list_uni_sparse[i] = temp;
            } else if (model_type == 5) {
                abessMLm<Eigen::SparseMatrix<double>> *temp = new abessMLm<Eigen::SparseMatrix<double>>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                temp->covariance_update = covariance_update;
                algorithm_list_mul_sparse[i] = temp;
            } else if (model_type == 6) {
                abessMultinomial<Eigen::SparseMatrix<double>> *temp = new abessMultinomial<Eigen::SparseMatrix<double>>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                temp->approximate_Newton = approximate_Newton;
                algorithm_list_mul_sparse[i] = temp;
            } else if (model_type == 8) {
                abessGamma<Eigen::SparseMatrix<double>> *temp = new abessGamma<Eigen::SparseMatrix<double>>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                temp->approximate_Newton = approximate_Newton;
                algorithm_list_uni_sparse[i] = temp;
            } else if (model_type == 9) {
                abessOrdinal<Eigen::SparseMatrix<double>> *temp = new abessOrdinal<Eigen::SparseMatrix<double>>(
                    algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                    is_warm_start, exchange_num, always_select, splicing_type, sub_search);
                algorithm_list_mul_sparse[i] = temp;
            }
        }
    }

    // parameter list
    Parameters parameters(sequence, lambda_seq, s_min, s_max);

    List out_result;
    if (!sparse_matrix) {
        if (y.cols() == 1 && model_type != 5 && model_type != 6) {
            Eigen::VectorXd y_vec = y.col(0).eval();

            out_result = abessWorkflow<Eigen::VectorXd, Eigen::VectorXd, double, Eigen::MatrixXd>(
                x, y_vec, n, p, normalize_type, weight, algorithm_type, path_type, is_warm_start, ic_type, ic_coef,
                Kfold, parameters, screening_size, g_index, early_stop, thread, sparse_matrix, cv_fold_id, A_init,
                algorithm_list_uni_dense);
        } else {
            out_result = abessWorkflow<Eigen::MatrixXd, Eigen::MatrixXd, Eigen::VectorXd, Eigen::MatrixXd>(
                x, y, n, p, normalize_type, weight, algorithm_type, path_type, is_warm_start, ic_type, ic_coef, Kfold,
                parameters, screening_size, g_index, early_stop, thread, sparse_matrix, cv_fold_id, A_init,
                algorithm_list_mul_dense);
        }
    } else {
        Eigen::SparseMatrix<double> sparse_x(n, p);

        // std::vector<triplet> tripletList;
        // tripletList.reserve(x.rows());
        // for (int i = 0; i < x.rows(); i++)
        // {
        //   tripletList.push_back(triplet(int(x(i, 1)), int(x(i, 2)), x(i, 0)));
        // }
        // sparse_x.setFromTriplets(tripletList.begin(), tripletList.end());

        sparse_x.reserve(x.rows());
        for (int i = 0; i < x.rows(); i++) {
            sparse_x.insert(int(x(i, 1)), int(x(i, 2))) = x(i, 0);
        }
        sparse_x.makeCompressed();

        if (y.cols() == 1 && model_type != 5 && model_type != 6) {
            Eigen::VectorXd y_vec = y.col(0).eval();

            out_result = abessWorkflow<Eigen::VectorXd, Eigen::VectorXd, double, Eigen::SparseMatrix<double>>(
                sparse_x, y_vec, n, p, normalize_type, weight, algorithm_type, path_type, is_warm_start, ic_type,
                ic_coef, Kfold, parameters, screening_size, g_index, early_stop, thread, sparse_matrix, cv_fold_id,
                A_init, algorithm_list_uni_sparse);
        } else {
            out_result = abessWorkflow<Eigen::MatrixXd, Eigen::MatrixXd, Eigen::VectorXd, Eigen::SparseMatrix<double>>(
                sparse_x, y, n, p, normalize_type, weight, algorithm_type, path_type, is_warm_start, ic_type, ic_coef,
                Kfold, parameters, screening_size, g_index, early_stop, thread, sparse_matrix, cv_fold_id, A_init,
                algorithm_list_mul_sparse);
        }
    }

    for (int i = 0; i < algorithm_list_size; i++) {
        delete algorithm_list_uni_dense[i];
        delete algorithm_list_mul_dense[i];
        delete algorithm_list_uni_sparse[i];
        delete algorithm_list_mul_sparse[i];
    }

    return out_result;
};

// [[Rcpp::export]]
List abessPCA_API(Eigen::MatrixXd x, int n, int p, int normalize_type, Eigen::VectorXd weight, Eigen::MatrixXd sigma,
                  int max_iter, int exchange_num, int path_type, bool is_warm_start, int ic_type, double ic_coef,
                  int Kfold, Eigen::MatrixXi sequence, int s_min, int s_max, int screening_size,
                  Eigen::VectorXi g_index, Eigen::VectorXi always_select, bool early_stop, int thread,
                  bool sparse_matrix, int splicing_type, int sub_search, Eigen::VectorXi cv_fold_id, int pca_num,
                  Eigen::VectorXi A_init) {
    /* this function for abessPCA only (model_type == 7) */

#ifdef _OPENMP
    // Eigen::initParallel();
    int max_thread = omp_get_max_threads();
    if (thread == 0 || thread > max_thread) {
        thread = max_thread;
    }

    Eigen::setNbThreads(thread);
    omp_set_num_threads(thread);
#endif
    int model_type = 7, algorithm_type = 6;
    Eigen::VectorXd lambda_seq = Eigen::VectorXd::Zero(1);
    int lambda_min = 0, lambda_max = 0, nlambda = 100;
    int primary_model_fit_max_iter = 1;
    double primary_model_fit_epsilon = 1e-3;
    int pca_n = -1;
    sub_search = 0;
    if (!sparse_matrix && n != x.rows()) {
        pca_n = n;
        n = x.rows();
    }
    Eigen::VectorXd y_vec = Eigen::VectorXd::Zero(n);

    //////////////////// function generate_algorithm_pointer() ////////////////////////////
    int algorithm_list_size = max(thread, Kfold);
    vector<Algorithm<Eigen::VectorXd, Eigen::VectorXd, double, Eigen::MatrixXd> *> algorithm_list_uni_dense(
        algorithm_list_size);
    vector<Algorithm<Eigen::VectorXd, Eigen::VectorXd, double, Eigen::SparseMatrix<double>> *>
        algorithm_list_uni_sparse(algorithm_list_size);
    for (int i = 0; i < algorithm_list_size; i++) {
        if (!sparse_matrix) {
            abessPCA<Eigen::MatrixXd> *temp = new abessPCA<Eigen::MatrixXd>(
                algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                is_warm_start, exchange_num, always_select, splicing_type, sub_search);
            temp->is_cv = Kfold > 1;
            temp->pca_n = pca_n;
            temp->sigma = sigma;
            algorithm_list_uni_dense[i] = temp;
        } else {
            abessPCA<Eigen::SparseMatrix<double>> *temp = new abessPCA<Eigen::SparseMatrix<double>>(
                algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                is_warm_start, exchange_num, always_select, splicing_type, sub_search);
            temp->is_cv = Kfold > 1;
            temp->pca_n = pca_n;
            temp->sigma = sigma;
            algorithm_list_uni_sparse[i] = temp;
        }
    }

    // call `abessWorkflow` for result
#ifdef R_BUILD
    List out_result(pca_num);
#else
    List out_result;
#endif
    List out_result_next;
    int num = 0;

    if (!sparse_matrix) {
        while (num++ < pca_num) {
            int pca_support_size_num = sequence.col(num - 1).sum();
            Eigen::VectorXi pca_support_size(pca_support_size_num);
            // map sequence matrix to support.size
            int non_zero_num = 0;
            for (int i = 0; i < sequence.rows(); i++) {
                if (sequence(i, num - 1) == 1) {
                    pca_support_size(non_zero_num++) = i + 1;
                }
            }

            // parameter list
            Parameters parameters(pca_support_size, lambda_seq, s_min, s_max);

            out_result_next = abessWorkflow<Eigen::VectorXd, Eigen::VectorXd, double, Eigen::MatrixXd>(
                x, y_vec, n, p, normalize_type, weight, algorithm_type, path_type, is_warm_start, ic_type, ic_coef,
                Kfold, parameters, screening_size, g_index, early_stop, thread, sparse_matrix, cv_fold_id, A_init,
                algorithm_list_uni_dense);
            Eigen::VectorXd beta_next;
#ifdef R_BUILD
            beta_next = out_result_next["beta"];
#else
            out_result_next.get_value_by_name("beta", beta_next);
#endif
            if (num == 1) {
#ifdef R_BUILD
                if (pca_num > 1) {
                    out_result(0) = out_result_next;
                } else {
                    out_result = out_result_next;
                }
#else
                out_result = out_result_next;
#endif
            } else {
#ifdef R_BUILD
                // Eigen::MatrixXd beta_new(p, num);
                // Eigen::VectorXd temp = out_result["beta"];
                // Eigen::Map<Eigen::MatrixXd> temp2(temp.data(), p, num - 1);
                // beta_new << temp2, beta_next;
                // out_result["beta"] = beta_new;
                out_result(num - 1) = out_result_next;
#else
                out_result.combine_beta(beta_next);
#endif
            }

            if (num < pca_num) {
                Eigen::MatrixXd temp = beta_next * beta_next.transpose();
                if (Kfold > 1) {
                    x -= x * temp;
                } else {
                    Eigen::MatrixXd temp1 = temp * sigma;
                    sigma += temp1 * temp - temp1 - temp1.transpose();
                    for (int i = 0; i < algorithm_list_size; i++) {
                        abessPCA<Eigen::MatrixXd> *pca_model =
                            dynamic_cast<abessPCA<Eigen::MatrixXd> *>(algorithm_list_uni_dense[i]);
                        if (pca_model) {
                            // cout << "update Sigma"<<endl;
                            pca_model->sigma = sigma;
                        }
                    }
                }
            }
        }
    } else {
        Eigen::SparseMatrix<double> sparse_x(n, p);

        // std::vector<triplet> tripletList;
        // tripletList.reserve(x.rows());
        // for (int i = 0; i < x.rows(); i++)
        // {
        //   tripletList.push_back(triplet(int(x(i, 1)), int(x(i, 2)), x(i, 0)));
        // }
        // sparse_x.setFromTriplets(tripletList.begin(), tripletList.end());

        sparse_x.reserve(x.rows());
        for (int i = 0; i < x.rows(); i++) {
            sparse_x.insert(int(x(i, 1)), int(x(i, 2))) = x(i, 0);
        }
        sparse_x.makeCompressed();

        while (num++ < pca_num) {
            int pca_support_size_num = sequence.col(num - 1).sum();
            Eigen::VectorXi pca_support_size(pca_support_size_num);
            // map sequence matrix to support.size
            int non_zero_num = 0;
            for (int i = 0; i < sequence.rows(); i++) {
                if (sequence(i, num - 1) == 1) {
                    pca_support_size(non_zero_num++) = i + 1;
                }
            }

            // parameter list
            Parameters parameters(pca_support_size, lambda_seq, s_min, s_max);

            out_result_next = abessWorkflow<Eigen::VectorXd, Eigen::VectorXd, double, Eigen::SparseMatrix<double>>(
                sparse_x, y_vec, n, p, normalize_type, weight, algorithm_type, path_type, is_warm_start, ic_type,
                ic_coef, Kfold, parameters, screening_size, g_index, early_stop, thread, sparse_matrix, cv_fold_id,
                A_init, algorithm_list_uni_sparse);
            Eigen::VectorXd beta_next;
#ifdef R_BUILD
            beta_next = out_result_next["beta"];
#else
            out_result_next.get_value_by_name("beta", beta_next);
#endif
            if (num == 1) {
#ifdef R_BUILD
                if (pca_num > 1) {
                    out_result(0) = out_result_next;
                } else {
                    out_result = out_result_next;
                }
#else
                out_result = out_result_next;
#endif
            } else {
#ifdef R_BUILD
                // Eigen::MatrixXd beta_new(p, num);
                // Eigen::VectorXd temp = out_result["beta"];
                // Eigen::Map<Eigen::MatrixXd> temp2(temp.data(), p, num - 1);
                // beta_new << temp2, beta_next;
                // out_result["beta"] = beta_new;
                out_result(num - 1) = out_result_next;
#else
                out_result.combine_beta(beta_next);
#endif
            }

            // update for next PCA
            if (num < pca_num) {
                Eigen::MatrixXd temp = beta_next * beta_next.transpose();
                if (Kfold > 1) {
                    sparse_x = sparse_x - sparse_x * temp;
                } else {
                    Eigen::MatrixXd temp1 = temp * sigma;
                    sigma += temp1 * temp - temp1 - temp1.transpose();
                    for (int i = 0; i < algorithm_list_size; i++) {
                        abessPCA<Eigen::SparseMatrix<double>> *pca_model =
                            dynamic_cast<abessPCA<Eigen::SparseMatrix<double>> *>(algorithm_list_uni_sparse[i]);
                        if (pca_model) {
                            // cout << "update Sigma"<<endl;
                            pca_model->sigma = sigma;
                        }
                    }
                }
            }
        }
    }

    for (int i = 0; i < algorithm_list_size; i++) {
        delete algorithm_list_uni_dense[i];
        delete algorithm_list_uni_sparse[i];
    }
    return out_result;
};

// [[Rcpp::export]]
List abessRPCA_API(Eigen::MatrixXd x, int n, int p, int max_iter, int exchange_num, int path_type, bool is_warm_start,
                   int ic_type, double ic_coef, Eigen::VectorXi sequence,
                   Eigen::VectorXd lambda_seq,  // rank of L
                   int s_min, int s_max, double lambda_min, double lambda_max, int nlambda, int screening_size,
                   int primary_model_fit_max_iter, double primary_model_fit_epsilon, Eigen::VectorXi g_index,
                   Eigen::VectorXi always_select, bool early_stop, int thread, bool sparse_matrix, int splicing_type,
                   int sub_search, Eigen::VectorXi A_init) {
#ifdef _OPENMP
    // Eigen::initParallel();
    int max_thread = omp_get_max_threads();
    if (thread == 0 || thread > max_thread) {
        thread = max_thread;
    }

    Eigen::setNbThreads(thread);
    omp_set_num_threads(thread);

#endif
    int model_type = 10, algorithm_type = 6;
    int Kfold = 1;
    int normalize_type = 0;
    Eigen::VectorXi cv_fold_id = Eigen::VectorXi::Zero(0);
    Eigen::VectorXd weight = Eigen::VectorXd::Ones(n);
    Eigen::VectorXd y_vec = Eigen::VectorXd::Zero(n);

    int algorithm_list_size = max(thread, Kfold);
    vector<Algorithm<Eigen::VectorXd, Eigen::VectorXd, double, Eigen::MatrixXd> *> algorithm_list_uni_dense(
        algorithm_list_size);
    vector<Algorithm<Eigen::VectorXd, Eigen::VectorXd, double, Eigen::SparseMatrix<double>> *>
        algorithm_list_uni_sparse(algorithm_list_size);

    for (int i = 0; i < algorithm_list_size; i++) {
        if (!sparse_matrix) {
            abessRPCA<Eigen::MatrixXd> *temp = new abessRPCA<Eigen::MatrixXd>(
                algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                is_warm_start, exchange_num, always_select, splicing_type, sub_search);
            temp->r = lambda_seq(0);
            algorithm_list_uni_dense[i] = temp;
        } else {
            abessRPCA<Eigen::SparseMatrix<double>> *temp = new abessRPCA<Eigen::SparseMatrix<double>>(
                algorithm_type, model_type, max_iter, primary_model_fit_max_iter, primary_model_fit_epsilon,
                is_warm_start, exchange_num, always_select, splicing_type, sub_search);
            temp->r = lambda_seq(0);
            algorithm_list_uni_sparse[i] = temp;
        }
    }

    // parameter list
    Parameters parameters(sequence, lambda_seq, s_min, s_max);

    List out_result;
    if (!sparse_matrix) {
        out_result = abessWorkflow<Eigen::VectorXd, Eigen::VectorXd, double, Eigen::MatrixXd>(
            x, y_vec, n, p, normalize_type, weight, algorithm_type, path_type, is_warm_start, ic_type, ic_coef, Kfold,
            parameters, screening_size, g_index, early_stop, thread, sparse_matrix, cv_fold_id, A_init,
            algorithm_list_uni_dense);

    } else {
        Eigen::SparseMatrix<double> sparse_x(n, p);

        // std::vector<triplet> tripletList;
        // tripletList.reserve(x.rows());
        // for (int i = 0; i < x.rows(); i++)
        // {
        //   tripletList.push_back(triplet(int(x(i, 1)), int(x(i, 2)), x(i, 0)));
        // }
        // sparse_x.setFromTriplets(tripletList.begin(), tripletList.end());

        sparse_x.reserve(x.rows());
        for (int i = 0; i < x.rows(); i++) {
            sparse_x.insert(int(x(i, 1)), int(x(i, 2))) = x(i, 0);
        }
        sparse_x.makeCompressed();

        out_result = abessWorkflow<Eigen::VectorXd, Eigen::VectorXd, double, Eigen::SparseMatrix<double>>(
            sparse_x, y_vec, n, p, normalize_type, weight, algorithm_type, path_type, is_warm_start, ic_type, ic_coef,
            Kfold, parameters, screening_size, g_index, early_stop, thread, sparse_matrix, cv_fold_id, A_init,
            algorithm_list_uni_sparse);
    }

    for (int i = 0; i < algorithm_list_size; i++) {
        delete algorithm_list_uni_dense[i];
        delete algorithm_list_uni_sparse[i];
    }

    return out_result;
}


/*
 test/eval/static_eval.cpp

 Copyright 2013 Karsten Ahnert

 Distributed under the Boost Software License, Version 1.0.
 (See accompanying file LICENSE_1_0.txt or
 copy at http://www.boost.org/LICENSE_1_0.txt)
 */

#include <gpcxx/eval/static_eval.hpp>

#include "../common/test_tree.hpp"

#include <boost/fusion/include/make_vector.hpp>
#include <boost/math/constants/constants.hpp>

#include <gtest/gtest.h>

#include <array>
#include <sstream>
#include <cmath>
#include <functional>

#define TESTNAME basic_eval_tests

using namespace std;

namespace fusion = boost::fusion;

TEST( TESTNAME , test1 )
{
    typedef double value_type;
    typedef std::array< double , 2 > context_type;
    typedef std::string symbol_type;

    test_tree< basic_tree_tag > trees;

    auto eval = gpcxx::make_static_eval< value_type , symbol_type , context_type >(
        fusion::make_vector(
                 fusion::make_vector( "1" , []( context_type const& t ) { return 1.0; } )
               , fusion::make_vector( "2" , []( context_type const& t ) { return 2.0; } )
               , fusion::make_vector( "x" , []( context_type const& t ) { return t[0]; } )
               , fusion::make_vector( "y" , []( context_type const& t ) { return t[1]; } )
                ) ,
        fusion::make_vector(
                 fusion::make_vector( "sin" , []( double v ) -> double { return std::sin( v ); } )
               , fusion::make_vector( "cos" , []( double v ) -> double { return std::cos( v ); } )
                ) ,
        fusion::make_vector(
                 fusion::make_vector( "plus" , std::plus< double >() )
               , fusion::make_vector( "minus" , std::minus< double >() )
                ) );

    EXPECT_EQ( eval.get_terminal_symbols() , std::vector< std::string >( { "1" , "2" , "x" , "y" } ) );
    EXPECT_EQ( eval.get_unary_symbols() , std::vector< std::string >( { "sin" , "cos" } ) );
    EXPECT_EQ( eval.get_binary_symbols() , std::vector< std::string >( { "plus" , "minus" } ) );

    context_type context1 = {{ 0.5 * boost::math::double_constants::pi , 3.5 }};
    double val1 = eval( trees.data , context1 );
    EXPECT_DOUBLE_EQ( val1 , 2.5 );

    context_type context2 = {{ 5.0 , boost::math::double_constants::pi }};
    double val2 = eval( trees.data2 , context2 );
    EXPECT_DOUBLE_EQ( val2 , -6.0 );
}

TEST( TESTNAME , test2 )
{
    typedef double value_type;
    typedef std::array< double , 2 > context_type;
    typedef std::string symbol_type;

    test_tree< basic_tree_tag > trees;

    auto eval = gpcxx::make_static_eval< value_type , symbol_type , context_type >(
        fusion::make_vector(
                 fusion::make_vector( "1" , []( context_type const& t ) { return 1.0; } )
               , fusion::make_vector( "2" , []( context_type const& t ) { return 2.0; } )
//               , fusion::make_vector( "x" , []( context_type const& t ) { return t[0]; } )
               , fusion::make_vector( "y" , []( context_type const& t ) { return t[1]; } )
                ) ,
        fusion::make_vector(
                 fusion::make_vector( "sin" , []( double v ) -> double { return std::sin( v ); } )
               , fusion::make_vector( "cos" , []( double v ) -> double { return std::cos( v ); } )
                ) ,
        fusion::make_vector(
                 fusion::make_vector( "plus" , std::plus< double >() )
               , fusion::make_vector( "minus" , std::minus< double >() )
                ) );

    context_type context1 = {{ 0.5 * boost::math::double_constants::pi , 3.5 }};
    EXPECT_THROW(
        /* double val1 = */ eval( trees.data , context1 )
        , gpcxx::gpcxx_exception );

    context_type context2 = {{ 5.0 , boost::math::double_constants::pi }};
    EXPECT_THROW(
        /* double val2 = */ eval( trees.data2 , context2 )
        , gpcxx::gpcxx_exception );

}


#include <iostream>
#include <utility>
#include <type_traits>
#include <cstdint>

// --------------------------------------------------------------------------

template <typename T, T Value>
struct meta_int : public std::integral_constant<T, Value> {

	template <typename TT, typename = typename std::enable_if<std::is_convertible<T, TT>::value>::type>
	constexpr operator TT() const { return TT(Value); }

	static constexpr T get() { return Value; }

};

#define META { return { }; }

#define META_INT_BINARY_OPERATOR(op) \
	template <typename T0, T0 v0, typename T1, T1 v1> \
	constexpr auto operator op(meta_int<T0, v0>, meta_int<T1, v1>) \
		-> meta_int<decltype(v0 op v1), (v0 op v1)> META
META_INT_BINARY_OPERATOR(+)
META_INT_BINARY_OPERATOR(-)
META_INT_BINARY_OPERATOR(*)
META_INT_BINARY_OPERATOR(/)
META_INT_BINARY_OPERATOR(%)
META_INT_BINARY_OPERATOR(&)
META_INT_BINARY_OPERATOR(|)
META_INT_BINARY_OPERATOR(==)
META_INT_BINARY_OPERATOR(!=)
//META_INT_BINARY_OPERATOR(>)
META_INT_BINARY_OPERATOR(<)
META_INT_BINARY_OPERATOR(>=)
META_INT_BINARY_OPERATOR(<=)
#undef META_INT_BINARY_OPERATOR

template <typename T0, T0 v0, typename T1, T1 v1, typename = typename std::enable_if<(v0 <= v1)>::type>
constexpr auto min(meta_int<T0, v0>, meta_int<T1, v1>) -> meta_int<T0, v0> META
template <typename T0, T0 v0, typename T1, T1 v1, typename = typename std::enable_if<(v0 > v1)>::type>
constexpr auto min(meta_int<T0, v0>, meta_int<T1, v1>) -> meta_int<T1, v1> META

template <typename T0, T0 v0, typename T1, T1 v1, typename = typename std::enable_if<(v0 >= v1)>::type>
constexpr auto max(meta_int<T0, v0>, meta_int<T1, v1>) -> meta_int<T0, v0> META
template <typename T0, T0 v0, typename T1, T1 v1, typename = typename std::enable_if<(v0 < v1)>::type>
constexpr auto max(meta_int<T0, v0>, meta_int<T1, v1>) -> meta_int<T1, v1> META

template <std::size_t Accum, char... Chars>
struct make_meta_int {
	static_assert(sizeof...(Chars) == 0, "bad overload");
	typedef meta_int<std::size_t, Accum> type;
};

template <std::size_t Accum, char C0, char... Chars>
struct make_meta_int<Accum, C0, Chars...> {
	static_assert(C0 >= '0' && C0 <= '9', "invalid digit");
	static_assert((Accum == 0 || Accum * 10 > Accum) && (Accum * 10) / 10 == Accum, "overflow detected");
	static_assert(C0 != 0 || sizeof...(Chars) == 0, "hex and octal formats not supported");
	typedef typename make_meta_int<Accum * 10 + std::size_t(C0 - '0'), Chars...>::type type;
};

// TODO support float and hex and octal literals?
template <char... Chars>
constexpr auto operator "" _m() -> typename make_meta_int<0, Chars...>::type META

#define META_RETURN_IF(Cond, Result) \
	typename std::enable_if<decltype(Cond)::value, decltype(Result)>::type META

#define META_RETURN(Result) \
	decltype(Result) META

template <typename T, T... Chars>
struct meta_string;

template <typename T, T... A, T... B>
constexpr auto operator+(meta_string<T, A...>, meta_string<T, B...>) -> meta_string<T, A..., B...> META

template <typename T, T... A, T B>
constexpr auto operator+(meta_string<T, A...>, meta_int<T, B>) -> meta_string<T, A..., B> META

template <typename T, T A, T... B>
constexpr auto operator+(meta_int<T, A>, meta_string<T, B...>) -> meta_string<T, A, B...> META

template <typename T, T... Chars>
struct meta_string {

	typedef T char_type;

	static constexpr std::size_t _length = sizeof...(Chars);

	typedef meta_int<bool, false> False;
	typedef meta_int<bool, true> True;

	typedef meta_int<std::size_t, 0> Zero;
	typedef meta_int<std::size_t, 1> One;
	typedef meta_int<std::size_t, _length> End;

	static constexpr T _data[_length] = { Chars... };

	template <T C>
	struct _append { typedef meta_string<T, Chars..., C> type; };

	// ----

	static constexpr const T * data() { return _data; }

	// ----

	static constexpr auto length() -> meta_int<std::size_t, _length> META
	static constexpr auto size() -> META_RETURN(length())
	static constexpr auto empty() -> META_RETURN(size() == 0_m)

	static constexpr auto npos() -> meta_int<std::size_t, std::size_t(-1)> META

	template <typename Index>
	static constexpr auto at(Index) -> meta_int<T, _data[Index::value]> META

	/* TODO
	static constexpr auto head(Zero) -> meta_string<T> META
	template <typename Length>
	static constexpr auto head(Length l) -> META_RETURN_IF(
		l != 0_m,
		head(l - 1_m) + at(l - 1_m)
	)
	*/
	template <std::size_t Length, typename Dummy = void>
	struct _head {
		typedef decltype(typename _head<Length - 1>::type() + at(meta_int<std::size_t, Length - 1>())) type;
	};
	template <typename Dummy>
	struct _head<0, Dummy> {
		typedef meta_string<T> type;
	};
	template <typename Length>
	static constexpr auto head(Length) -> typename _head<Length::value>::type META

	/* TODO
	static constexpr auto tail(Zero) -> meta_string<T> META
	template <typename Length>
	static constexpr auto tail(Length l) -> META_RETURN_IF(
		l != 0_m,
		at(length() - l) + tail(l - 1_m)
	)
	*/
	template <std::size_t Length, typename Dummy = void>
	struct _tail {
		typedef decltype(at(meta_int<std::size_t, length() - Length>()) + typename _tail<Length - 1>::type()) type;
	};
	template <typename Dummy>
	struct _tail<0, Dummy> {
		typedef meta_string<T> type;
	};
	template <typename Length>
	static constexpr auto tail(Length) -> typename _tail<Length::value>::type META

	// ----

	// TODO s + l > length()
	// TODO check 0 <= s < length()
	template <typename Start, typename Length>
	static constexpr auto substr(Start s, Length l = npos()) -> META_RETURN(
		tail(length() - s).head(l)
	)

	// TODO s + l > length()
	// TODO check 0 <= s < length()
	template <typename Start, typename Length, typename Replacement>
	static constexpr auto replace(Start s, Length l, Replacement r) -> META_RETURN(
		head(s) + r + tail(length() - s - l)
	)

	// TODO s + l > length()
	// TODO check 0 <= s < length()
	template <typename Start, typename Length>
	static constexpr auto erase(Start s, Length l = npos()) -> META_RETURN(
		head(s) + tail(length() - s - l)
	)
	/*
	// TODO OR NOT TODO, THAT IS THE QUESTION. check 0 <= p < length()
	template <typename Pos, typename String>
	static constexpr auto insert(Pos p, String s) -> META_RETURN(
		replace(p, 0_m, s)
	)
	*/
};

template <typename T, T... Chars>
constexpr std::size_t meta_string<T, Chars...>::_length;

template <typename T, T... Chars>
constexpr T meta_string<T, Chars...>::_data[meta_string<T, Chars...>::_length];

template <char... Chars>
std::ostream & operator<<(std::ostream & os, meta_string<char, Chars...> str) {
	os.write(str.data(), str.length());
	return os;
}
template <typename T, T Value>
std::ostream & operator<<(std::ostream & os, meta_int<T, Value>) {
	return os << Value;
}

// --------------------------------------------------------------------------

template <
	typename Wrapper,
	typename Prefix = meta_string<
		typename std::decay<decltype(*Wrapper::value)>::type
	>,
	std::size_t Remaining = sizeof(Wrapper::value) / sizeof(*Wrapper::value)
>
struct to_meta_string {
private:
	static constexpr std::size_t length = sizeof(Wrapper::value) / sizeof(*Wrapper::value);
	typedef meta_int<typename Prefix::char_type, Wrapper::value[length - Remaining]> next_char;
public:
	typedef typename to_meta_string<
		Wrapper,
		decltype(Prefix() + next_char()),
		Remaining - 1
	>::type type;
};

template <typename Wrapper, typename Prefix>
struct to_meta_string<Wrapper, Prefix, 0> {
	typedef Prefix type;
};

#define STRING_WRAPPER_NAME(name) raw_ ## name ## _wrapper

#define DECLARE_STRING_WRAPPER(name, str) \
struct STRING_WRAPPER_NAME(name) { \
	static constexpr char value[] = str; \
};

#define DECLARE_STRING(name, str) \
	DECLARE_STRING_WRAPPER(name, str) \
	static constexpr typename to_meta_string<STRING_WRAPPER_NAME(name)>::type name { };

// --------------------------------------------------------------------------

#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp>
#include <boost/preprocessor/config/limits.hpp>

#define S_DECOMPOSE_HELPER(z, n, data) \
	BOOST_PP_COMMA_IF(n) (n < sizeof(data) ? data[n] : '\0')

#define S_DECOMPOSE(STR)  \
	BOOST_PP_REPEAT(BOOST_PP_LIMIT_REPEAT, S_DECOMPOSE_HELPER, STR)

#define S(s) meta_string<char, S_DECOMPOSE(s)>().head(meta_int<std::size_t, sizeof(s) - 1>())

// --------------------------------------------------------------------------

#include "type_name.hpp"

DECLARE_STRING(long_string, "This string can be however long you want it to be!")

int main() {

	auto str = S("Hello") + S(" ") + S("World") + S("!");
	std::cout << type_name(str) << std::endl;

	auto modified = str.substr(1_m, 5_m).replace(1_m, 2_m, S("?-?"));
	std::cout << type_name(modified) << std::endl;

	std::cout << "\"" << str << "\" \"" << modified
		<< "\" = " << str.length() << " / " << BOOST_PP_LIMIT_REPEAT << std::endl;

	auto mixed = long_string + S(" Or can it?");
	std::cout << type_name(mixed) << std::endl;

	std::cout << '"' << mixed << '"' << std::endl;

	return 0;
}


#include <benchmark/benchmark.h>

#include <Eigen/Dense>
#include <iostream>

#include "utils.h"

static void BM_NNWPriorPred(benchmark::State& state) {
  int dim = state.range(0);
  auto hierarchy = get_multivariate_nnw_hierarchy(dim);
  Eigen::VectorXd x = Eigen::VectorXd::Zero(dim);
  for (auto _ : state) {
    hierarchy->prior_pred_lpdf(x);
  }
}

static void BM_NNWSampleFullCond(benchmark::State& state) {
  int dim = state.range(0);
  auto hierarchy = get_multivariate_nnw_hierarchy(dim);
  Eigen::MatrixXd data = Eigen::MatrixXd::Random(10, dim);
  for (auto _ : state) {
    hierarchy->initialize();
    for (int i = 0; i < 10; i++) {
      hierarchy->add_datum(i, data.row(i));
    }
    hierarchy->sample_full_cond();
  }
}

static void BM_NNWConditionalPred(benchmark::State& state) {
  int dim = state.range(0);
  auto hierarchy = get_multivariate_nnw_hierarchy(dim);
  Eigen::MatrixXd data = Eigen::MatrixXd::Random(10, dim);
  for (int i = 0; i < 10; i++) {
    hierarchy->add_datum(i, data.row(i));
  }
  Eigen::VectorXd x = Eigen::VectorXd::Zero(dim);

  for (auto _ : state) {
    std::dynamic_pointer_cast<NNWHierarchy>(hierarchy)
        ->save_posterior_hypers();
    hierarchy->conditional_pred_lpdf(x);
  }
}

BENCHMARK(BM_NNWPriorPred)->RangeMultiplier(2)->Range(2, 2 << 5);
BENCHMARK(BM_NNWSampleFullCond)->RangeMultiplier(2)->Range(2, 2 << 5);
BENCHMARK(BM_NNWConditionalPred)->RangeMultiplier(2)->Range(2, 2 << 5);


/*!
 * @file
 * This file defines unit tests for `boost::mpl::topological_sort`.
 */

#include <boost/mpl/topological_sort.hpp>
#include <boost/mpl/any_of.hpp>
#include <boost/mpl/equal.hpp>
#include <boost/mpl/placeholders.hpp>
#include <boost/mpl/quote.hpp>
#include <boost/mpl/set.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/type_traits/is_same.hpp>

using namespace boost::mpl;

template <typename Sequence, typename Element>
struct contains_equal
    : any_of<Sequence, equal<Element, _1, quote2<boost::is_same>>>
{ };

template <typename ...AdjacentVertices>
struct vertex {
    using open_neighborhood = typename vector<AdjacentVertices...>::type;
};

template <typename ...Vertices>
struct graph {
    using vertices = typename set<Vertices...>::type;
};

namespace test_trivial {
    static_assert(equal<
        vector<>,
        topological_sort<graph<>>::type
    >::value, "");

    static_assert(equal<
        vector<vertex<>>,
        topological_sort<graph<vertex<>>>::type
    >::value, "");
}

namespace test_linear {
    struct v0 : vertex<> { };
    struct v1 : vertex<v0> { };
    struct v2 : vertex<v1> { };

    static_assert(equal<
        vector<v0, v1, v2>,
        topological_sort<graph<v0, v1, v2>>::type
    >::value, "");
}

namespace test_multiple_valid_orderings {
    struct v0 : vertex<> { };
    struct v1 : vertex<v0> { };
    struct v2 : vertex<v0> { };
    struct v3 : vertex<v1, v2> { };

    static_assert(contains_equal<
        vector<
            vector<v0, v1, v2, v3>,
            vector<v0, v2, v1, v3>
        >,
        topological_sort<graph<v0, v1, v2, v3>>::type
    >::value, "");
}

int main() { }


//=======================================================================
// Copyright 2001 Jeremy G. Siek, Andrew Lumsdaine, Lie-Quan Lee,
//
// This file is part of the Boost Graph Library
//
// You should have received a copy of the License Agreement for the
// Boost Graph Library along with the software; see the file LICENSE.
// If not, contact Office of Research, Indiana University,
// Bloomington, IN 47405.
//
// Permission to modify the code and to distribute the code is
// granted, provided the text of this NOTICE is retained, a notice if
// the code was modified is included with the above COPYRIGHT NOTICE
// and with the COPYRIGHT NOTICE in the LICENSE file, and that the
// LICENSE file is distributed with the modified code.
//
// LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.
// By way of example, but not limitation, Licensor MAKES NO
// REPRESENTATIONS OR WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY
// PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE COMPONENTS
// OR DOCUMENTATION WILL NOT INFRINGE ANY PATENTS, COPYRIGHTS, TRADEMARKS
// OR OTHER RIGHTS.
//=======================================================================
#include <boost/config.hpp>
#include <vector>
#include <list>
#include <boost/graph/biconnected_components.hpp>
#include <boost/graph/adjacency_list.hpp>

namespace boost
{
  struct edge_component_t
  {
    enum
    { num = 555 };
    typedef edge_property_tag kind;
  }
  edge_component;
}

int
main()
{
  using namespace boost;
  typedef adjacency_list < vecS, vecS, undirectedS,
    no_property, property < edge_component_t, std::size_t > >graph_t;
  typedef graph_traits < graph_t >::vertex_descriptor vertex_t;
  graph_t g(9);
  add_edge(0, 5, g);
  add_edge(0, 1, g);
  add_edge(0, 6, g);
  add_edge(1, 2, g);
  add_edge(1, 3, g);
  add_edge(1, 4, g);
  add_edge(2, 3, g);
  add_edge(4, 5, g);
  add_edge(6, 8, g);
  add_edge(6, 7, g);
  add_edge(7, 8, g);

  std::size_t c = 0;
  std::vector < std::size_t > discover_time(num_vertices(g));
  std::vector < vertex_t > lowpt(num_vertices(g));
  property_map < graph_t, edge_component_t >::type
    component = get(edge_component, g);
  biconnected_components(0, 8, g, component, c, &discover_time[0], &lowpt[0]);

  std::cout << "graph A {\n" << "  node[shape=\"circle\"]\n";

  graph_traits < graph_t >::edge_iterator ei, ei_end;
  for (tie(ei, ei_end) = edges(g); ei != ei_end; ++ei)
    std::cout << source(*ei, g) << " -- " << target(*ei, g)
      << "[label=\"" << component[*ei] << "\"]\n";
  std::cout << "}\n";

  return 0;
}


/**
* \file importance_sampling.cpp
* \brief main file for importance sampling method to infere the age of edges
in a network.
* \author Guillaume St-Onge
* \version 1.0
* \date 08/11/2017
*/

#include "DynamicNetwork.hpp"
#include "auxiliaryFunctions.hpp"
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <boost/random/uniform_01.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/discrete_distribution.hpp>
#include <chrono>

using namespace std;
using namespace DynNet;

typedef boost::random::uniform_01<> uniform_01;
typedef boost::random::uniform_int_distribution<> uniform_int;

const unsigned int CALIBSIZE = 100;
int main(int argc, char const *argv[])
{
	//Parameters
	string path = argv[1];
	double gamma = stod(argv[2]);
	double b = stod(argv[3]);
	unsigned int sampleSize = atoi(argv[4]);
	double seedExponent = stod(argv[5]);
	unsigned int seed;
	if (argc == 6)
	{
		seed = (unsigned int) std::chrono::high_resolution_clock::now().time_since_epoch().count();
	}
	else
	{
		seed = atoi(argv[6]);
	}

	unsigned int calibSize;
	if (sampleSize > CALIBSIZE)
	{
		calibSize = CALIBSIZE;
	}
	else
	{
		calibSize = sampleSize;
	}
	pair<double,double> param(gamma,b);

	//Load edgeList
	vector<edge> edgeList = input_edgeList(path);
	size_t M = edgeList.size();

	//Create unvarying maps associated to the observed network
	unordered_map<node,set<node> > neighborMap;
	unordered_map<node, unsigned int> degreeMap;
	edgeIntMap multiplicityMap;
	for (int i = 0; i < edgeList.size(); ++i)
	{
		//edges are assume to be in ascending order
		neighborMap[edgeList[i].first].insert(edgeList[i].second);
		neighborMap[edgeList[i].second].insert(edgeList[i].first);
		if (multiplicityMap.find(edgeList[i]) ==
			multiplicityMap.end())
		{
			multiplicityMap[edgeList[i]] = 1;
		}
		else
		{
			multiplicityMap[edgeList[i]] += 1;
		}
		if (degreeMap.find(edgeList[i].first) != degreeMap.end())
		{
			degreeMap[edgeList[i].first] += 1;
		}
		else
		{
			degreeMap[edgeList[i].first] = 1;
		}
		if (degreeMap.find(edgeList[i].second) != degreeMap.end())
		{
			degreeMap[edgeList[i].second] += 1;
		}
		else
		{
			degreeMap[edgeList[i].second] = 1;
		}
	}
	size_t N = degreeMap.size();

	//Initialize mean marginal estimation
	unordered_map< edge, vector<double>,
		boost::hash<edge> > meanMarginalMap;
	for (auto iter = multiplicityMap.begin(); iter != multiplicityMap.end();
		++iter)
	{
		vector<double> emptyVector(iter->second, 0.);
		meanMarginalMap[iter->first] = emptyVector;
	}
	double effectiveWeightSum = 0.;

	/*=======================================
		Calibration of the mean logweight
	=======================================*/

	vector<DynamicNetwork> calibSample;

	//Initialize random number generator and seed distribution
	RNGType gen(seed);
	vector<double> seedWeightVector;
	for (auto iter = edgeList.begin(); iter != edgeList.end() ; ++iter)
	{
		if (iter->first == iter->second)
		{
			seedWeightVector.push_back(0.);
		}
		else
		{
			seedWeightVector.push_back(pow(degreeMap[iter->first]
				*degreeMap[iter->second],seedExponent));
		}
	}
	boost::random::discrete_distribution<int> seedDist(seedWeightVector.begin(),
		seedWeightVector.end());

	//Get calibrating sample
	for (int n = 0; n < calibSize; ++n)
	{
		//Initialize dynamic network
		DynamicNetwork net(neighborMap, multiplicityMap);

		//choose a first random edge
		int seedIndex = seedDist(gen);
		edge seedEdge = edgeList[seedIndex];
		double seedInvWeight = 1/(seedWeightVector[seedIndex]);
		net.init(seedEdge, seedInvWeight);
		//init degree norm
		double degreeNormalization = 2.; //initial loop are impossible

		//Reconstruct the network
		while (net.get_reachableEdgeSet().size() > 0)
		{
			add_edge(net, param, degreeNormalization, gen);
		}
		calibSample.push_back(net);
	}

	//Determine mean logweight from calibration
	double meanLogweight = 0.;
	for (int n = 0; n < calibSize; ++n)
	{
		meanLogweight += calibSample[n].get_logweight();
	}
	meanLogweight /= calibSize;

	//Estimate the mean marginal from the sample
	for (int n = 0; n < calibSize; ++n)
	{
		update_meanMarginal(calibSample[n], meanMarginalMap, meanLogweight,
			effectiveWeightSum);
	}
	calibSample.clear();

	/*=======================================
	       Refining the mean marginal
	=======================================*/

	for (int n = 0; n < sampleSize-calibSize; ++n)
	{
		//Initialize dynamic network
		DynamicNetwork net(neighborMap, multiplicityMap);

		//choose a first random edge
		int seedIndex = seedDist(gen);
		edge seedEdge = edgeList[seedIndex];
		double seedInvWeight = 1/(seedWeightVector[seedIndex]);
		net.init(seedEdge, seedInvWeight);
		//init degree norm
		double degreeNormalization = 2.; //initial loop are impossible

		//Reconstruct the network
		while (net.get_reachableEdgeSet().size() > 0)
		{
			add_edge(net, param, degreeNormalization, gen);
		}

		update_meanMarginal(net, meanMarginalMap, meanLogweight,
			effectiveWeightSum);
	}

	/*=======================================
	       		Output the data
	=======================================*/
	for (auto iter = meanMarginalMap.begin(); iter != meanMarginalMap.end();
		++iter)
	{
		double average = 0;
		for (int i = 0; i < (iter->second).size(); ++i)
		{
			average += (iter->second)[i]/effectiveWeightSum;
		}
		for (int i = 0; i < (iter->second).size(); ++i)
		{
			cout << (iter->first).first << " " << (iter->first).second << " " << i << " ";
			cout << average / double((iter->second).size()) << endl;
		}
	}

	return 0;
}


/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2015 Scientific Computing and Imaging Institute,
   University of Utah.

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

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

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

#include <gtest/gtest.h>
#include <boost/test/floating_point_comparison.hpp>
#include <Core/Math/MiscMath.h>

using namespace SCIRun;

TEST(FloatComparisonTest, BoostCheck)
{
  using namespace boost::test_tools;
  close_at_tolerance<double> comp(percent_tolerance(1e-5));
  EXPECT_TRUE(comp(1, 1));
  EXPECT_FALSE(comp(1.0/3, 0.33333));
  EXPECT_TRUE(comp(1.0/3, 0.333333333));
}

TEST(InfinityTests, IsFiniteTest)
{
  EXPECT_FALSE(IsFinite(std::numeric_limits<double>::infinity()));
  EXPECT_FALSE(IsFinite(-std::numeric_limits<double>::infinity()));
  EXPECT_FALSE(IsFinite(std::numeric_limits<double>::quiet_NaN()));
  EXPECT_FALSE(IsFinite(std::numeric_limits<double>::signaling_NaN()));
  EXPECT_TRUE(IsFinite(0.3));
}

TEST(InfinityTests, IsInfiniteTest)
{
  EXPECT_TRUE(IsInfinite(std::numeric_limits<double>::infinity()));
  EXPECT_TRUE(IsInfinite(-std::numeric_limits<double>::infinity()));
  EXPECT_FALSE(IsInfinite(std::numeric_limits<double>::quiet_NaN()));
  EXPECT_FALSE(IsInfinite(std::numeric_limits<double>::signaling_NaN()));
  EXPECT_FALSE(IsInfinite(0.3));
}

TEST(NanTests, IsNanTest)
{
  EXPECT_FALSE(IsNan(std::numeric_limits<double>::infinity()));
  EXPECT_FALSE(IsNan(-std::numeric_limits<double>::infinity()));
  EXPECT_TRUE(IsNan(std::numeric_limits<double>::quiet_NaN()));
  EXPECT_TRUE(IsNan(std::numeric_limits<double>::signaling_NaN()));
  EXPECT_FALSE(IsNan(0.3));
}


/*
 *  testRatFuncF.cpp
 *  Trogdor6
 *
 *  Created by Paul Hansen on 4/7/10.
 *  Copyright 2010 Stanford University. All rights reserved.
 *
 */

#define BOOST_TEST_MAIN
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE Test3D

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

#include <iostream>
#include <cstdlib>
#include "RationalFunction.h"

typedef RationalFunction<float> RatFuncF;

using namespace std;

BOOST_AUTO_TEST_CASE( TestConstructors )
{
    RatFuncF zed;
    BOOST_CHECK_EQUAL(zed.value(2342.13), 0.0);
    BOOST_CHECK_EQUAL(zed.order(), 0);
    BOOST_CHECK_EQUAL(zed.numerator()[0], 0.0);
    BOOST_CHECK_EQUAL(zed.denominator()[0], 1.0);
    BOOST_CHECK(zed == 0.0f);

    RatFuncF uno(1.0);
    BOOST_CHECK_EQUAL(uno.value(5.2), 1.0);
    BOOST_CHECK_EQUAL(uno.order(), 0);
    BOOST_CHECK_EQUAL(uno.numerator()[0], 1.0);
    BOOST_CHECK_EQUAL(uno.denominator()[0], 1.0);
    BOOST_CHECK(uno == 1.0f);

    RatFuncF secondOne(uno);
    RatFuncF secondZero(zed);
    BOOST_CHECK(secondOne == uno);
    BOOST_CHECK(secondZero == zed);
    BOOST_CHECK_EQUAL(secondOne(3.1), 1.0);
    BOOST_CHECK_EQUAL(secondZero(-12), 0.0);
    BOOST_CHECK_EQUAL(secondOne.order(), uno.order());
    BOOST_CHECK_EQUAL(secondZero.order(), zed.order());

    RatFuncF thirdOne, thirdZero;
    thirdOne = uno;
    thirdZero = zed;
    BOOST_CHECK(thirdOne == uno);
    BOOST_CHECK(thirdZero == zed);
    BOOST_CHECK(thirdOne != zed);
    BOOST_CHECK(thirdZero != uno);

    RatFuncF::Poly numer, denom;
    numer.coefficient(0, 1.0);
    numer.coefficient(1, 2.0);
    numer.coefficient(2, 3.0);
    denom.coefficient(0, 1.0);
    denom.coefficient(1, 2.0);

    RatFuncF r(numer, denom);
    BOOST_CHECK_EQUAL(r.order(), r.numerator().order());
    RatFuncF rInverse(denom, numer);
    BOOST_CHECK_EQUAL(rInverse.order(), rInverse.denominator().order());
    BOOST_CHECK(r.numerator() == numer);
    BOOST_CHECK(r.denominator() == denom);
    BOOST_CHECK(r.numerator() == rInverse.denominator());
    BOOST_CHECK(r.denominator() == rInverse.numerator());
}

BOOST_AUTO_TEST_CASE( Value )
{
    RatFuncF three(3.0, 1.0);
    RatFuncF three2(6.0, 2.0);
    BOOST_CHECK_EQUAL(three(0), 3.0);
    BOOST_CHECK_EQUAL(three2(0), 3.0);

    RatFuncF::Poly numer, denom;
    const int ORDER = 10;
    for (int nn = 0; nn <= ORDER; nn++)
    {
        numer.coefficient(nn, drand48());
        denom.coefficient(nn, drand48());
    }
    RatFuncF r(numer, denom);
    RatFuncF s(denom, numer);

    BOOST_CHECK_EQUAL(r(5.0), numer(5.0)/denom(5.0));
    BOOST_CHECK_CLOSE(s(5.0), 1.0/r(5.0), 1e-5);
}

BOOST_AUTO_TEST_CASE( TestOrder )
{
    RatFuncF::Poly o0(0), o1(0,1), o2(0,1,2), o3(0,1,2,3);

    RatFuncF z(o0, o1); // this is 0/x, which will be reset to 0/1.
    BOOST_CHECK_EQUAL(z.order(), 0);
    RatFuncF r(o1, o1);
    BOOST_CHECK_EQUAL(r.order(), 1);
    RatFuncF s(o3, o1);
    BOOST_CHECK_EQUAL(s.order(), 3);
    RatFuncF t(o2, o1);
    BOOST_CHECK_EQUAL(t.order(), 2);
}

BOOST_AUTO_TEST_CASE( AccessCoefficients )
{
    RatFuncF::Poly f;
    for (int nn = 0; nn <= 5; nn++)
        f.coefficient(nn, -1.0 * nn);

    BOOST_CHECK_EQUAL(f[0], 0.0);
    BOOST_CHECK_EQUAL(f[1], -1.0);
    BOOST_CHECK_EQUAL(f[2], -2.0);
    BOOST_CHECK_EQUAL(f[3], f.coefficient(3));
    BOOST_CHECK_EQUAL(f[4], f.coefficient(4));
    BOOST_CHECK_EQUAL(f[5], f.coefficient(5));
}

static RatFuncF::Poly polyOfOrderUpTo(unsigned int n)
{
    RatFuncF::Poly p;
    int order = lrand48()%n;
    for (int mm = 0; mm <= order; mm++)
        p.coefficient(mm, drand48());

    return p;
}

static RatFuncF ratFuncOfOrderUpTo(unsigned int n)
{
    return RatFuncF(polyOfOrderUpTo(n), polyOfOrderUpTo(n));
}

BOOST_AUTO_TEST_CASE( Arithmetic )
{
    int numTests = 50;
    for (int mm = 0; mm < numTests; mm++)
    {
        RatFuncF r(ratFuncOfOrderUpTo(10));
        RatFuncF s(ratFuncOfOrderUpTo(10));

        double x = drand48();

        BOOST_CHECK_CLOSE( (r+s)(x), r(x)+s(x), 1.0 ); // test to 1% tolerance
        BOOST_CHECK_CLOSE( (r-s)(x), r(x)-s(x), 1.0 );
        BOOST_CHECK_CLOSE( (r*s)(x), r(x)*s(x), 1.0 );
        BOOST_CHECK_CLOSE( (r/s)(x), r(x)/s(x), 1.0 );

        BOOST_CHECK_CLOSE( (r*s)(x), (s*r)(x), 1.0 );
        BOOST_CHECK_CLOSE( (r+s)(x), (s+r)(x), 1.0 );
        BOOST_CHECK_CLOSE( (r/s)(x), 1.0/(s/r)(x), 1.0 );
        BOOST_CHECK_CLOSE( (r-s)(x), -(s-r)(x), 1.0 );
    }
}

BOOST_AUTO_TEST_CASE( InPlaceAdd )
{
    RatFuncF::Poly numer;
    numer.coefficient(0, 1.0);
    numer.coefficient(1, 2.0);
    numer.coefficient(2, 3.0);

    RatFuncF::Poly denom;
    denom.coefficient(0, 2.0);
    denom.coefficient(1, -1.0);

    RatFuncF r(numer, denom);
    RatFuncF s(denom, numer);

    RatFuncF r2(r), r3(r), r4(r), r5(r);
    BOOST_CHECK(r2 == r);
    r2 += s;
    r3 -= s;
    r4 *= s;
    r5 /= s;

    BOOST_CHECK(r2 == r + s);
    BOOST_CHECK(r3 == r - s);
    BOOST_CHECK(r4 == r * s);
    BOOST_CHECK(r5 == r / s);
}

BOOST_AUTO_TEST_CASE( InPlaceArithmetic )
{
    RatFuncF r(ratFuncOfOrderUpTo(10)),
        s(ratFuncOfOrderUpTo(10));

    RatFuncF r2(r), r3(r), r4(r), r5(r);

    r2 += s;
    r3 -= s;
    r4 *= s;
    r5 /= s;

    int numTests = 50;
    for (int mm = 0; mm < numTests; mm++)
    {
        double x = 50 + 100*drand48();

        BOOST_CHECK_CLOSE(r2(x), (r+s)(x), 1.0);
        BOOST_CHECK_CLOSE(r3(x), (r-s)(x), 1.0);
        BOOST_CHECK_CLOSE(r4(x), (r*s)(x), 1.0);
        BOOST_CHECK_CLOSE(r5(x), (r/s)(x), 1.0);
    }
}

BOOST_AUTO_TEST_CASE( Print )
{
    BOOST_CHECK(true);

    RatFuncF defaultRF;
    BOOST_TEST_MESSAGE("Default RatFuncF " << defaultRF);
}