#include "Polynomial.h"
#include <Eigen/Core>
#include <random>
#include "testUtil.h"
#include <iostream>

using namespace Eigen;
using namespace std;

template <typename CoefficientType>
void testIntegralAndDerivative() {
  VectorXd coefficients = VectorXd::Random(5);
  Polynomial<CoefficientType> poly(coefficients);
  Polynomial<CoefficientType> third_derivative = poly.derivative(3);
  Polynomial<CoefficientType> third_derivative_check = poly.derivative().derivative().derivative();
  valuecheck(third_derivative.getCoefficients(), third_derivative_check.getCoefficients(), 1e-14);

  Polynomial<CoefficientType> tenth_derivative = poly.derivative(10);
  valuecheck(tenth_derivative.getCoefficients(), VectorXd::Zero(1), 1e-14);

  Polynomial<CoefficientType> integral = poly.integral(0.0);
  Polynomial<CoefficientType> poly_back = integral.derivative();
  valuecheck(poly_back.getCoefficients(), poly.getCoefficients(), 1e-14);
}

template <typename CoefficientType>
void testOperators() {
  int max_num_coefficients = 6;
  int num_tests = 10;
  default_random_engine generator;
  std::uniform_int_distribution<> int_distribution(1, max_num_coefficients);
  uniform_real_distribution<double> uniform;

  for (int i = 0; i < num_tests; ++i) {
    VectorXd coeff1 = VectorXd::Random(int_distribution(generator));
    Polynomial<CoefficientType> poly1(coeff1);

    VectorXd coeff2 = VectorXd::Random(int_distribution(generator));
    Polynomial<CoefficientType> poly2(coeff2);

    double scalar = uniform(generator);

    Polynomial<CoefficientType> sum = poly1 + poly2;
    Polynomial<CoefficientType> difference = poly2 - poly1;
    Polynomial<CoefficientType> product = poly1 * poly2;
    Polynomial<CoefficientType> poly1_plus_scalar = poly1 + scalar;
    Polynomial<CoefficientType> poly1_minus_scalar = poly1 - scalar;
    Polynomial<CoefficientType> poly1_scaled = poly1 * scalar;
    Polynomial<CoefficientType> poly1_div = poly1 / scalar;

    double t = uniform(generator);
    valuecheck(sum.value(t), poly1.value(t) + poly2.value(t), 1e-8);
    valuecheck(difference.value(t), poly2.value(t) - poly1.value(t), 1e-8);
    valuecheck(product.value(t), poly1.value(t) * poly2.value(t), 1e-8);
    valuecheck(poly1_plus_scalar.value(t), poly1.value(t) + scalar, 1e-8);
    valuecheck(poly1_minus_scalar.value(t), poly1.value(t) - scalar, 1e-8);
    valuecheck(poly1_scaled.value(t), poly1.value(t) * scalar, 1e-8);
    valuecheck(poly1_div.value(t), poly1.value(t) / scalar, 1e-8);
  }
}

template <typename CoefficientType>
void testRoots() {
  int max_num_coefficients = 6;
  int num_tests = 50;
  default_random_engine generator;
  std::uniform_int_distribution<> int_distribution(1, max_num_coefficients);

  for (int i = 0; i < num_tests; ++i) {
    VectorXd coeffs = VectorXd::Random(int_distribution(generator));
    Polynomial<CoefficientType> poly(coeffs);
    auto roots = poly.roots();
    valuecheck(roots.rows(), poly.getDegree());
    for (int i = 0; i < roots.size(); i++) {
      auto value = poly.value(roots[i]);
      valuecheck(std::abs(value), 0.0, 1e-8);
    }
  }
}

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

  testIntegralAndDerivative<double>();
  testOperators<double>();
  testRoots<double>();
  cout << "test passed" << endl;
  return 0;
}


#include "advent.hpp"

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <scn/scn.h>
#include <tuple>

#include <Eigen/Core>

using std::pair;
using std::ifstream;
using std::string;
using std::vector;
using Eigen::Array;

auto day13(int argc, char** argv) -> int
{
    if (argc < 2) {
        fmt::print("Error: no input.");
        return 1;
    }

    ifstream infile(argv[1]); // NOLINT
    string line;

    i64 x{0};
    i64 y{0};
    vector<pair<i64, i64>> points;
    while (std::getline(infile, line)) { // NOLINT
        if (line.empty()) { break; }
        scn::scan(std::string(line), "{},{}", y, x);
        points.emplace_back(x, y);
    }
    for (auto [a, b] : points) {
        x = std::max(x, a);
        y = std::max(y, b);
    }
    Array<i64, -1, -1> paper = decltype(paper)::Zero(x+1, y+1);
    vector<pair<char, i64>> folds;

    vector<string> tokens;
    while(std::getline(infile, line)) { // NOLINT
        tokens.clear();
        util::tokenize(line, '=', tokens);
        folds.emplace_back(tokens[0].back(), std::stoi(tokens[1]));
    }

    for (auto [a, b] : points) {
        paper(a, b) = 1;
    }

    using Eigen::all;
    using Eigen::seqN;

    x = paper.rows();
    y = paper.cols();

    i64 part1{-1};
    for (auto [c, v] : folds) {
        auto b = paper.block(0, 0, x, y);
        if (c == 'x') {
            b(all, seqN(0, v)) += b(all, seqN(v+1, b.cols()-v-1)).rowwise().reverse();
            y = v;
        }
        if (c == 'y') {
            b(seqN(0, v), all) += b(seqN(v+1, b.rows()-v-1), all).colwise().reverse();
            x = v;
        }
        if (part1 == -1) { part1 = (paper.block(0, 0, x, y) > 0).count(); }
    }
    auto b = paper.block(0, 0, x, y);
    b = (b > 1).select(1, b);
    for (i64 i = 0; i < x; ++i) { // NOLINT
        for (i64 j = 0; j < y; ++j) { // NOLINT
            auto c = b(i, j) == 1 ? '#' : ' ';
            fmt::print("{} ", c);
        }
        fmt::print("\n");
    }
    fmt::print("part 1: {}\n", part1);

    return 0;
}


#include "mantella_bits/samplesAnalysis.hpp"

// C++ standard library
#include <algorithm>
#include <cassert>
#include <cmath>
#include <memory>
#include <stdexcept>
#include <utility>

// Armadillo
#include <armadillo>

// Mantella
#include "mantella_bits/assert.hpp"
#include "mantella_bits/combinatorics.hpp"
#include "mantella_bits/config.hpp"
#include "mantella_bits/optimisationProblem.hpp"
#include "mantella_bits/probability.hpp"

namespace mant {
  double fitnessDistanceCorrelation(
      const std::unordered_map<arma::vec, double, Hash, IsEqual>& samples) {
    assert(samples.size() > 1 && "fitnessDistanceCorrelation: The number of samples must be greater than 1.");
    assert(isDimensionallyConsistent(samples) && "fitnessDistanceCorrelation: The samples must be dimensionally consistent");

    // Converts the set of samples into a matrix of parameters (each row is a dimension and each column a parameter) and a row vector of objective values, such that we can use Armadillo C++ to manipulate them.
    arma::mat parameters(samples.cbegin()->first.n_elem, samples.size());
    arma::rowvec objectiveValues(parameters.n_cols);

    arma::uword n = 0;
    for (const auto& sample : samples) {
      parameters.col(n) = sample.first;
      objectiveValues(n) = sample.second;

      ++n;
    }

    // Determines one parameter having a minimal objective value within the set of samples.
    // The best parameter is then subtracted from all other parameters, so that we can later calculated the distance towards the best one.
    arma::uword bestParameterIndex = objectiveValues.index_min();
    parameters.each_col() -= parameters.col(bestParameterIndex);

    // Excludes the best/reference parameter from the correlation, as it will always perfectly correlate with itself and therefore bias the correlation coefficient.
    parameters.shed_col(bestParameterIndex);
    objectiveValues.shed_col(bestParameterIndex);

    // Uses `arma::sum(arma::square(...))` to emulate a column-wise vector norm operator that Armadillo C++ does not have (and this is not likely to change, as it interferes with matrix norms).
    return arma::as_scalar(arma::cor(arma::sqrt(arma::sum(arma::square(parameters))), objectiveValues));
  }

  double lipschitzContinuity(
      const std::unordered_map<arma::vec, double, Hash, IsEqual>& samples) {
    assert(samples.size() > 1 && "lipschitzContinuity: The number of samples must be greater than 1.");
    assert(isDimensionallyConsistent(samples) && "lipschitzContinuity: The samples must be dimensionally consistent");

    double lipschitzContinuity = 0.0;
    for (auto firstSample = samples.cbegin(); firstSample != samples.cend();) {
      for (auto secondSample = ++firstSample; secondSample != samples.cend(); ++secondSample) {
        lipschitzContinuity = std::max(lipschitzContinuity, std::abs(firstSample->second - secondSample->second) / arma::norm(firstSample->first - secondSample->first));
      }
    }

    return lipschitzContinuity;
  }

  std::vector<arma::uvec> additiveSeparability(
      OptimisationProblem& optimisationProblem,
      const arma::uword numberOfEvaluations,
      const double minimalConfidence) {
    assert(numberOfEvaluations > 0 && "additiveSeparability: The number of evaluations must be greater than 0.");
    assert(0.0 <= minimalConfidence && minimalConfidence <= 1.0 && "additiveSeparability: The minimal confidence must be within the interval (0, 1].");

    if (!isRepresentableAsFloatingPoint(numberOfEvaluations)) {
      throw std::range_error("additiveSeparability: The number of elements must be representable as a floating point.");
    }

    if (minimalConfidence <= 0) {
      std::vector<arma::uvec> partition;
      partition.reserve(optimisationProblem.numberOfDimensions_);

      for (arma::uword n = 0; n < optimisationProblem.numberOfDimensions_; ++n) {
        partition.push_back({n});
      }

      return partition;
    }

    /* The first of two steps to analyse the additive separability of a function is to estimate all two-set separations that fulfil the deviation and confidence requirements.
     * A function *f* is additive separable into two other function *g*, *h* if the following holds:
     *
     * f(x, y) - g(x) - h(y) = 0, for all x, y
     *
     * As it is practical impossible to simply guess two separations *g*, *h* of *f*, when we only got a caller to `.getObjectiveValue(...)` and no analytic form, we use a direct consequence from the equation above instead, that must also hold true for additive separable functions and uses only *f*:
     *
     * f(a, c) + f(b, d) - f(a, d) - f(b, c) = 0, for all a, b, c, d
     *
     */

    std::vector<std::pair<arma::uvec, arma::uvec>> partitionCandidates = twoSetsPartitions(optimisationProblem.numberOfDimensions_);

    arma::rowvec confidences(partitionCandidates.size(), arma::fill::zeros);
    for (arma::uword n = 0; n < partitionCandidates.size(); ++n) {
      const std::pair<arma::uvec, arma::uvec>& partitionCandidate = partitionCandidates.at(n);

      for (arma::uword k = 0; k < numberOfEvaluations; ++k) {
        const arma::vec& firstFirstParamter = uniformRandomNumbers(optimisationProblem.numberOfDimensions_);
        const arma::vec& secondSecondParameter = uniformRandomNumbers(optimisationProblem.numberOfDimensions_);

        arma::vec firstSecondParameter = firstFirstParamter;
        firstSecondParameter.elem(partitionCandidate.first) = secondSecondParameter.elem(partitionCandidate.first);
        arma::vec secondFirstParameter = secondSecondParameter;
        secondFirstParameter.elem(partitionCandidate.first) = firstFirstParamter.elem(partitionCandidate.first);

        // **Note:** The summation of not-a-number values results in a not-a-number value and comparing it with another value returns false, so everything will work out just fine.
        if (std::abs(optimisationProblem.getObjectiveValueOfNormalisedParameter(firstFirstParamter) + optimisationProblem.getObjectiveValueOfNormalisedParameter(secondSecondParameter) - optimisationProblem.getObjectiveValueOfNormalisedParameter(firstSecondParameter) - optimisationProblem.getObjectiveValueOfNormalisedParameter(secondFirstParameter)) < ::mant::machinePrecision) {
          ++confidences(n);
          if (confidences(n) / static_cast<double>(numberOfEvaluations) >= minimalConfidence) {
            // Proceeds with the next partition candidate, as we already reached the confidence threshold.
            break;
          }
        }
      }
    }

    const arma::uvec& acceptablePartitionCandidatesIndicies = arma::find(confidences / static_cast<double>(numberOfEvaluations) >= minimalConfidence);
    std::vector<std::pair<arma::uvec, arma::uvec>> acceptablePartitionCandidates;
    acceptablePartitionCandidates.reserve(acceptablePartitionCandidatesIndicies.n_elem);

    for (const auto acceptablePartitionCandidateIndex : acceptablePartitionCandidatesIndicies) {
      acceptablePartitionCandidates.push_back(partitionCandidates.at(acceptablePartitionCandidateIndex));
    }

    /* The last of the two steps is to calculate the partition with the maximal number of parts, from all acceptable two-set partitions.
     * If we now weaken our observation and assume that each accepted two-set partition holds true for **all** inputs, the partition with the maximal number of parts can be calculated by combining all intersections between one part and an other.
     *
     * For example, assume that we got 3 acceptable two-set partitions:
     *
     * - {{1, 2, 3, 4, 5}, {6}}
     * - {{1}, {2, 3, 4, 5, 6}}
     * - {{1, 2, 3}, {4, 5, 6}}
     *
     * We would then calculate the intersection between the first two partitions:
     *
     * {{1, 2, 3, 4, 5}, {6}} intersect {{1}, {2, 3, 4, 5, 6}} =
     *   {{1, 2, 3, 4, 5} intersect {1}}             union
     *   {{1, 2, 3, 4, 5} intersect {2, 3, 4, 5, 6}} union
     *   {{6} intersect {1}}                         union \
     *   {{6} intersect {2, 3, 4, 5, 6}}                   / skipped and directly replaced by {{6}}
     *   = {{1}, {2, 3, 4, 5}, {6}}
     *
     * And intersect the resulting partitions with the remaining one:
     *
     * {{1}, {2, 3, 4, 5}, {6}} intersect {{1, 2, 3}, {4, 5, 6}} =
     *   {{1} intersect {1, 2, 3}}          union \
     *   {{1} intersect {4, 5, 6}}          union / skipped and directly replaced by {{1}}
     *   {{2, 3, 4, 5} intersect {1, 2, 3}} union
     *   {{2, 3, 4, 5} intersect {4, 5, 6}} union
     *   {{6} intersect {1, 2, 3}}          union \
     *   {{6} intersect {4, 5, 6}}                / skipped and directly replaced by {{6}}
     *   = {{1}, {2, 3}, {4, 5}, {6}}
     *
     * And get {{1}, {2, 3}, {4, 5}, {6}} as partition with the maximal number of parts.
     */

    if (acceptablePartitionCandidates.size() > 1) {
      std::vector<arma::uvec> partition = {acceptablePartitionCandidates.at(0).first, acceptablePartitionCandidates.at(0).second};
      acceptablePartitionCandidates.erase(acceptablePartitionCandidates.cbegin());

      for (const auto& acceptablePartitionCandidate : acceptablePartitionCandidates) {
        std::vector<arma::uvec> nextPartition;
        for (const auto& part : partition) {
          if (part.n_elem == 1) {
            nextPartition.push_back(part);
          } else {
            // **Note:** `std::set_intersection` requires that all parts are ordered at this point.
            std::vector<arma::uword> intersection;
            std::set_intersection(part.begin(), part.end(), acceptablePartitionCandidate.first.begin(), acceptablePartitionCandidate.first.end(), intersection.begin());
            nextPartition.push_back(arma::uvec(intersection));
            intersection.clear();
            std::set_intersection(part.begin(), part.end(), acceptablePartitionCandidate.second.begin(), acceptablePartitionCandidate.second.end(), intersection.begin());
            nextPartition.push_back(arma::uvec(intersection));
          }
        }
        partition = nextPartition;

        // We are already finished, as there is no finer partition as having one part for each dimensions.
        if (partition.size() == optimisationProblem.numberOfDimensions_) {
          break;
        }
      }

      return partition;
    } else if (acceptablePartitionCandidates.size() == 1) {
      return {acceptablePartitionCandidates.at(0).first, acceptablePartitionCandidates.at(0).second};
    } else {
      return {arma::regspace<arma::uvec>(0, optimisationProblem.numberOfDimensions_ - 1)};
    }
  }

  std::vector<arma::uvec> additiveSeparability(
      OptimisationProblem& optimisationProblem,
      const arma::uword numberOfEvaluations) {
    return additiveSeparability(optimisationProblem, numberOfEvaluations, 1.0);
  }
}


//==================================================================================================
/*!
  @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_CONSTANT_SMALLESTPOSVAL_HPP_INCLUDED
#define BOOST_SIMD_CONSTANT_SMALLESTPOSVAL_HPP_INCLUDED

/*!
  @ingroup group-constant
  @defgroup constant-Smallestposval Smallestposval (function template)

  Generates the least non zero, non denormal, positive value.

  @headerref{<boost/simd/constant/smallestposval.hpp>}

  @par Description

  1.  @code
      template<typename T> T Smallestposval();
      @endcode

  2.  @code
      template<typename T> T Smallestposval( boost::simd::as_<T> const& target );
      @endcode

  Generates a value of type @c T that evaluates to the smallest positive, non-denormal value of
  type @c T.

  @par Parameters

  | Name                | Description                                                         |
  |--------------------:|:--------------------------------------------------------------------|
  | **target**          | a [placeholder](@ref type-as) value encapsulating the constant type |

  @par Return Value
  A value of type @c as_integer_t<T> that evaluates to

  | Type            | double                        | float         | Integral        |
  |:----------------|:------------------------------|---------------|-----------------|
  | **Values**      |   2.225073858507201e-308      | 1.1754944e-38 |  1              |

  @par Requirements
  - **T** models Value
**/

#include <boost/simd/constant/scalar/smallestposval.hpp>
#include <boost/simd/constant/simd/smallestposval.hpp>

#endif


// Copyright (C) 2017 Minhyuk Sung <mhsung@cs.stanford.edu>
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.
//

#include "LibiglMesh.h"

#include <Eigen/Geometry>
#include <utils/utils.h>

void LibiglMesh::normalize_mesh(MatrixXd& _V) {
  const auto bb_min = _V.colwise().minCoeff();
  const auto bb_max = _V.colwise().maxCoeff();
  const auto center = 0.5 * (bb_max + bb_min);
  const double bbox_diagonal = (bb_max - bb_min).norm();
  CHECK_GT(bbox_diagonal, 1.0E-6);

  // Move center to (0,0,0).
  _V = _V.rowwise() - center;

  // Scale to bounding box diagonal 1.
  _V /= bbox_diagonal;
}

void LibiglMesh::translate_mesh(MatrixXd& _V, const Vector3d& _t) {
  _V = _V.rowwise() + _t.transpose();
}

void LibiglMesh::transform_mesh(
    MatrixXd& _V, const Vector3d& _r, const Vector3d& _t) {
  const double kZeroTol = 1.0e-6;

  double angle = _r.norm();
  Vector3d axis;
  if (std::abs(angle) < kZeroTol) {
    angle = 0.0;
    axis = Vector3d::UnitX();
  } else {
    axis = _r.normalized();
  }
  AngleAxisd rotation(angle, axis);
  const Matrix3d R = rotation.toRotationMatrix();

  Affine3d T = Affine3d::Identity();
  T.prerotate(R);
  T.pretranslate(_t);

  const Eigen::Matrix<double, Dynamic, 3>& V_temp = _V;
  _V = (T * V_temp.transpose()).transpose();
}

void LibiglMesh::inverse_transform_mesh(
    MatrixXd& _V, const Vector3d& _r, const Vector3d& _t) {
  const double kZeroTol = 1.0e-6;

  double angle = _r.norm();
  Vector3d axis;
  if (std::abs(angle) < kZeroTol) {
    angle = 0.0;
    axis = Vector3d::UnitX();
  } else {
    axis = _r.normalized();
  }
  AngleAxisd rotation(angle, axis);
  const Matrix3d R = rotation.toRotationMatrix();

  Affine3d T_inv = Affine3d::Identity();
  T_inv.prerotate(R);
  T_inv.pretranslate(_t);
  const Affine3d T = T_inv.inverse();

  const Eigen::Matrix<double, Dynamic, 3>& V_temp = _V;
  _V = (T * V_temp.transpose()).transpose();
}

/*
void LibiglMesh::transform_mesh(const std::string& _filename) {
  Matrix4d mat;
  if (!Utils::read_eigen_matrix_from_file(_filename, &mat)) {
    return;
  }

  Affine3d T(mat);
  Matrix<double, Dynamic, 3> V_copy = V_;
  V_ = (T * V_copy.transpose()).transpose();

  update_bounding_box();
  if (renderer_ == nullptr) {
    LOG(WARNING) << "Renderer is not set";
  } else {
    renderer_->set_mesh(V_, F_);
    renderer_->set_scene_pos(center_.cast<float>(), (float)radius_);
  }
}
*/


//==================================================================================================
/*!
  @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_MEANOF_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_MEANOF_HPP_INCLUDED

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

 /*!

    @ingroup group-arithmetic
    Function object implementing meanof capabilities

    Computes the mean of its parameter avoiding overflow.

    @par semantic:
    For any given value @c x and @c y of type @c T:

    @code
    T r = meanof(x, y);
    @endcode

    is similar to:

    @code
    T r = (x+y)/2;
    @endcode

    @par Note:
    Take care that for integers the value returned can differ by one unit
    from \c ceil((a+b)/2.0) or \c floor((a+b)/2.0), but is always one of
    the two values.

    @see average

  **/
  Value meanof(Value const& x, Value const& y);
} }
#endif

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

#endif


#include <algorithm>
#include <functional>
#include <limits>
#include <vector>

#include <Eigen/Core>
#include <Eigen/Eigenvalues>
#include <glog/logging.h>

#include <product-quantization/learn-product-quantization.h>

namespace product_quantization {
void ComputePCARotation(
    const Eigen::MatrixXf& data_points, Eigen::MatrixXf* rotation_matrix,
    std::vector<float>* variances) {
  CHECK_NOTNULL(rotation_matrix);
  CHECK_NOTNULL(variances);
  CHECK_GE(data_points.cols(), data_points.rows());

  int num_dimensions = data_points.rows();
  int num_data_points = data_points.cols();
  rotation_matrix->resize(num_dimensions, num_dimensions);
  variances->resize(num_dimensions);

  Eigen::VectorXf mean = data_points.rowwise().mean();

  Eigen::MatrixXf covariance_matrix;
  covariance_matrix.setZero(num_dimensions, num_dimensions);
  for (int i = 0; i < num_data_points; ++i) {
    Eigen::VectorXf p = data_points.col(i) - mean;
    covariance_matrix += p * p.transpose();
  }
  covariance_matrix /= static_cast<float>(num_data_points - 1);

  Eigen::EigenSolver<Eigen::MatrixXf> eigen_solver(covariance_matrix);
  Eigen::VectorXf eigenvalues = eigen_solver.eigenvalues().real();
  *rotation_matrix = eigen_solver.eigenvectors().real().transpose();

  for (int i = 0; i < num_dimensions; ++i) {
    CHECK_GE(eigenvalues(i), 0.0);
    (*variances)[i] = eigenvalues(i);
  }
}

void EigenvalueAllocation(
    const Eigen::MatrixXf& rotation_matrix, const std::vector<float>& variances,
    int num_components, Eigen::MatrixXf* permutated_rotation_matrix) {
  CHECK_EQ(rotation_matrix.cols(), rotation_matrix.rows());
  CHECK_EQ(static_cast<unsigned int>(rotation_matrix.cols()), variances.size());
  CHECK_EQ(rotation_matrix.cols() % num_components, 0);
  CHECK_NOTNULL(permutated_rotation_matrix);

  int num_dimensions = rotation_matrix.cols();
  permutated_rotation_matrix->setIdentity(num_dimensions, num_dimensions);
  // Sorts the rows in decreasing order of variance.
  std::vector<std::pair<float, int> > variance_index_pairs(num_dimensions);
  for (int i = 0; i < num_dimensions; ++i) {
    variance_index_pairs[i].first = variances[i];
    variance_index_pairs[i].second = i;
  }
  std::sort(
      variance_index_pairs.begin(), variance_index_pairs.end(),
      std::greater<std::pair<float, int> >());

  // Performs EigenvalueAllocation by balancing the variances in a greedy
  // fashion: Given the sorted variances, the algorithm iteratively selects the
  // component with the minimum product of variances for which we have not
  // yet selected enough rows.
  std::vector<float> variance_product_per_component(num_components, -1.0f);
  std::vector<int> num_selected_dimensions_per_component(num_components, 0);
  int max_num_dimensions_per_component = num_dimensions / num_components;
  for (int i = 0; i < num_dimensions; ++i) {
    // Finds the component with the minimum product of variances.
    int selected_component = -1;
    float min_product_variance = std::numeric_limits<float>::max();
    for (int j = 0; j < num_components; ++j) {
      if (num_selected_dimensions_per_component[j] ==
          max_num_dimensions_per_component) {
        continue;
      }

      if (variance_product_per_component[j] < min_product_variance) {
        min_product_variance = variance_product_per_component[j];
        selected_component = j;
      }
    }
    CHECK_GE(selected_component, 0);
    if (min_product_variance == -1.0f) {
      variance_product_per_component[selected_component] =
          variance_index_pairs[i].first;
    } else {
      variance_product_per_component[selected_component] *=
          variance_index_pairs[i].first;
    }
    permutated_rotation_matrix->row(
        selected_component * max_num_dimensions_per_component +
        num_selected_dimensions_per_component[selected_component]) =
        rotation_matrix.row(variance_index_pairs[i].second);
    ++num_selected_dimensions_per_component[selected_component];
  }
}
}  // namespace product_quantization


/*
 * =====================================================================================
 *
 *       Filename:  ip3_atp_model.hpp
 *
 *    Description:
 *
 *        Version:  1.0
 *        Created:  Sunday 20 July 2014 10:44:10  IST
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Anup Pillai (), anupgpillai@gmail.com
 *   Organization:  IISER Pune
 *
 * =====================================================================================
 */

#ifndef ASTRON_IP3_ATP_MODEL_HPP_INCLUDED
#define ASTRON_IP3_ATP_MODEL_HPP_INCLUDED
#include <vector>
#include <iostream>
#include <cmath>
#include <boost/math/constants/constants.hpp>
#include "astron_utility_functions.hpp"
//const double pi = boost::math::constants::pi<double>();

//DECLATATIONS FOR IP3_SYSTEM
class IP3
{
   private:
//------------------parameters from reduced Li-Rinzel form for IP3-calicum signalling
      double a2 = 0.2E-03;     //    0.2         1/(micro M * sec)
      double d1 = 0.13;   //     0.13        micro M
      double d2 = 1.049;  //     1.049       micro M
      double d3 = 0.9434;   //   0.9434      micro M
      double d5 = 0.08234; //    0.08234     micro M

      double c0 = 2.0;  //       2.0         micro M
      double c1 = 0.185;   //                Dimensionless
      double v1 = 6.0E-03; //        6.0         1/sec
      double v2 = 0.11E-03;//        0.11        1/sec
      double v3 = 0.9E-03;     //    0.9         1/ (micro M * sec)
      double k3 = 0.1;   //      0.1         micro M

//------------ parameters from Stamastakis-Mantzaris model for IP3 via ATP

      double v4 = 4.0E-03;  //4.0 micro M/sec
      double k4 = 0.3;  // 0.3 micro M
      double a5 = 0.0; // dim.less (calcium feedback for IP3 production)
      double k5 = 1.1; // 1.1 micro M
      double v6 = 4E-03; // 0.19 /sec

      double c7 = 1.0; // Dim.less cy_vol/extr-cell_vol
      double v7 = 5.0E-03; // 5.0 micro M/sec (maximum ATP production rate)
      double v8 = 6.0E-03;  // 6.0 micro M/sec (maximum degeneration rate)
      double k8 = 5.0; // 5.0 micro M
      double F0 = 0.05; // dimention less (ATP feedback)
      double Cmax = 1.5; // 1.5 micro M (ATP feedback)
   public:

      double CaER = 0.0;      // micro M (Calcium concentration in the ER)
      //double ip3_conc = 0.0;      // micro M (Given IP3 concentration in the cytosol)
      //double ca_conc = 0.0;

      double ip3_tau = 0.0;
      double ip3_rate = 0.0;
      double ip3_thres = 0.0;
      double ip3_gen = 0.0;
      double ip3_equ = 0.0;

//------------------ CONSTRUCTORS

   IP3(): CaER(0.0)//, ca_conc(0.0), ip3_conc(0.0) /* IP3 class implicit constructor */
   {
   };

   /* ASTRO class explicit constructor */
   //IP3( double CaER_, double ca_conc_, double ip3_conc_ ): CaER(CaER_), ca_conc(ca_conc_), ip3_conc(ip3_conc_)
   IP3( double CaER_ ): CaER(CaER_)
   {
   };
//------------------ Function declarations
   void set_CaER(double ca_conc);

   double m_inf(double ip3_conc, double ca_conc);
   double h_inf(double ip3_conc, double ca_conc);
   double h_tau(double ip3_conc, double ca_conc);

   double F_ca(double ca_conc);

   template <class State, class Deriv >
   void operator() ( const State &x, Deriv &dxdt , const double  t );
};
//--------------------m_inf Function
double IP3::m_inf(double ip3_conc, double ca_conc)
{
   double value =  ( ip3_conc / ( ip3_conc + d1 ) ) * ( ca_conc / (ca_conc + d5) ) ;
   return value;
};
//--------------------h_tau Function
double IP3::h_tau(double ip3_conc, double ca_conc)
{
   double Q2 = d2 * ((ip3_conc + d1)/(ip3_conc + d3));
   double value  = 1 / (a2 * (Q2 + ca_conc) );
   return value;
}

//--------------------h_inf Function
double IP3::h_inf(double ip3_conc, double ca_conc)
{
   double Q2 = d2 * ((ip3_conc + d1)/(ip3_conc + d3));
   double value = Q2/(Q2+ca_conc);
   return value;
};
//--------------------Set CaER Function
void IP3::set_CaER(double ca_conc)
{
   this->CaER = (c0 - ca_conc) / c1;
   //std::cout << "Set_CaER = " << (c0 - ca_conc)/c1 << "\t" << CaER << "\n";
};
//----------Calicum induced ATP release function
double IP3::F_ca(double ca_conc)
{
   double val = ( (F0/(F0-1)) - (2 * (ca_conc/Cmax)) ) / ( (1/(F0-1)) - pow((ca_conc/Cmax),2) );
   return val;
}
//------- ASTRO class ODE Function
template <class State, class Deriv >
void IP3::operator() ( const State &x_, Deriv &dxdt_ , const double t )
{
   typename boost::range_iterator< const State >::type x = boost::begin( x_ );
   typename boost::range_iterator< Deriv >::type dxdt = boost::begin( dxdt_ );

   //std::cout << CaER << "\n";

   set_CaER(x[1]); // Update ER calcium level

   dxdt[0] = ( h_inf(x[2],x[1]) - x[0] ) / h_tau(x[2],x[1]) ; // dh/dt

   double JCh = ( c1 * v1 * pow(m_inf(x[2],x[1]),3) * pow(x[0],3) * (x[1] - CaER) ); //   J_channel
   double JPump = ( v3 * pow(x[1],2) ) / ( pow(k3,2) + pow(x[1],2) ) ;   //    J_Pump
   double JLeak = c1 * v2 * ( x[1] - CaER );   //    J_Leak

   dxdt[1] = - (JCh + JPump + JLeak) ;// dCa/dt
   double ip3_degrade = (x[2] - ip3_equ)/ip3_tau;
   double ip3_mGluR = ip3_rate * heaviside(ip3_gen,ip3_thres);
   //double ip3_atp = (v4 * x[3])/(k4+x[3]);
   double ip3_atp = 0.0;//(v4 * x[3])/(k4+x[3]) * ( (x[1] + ((1-a5)*k5)) / (x[1] + k5) ); //- v6*x[2];
   dxdt[2] =  -ip3_degrade + ip3_mGluR + ip3_atp ;//dip3/dt
   //dxdt[3] = ( c7 * v7 * F_ca(x[1]) ) - ( v8 * (x[3] / (k8 + x[3])) ) ; //dATP/dt

};

#endif // ASTRON_IP3_ATP_MODEL_HPP_INCLUDED


// Copyright Louis Dionne 2013-2016
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)

#include <boost/hana/assert.hpp>
#include <boost/hana/config.hpp>
#include <boost/hana/equal.hpp>
#include <boost/hana/for_each.hpp>
#include <boost/hana/integral_constant.hpp>
#include <boost/hana/mult.hpp>
#include <boost/hana/negate.hpp>
#include <boost/hana/plus.hpp>
#include <boost/hana/tuple.hpp>

#include <string>
#include <vector>
namespace hana = boost::hana;

int main() {

{

//! [operators]
BOOST_HANA_CONSTANT_CHECK(hana::int_c<1> + hana::int_c<3> == hana::int_c<4>);

// Mixed-type operations are supported, but only when it involves a
// promotion, and not a conversion that could be lossy.
BOOST_HANA_CONSTANT_CHECK(hana::size_c<3> * hana::ushort_c<5> == hana::size_c<15>);
BOOST_HANA_CONSTANT_CHECK(hana::llong_c<15> == hana::int_c<15>);
//! [operators]

}{

//! [times_loop_unrolling]
std::string s;
for (char c = 'x'; c <= 'z'; ++c)
    hana::int_<5>::times([&] { s += c; });

BOOST_HANA_RUNTIME_CHECK(s == "xxxxxyyyyyzzzzz");
//! [times_loop_unrolling]

}{

//! [times_higher_order]
std::string s;
BOOST_HANA_CONSTEXPR_LAMBDA auto functions = hana::make_tuple(
    [&] { s += "x"; },
    [&] { s += "y"; },
    [&] { s += "z"; }
);
hana::for_each(functions, hana::int_<5>::times);
BOOST_HANA_RUNTIME_CHECK(s == "xxxxxyyyyyzzzzz");
//! [times_higher_order]

}{

//! [from_object]
std::string s;
for (char c = 'x'; c <= 'z'; ++c)
    hana::int_c<5>.times([&] { s += c; });

BOOST_HANA_RUNTIME_CHECK(s == "xxxxxyyyyyzzzzz");
//! [from_object]

}{

//! [times_with_index_runtime]
std::vector<int> v;
hana::int_<5>::times.with_index([&](auto index) { v.push_back(index); });

BOOST_HANA_RUNTIME_CHECK(v == std::vector<int>{0, 1, 2, 3, 4});
//! [times_with_index_runtime]

//! [times_with_index_compile_time]
constexpr auto xs = hana::tuple_c<int, 0, 1, 2>;
hana::int_<3>::times.with_index([xs](auto index) {
    BOOST_HANA_CONSTANT_CHECK(xs[index] == index);
});
//! [times_with_index_compile_time]

}{

//! [literals]
using namespace hana::literals; // contains the _c suffix

BOOST_HANA_CONSTANT_CHECK(1234_c == hana::llong_c<1234>);
BOOST_HANA_CONSTANT_CHECK(-1234_c == hana::llong_c<-1234>);
BOOST_HANA_CONSTANT_CHECK(1_c + (3_c * 4_c) == hana::llong_c<1 + (3 * 4)>);
//! [literals]

}{

//! [integral_c]
BOOST_HANA_CONSTANT_CHECK(hana::integral_c<int, 2> == hana::int_c<2>);
static_assert(decltype(hana::integral_c<int, 2>)::value == 2, "");
//! [integral_c]

}

}


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

using namespace Eigen;
using namespace std;

int main(int, char**)
{
  cout.precision(3);
  Vector3d v(1,2,3);
v.array() += 3;
v.array() -= 2;
cout << v << endl;

  return 0;
}


//==================================================================================================
/*!
  @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_ARCH_COMMON_SIMD_FUNCTION_HYPOT_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_HYPOT_HPP_INCLUDED
#include <boost/simd/detail/overload.hpp>

#include <boost/simd/meta/hierarchy/simd.hpp>
#include <boost/simd/detail/constant/maxexponentm1.hpp>
#include <boost/simd/detail/constant/minexponent.hpp>
#include <boost/simd/function/abs.hpp>
#include <boost/simd/function/exponent.hpp>
#include <boost/simd/function/ldexp.hpp>
#include <boost/simd/function/max.hpp>
#include <boost/simd/function/min.hpp>
#include <boost/simd/function/plus.hpp>
#include <boost/simd/function/sqr.hpp>
#include <boost/simd/function/sqrt.hpp>
#include <boost/simd/function/unary_minus.hpp>
#include <boost/simd/detail/dispatch/meta/as_integer.hpp>

#ifndef BOOST_SIMD_NO_INVALIDS
#include <boost/simd/function/if_else.hpp>
#include <boost/simd/function/logical_and.hpp>
#include <boost/simd/function/logical_or.hpp>
#include <boost/simd/function/is_inf.hpp>
#include <boost/simd/function/is_nan.hpp>
#include <boost/simd/constant/inf.hpp>
#endif

namespace boost { namespace simd { namespace ext
{
   namespace bd = boost::dispatch;
   namespace bs = boost::simd;
   BOOST_DISPATCH_OVERLOAD_IF(hypot_
                          , (typename A0, typename X)
                          , (detail::is_native<X>)
                          , bd::cpu_
                          , bs::pack_<bd::floating_<A0>, X>
                          , bs::pack_<bd::floating_<A0>, X>
                          )
   {
      BOOST_FORCEINLINE A0 operator()( const A0& a0, const A0& a1) const BOOST_NOEXCEPT
      {
        using iA0 = bd::as_integer_t<A0>;
        A0 r =  bs::abs(a0);
        A0 i =  bs::abs(a1);
        iA0 e =  exponent(bs::max(i, r));
        e = bs::min(bs::max(e,Minexponent<A0>()),Maxexponentm1<A0>());
        A0 res =  ldexp(sqrt(sqr(ldexp(r, -e))+sqr(ldexp(i, -e))), e);
        #ifndef BOOST_SIMD_NO_INVALIDS
        auto test = logical_or(logical_and(is_nan(a0), is_inf(a1)),
                              logical_and(is_nan(a1), is_inf(a0)));
        return if_else(test, Inf<A0>(), res);
        #else
        return res;
        #endif
      }
   };

  BOOST_DISPATCH_OVERLOAD_IF ( hypot_
                          , (typename A0, typename X)
                          , (detail::is_native<X>)
                          , bd::cpu_
                          , bs::fast_tag
                          , bs::pack_<bd::floating_<A0>, X>
                          , bs::pack_<bd::floating_<A0>, X>
                          )
  {

    BOOST_FORCEINLINE A0 operator() (const fast_tag &,  A0 const& a0, A0 const& a1
                                    ) const BOOST_NOEXCEPT
    {
      return boost::simd::sqrt(bs::fma(a0, a0, sqr(a1)));
    }
  };
} } }

#endif


#include <NTL/vec_lzz_p.h>

NTL_START_IMPL

void conv(vec_zz_p& x, const vec_ZZ& a)
{
   long i, n;

   n = a.length();
   x.SetLength(n);

   zz_p* xp = x.elts();
   const ZZ* ap = a.elts();

   for (i = 0; i < n; i++)
      conv(xp[i], ap[i]);
}

void conv(vec_ZZ& x, const vec_zz_p& a)
{
   long n = a.length();
   x.SetLength(n);
   long i;
   for (i = 0; i < n; i++)
      x[i] = rep(a[i]);
}

void InnerProduct(zz_p& x, const vec_zz_p& a, const vec_zz_p& b)
{
   long n = min(a.length(), b.length());
   long i;
   zz_p accum, t;

   clear(accum);
   for (i = 0; i < n; i++) {
      mul(t, a[i], b[i]);
      add(accum, accum, t);
   }

   x = accum;
}

void InnerProduct(zz_p& x, const vec_zz_p& a, const vec_zz_p& b,
                  long offset)
{
   if (offset < 0) Error("InnerProduct: negative offset");
   if (NTL_OVERFLOW(offset, 1, 0)) Error("InnerProduct: offset too big");

   long n = min(a.length(), b.length()+offset);
   long i;
   zz_p accum, t;

   clear(accum);
   for (i = offset; i < n; i++) {
      mul(t, a[i], b[i-offset]);
      add(accum, accum, t);
   }

   x = accum;
}

long CRT(vec_ZZ& gg, ZZ& a, const vec_zz_p& G)
{
   long n = gg.length();
   if (G.length() != n) Error("CRT: vector length mismatch");

   long p = zz_p::modulus();

   ZZ new_a;
   mul(new_a, a, p);

   long a_inv;
   a_inv = rem(a, p);
   a_inv = InvMod(a_inv, p);

   long p1;
   p1 = p >> 1;

   ZZ a1;
   RightShift(a1, a, 1);

   long p_odd = (p & 1);

   long modified = 0;

   long h;

   ZZ g;
   long i;
   for (i = 0; i < n; i++) {
      if (!CRTInRange(gg[i], a)) {
         modified = 1;
         rem(g, gg[i], a);
         if (g > a1) sub(g, g, a);
      }
      else
         g = gg[i];

      h = rem(g, p);
      h = SubMod(rep(G[i]), h, p);
      h = MulMod(h, a_inv, p);
      if (h > p1)
         h = h - p;

      if (h != 0) {
         modified = 1;

         if (!p_odd && g > 0 && (h == p1))
            MulSubFrom(g, a, h);
         else
            MulAddTo(g, a, h);
      }

      gg[i] = g;
   }

   a = new_a;

   return modified;
}

void mul(vec_zz_p& x, const vec_zz_p& a, zz_p b)
{
   long n = a.length();
   x.SetLength(n);

   long i;

   if (n <= 1) {

      for (i = 0; i < n; i++)
	 mul(x[i], a[i], b);

   }
   else {

      long p = zz_p::modulus();
      double pinv = zz_p::ModulusInverse();
      long bb = rep(b);
      mulmod_precon_t bpinv = PrepMulModPrecon(bb, p, pinv);

      const zz_p *ap = a.elts();
      zz_p *xp = x.elts();

      for (i = 0; i < n; i++)
         xp[i].LoopHole() = MulModPrecon(rep(ap[i]), bb, p, bpinv);

   }
}

void mul(vec_zz_p& x, const vec_zz_p& a, long b_in)
{
   zz_p b;
   b = b_in;
   mul(x, a, b);
}

void add(vec_zz_p& x, const vec_zz_p& a, const vec_zz_p& b)
{
   long n = a.length();
   if (b.length() != n) Error("vector add: dimension mismatch");

   x.SetLength(n);
   long i;
   for (i = 0; i < n; i++)
      add(x[i], a[i], b[i]);
}

void sub(vec_zz_p& x, const vec_zz_p& a, const vec_zz_p& b)
{
   long n = a.length();
   if (b.length() != n) Error("vector sub: dimension mismatch");
   x.SetLength(n);
   long i;
   for (i = 0; i < n; i++)
      sub(x[i], a[i], b[i]);
}

void clear(vec_zz_p& x)
{
   long n = x.length();
   long i;
   for (i = 0; i < n; i++)
      clear(x[i]);
}

void negate(vec_zz_p& x, const vec_zz_p& a)
{
   long n = a.length();
   x.SetLength(n);
   long i;
   for (i = 0; i < n; i++)
      negate(x[i], a[i]);
}

long IsZero(const vec_zz_p& a)
{
   long n = a.length();
   long i;

   for (i = 0; i < n; i++)
      if (!IsZero(a[i]))
         return 0;

   return 1;
}

vec_zz_p operator+(const vec_zz_p& a, const vec_zz_p& b)
{
   vec_zz_p res;
   add(res, a, b);
   NTL_OPT_RETURN(vec_zz_p, res);
}

vec_zz_p operator-(const vec_zz_p& a, const vec_zz_p& b)
{
   vec_zz_p res;
   sub(res, a, b);
   NTL_OPT_RETURN(vec_zz_p, res);
}

vec_zz_p operator-(const vec_zz_p& a)
{
   vec_zz_p res;
   negate(res, a);
   NTL_OPT_RETURN(vec_zz_p, res);
}

zz_p operator*(const vec_zz_p& a, const vec_zz_p& b)
{
   zz_p res;
   InnerProduct(res, a, b);
   return res;
}

void VectorCopy(vec_zz_p& x, const vec_zz_p& a, long n)
{
   if (n < 0) Error("VectorCopy: negative length");
   if (NTL_OVERFLOW(n, 1, 0)) Error("overflow in VectorCopy");

   long m = min(n, a.length());

   x.SetLength(n);

   long i;

   for (i = 0; i < m; i++)
      x[i] = a[i];

   for (i = m; i < n; i++)
      clear(x[i]);
}

NTL_END_IMPL


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.

// 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)
//
// Polygon Overlay Example

#include <fstream>
#include <iostream>
#include <string>
#include <vector>

#include <boost/foreach.hpp>

#include <boost/geometry/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/adapted/c_array.hpp>

#if defined(HAVE_SVG)
#  include <boost/geometry/io/svg/svg_mapper.hpp>
#endif

BOOST_GEOMETRY_REGISTER_C_ARRAY_CS(cs::cartesian)

int main(void)
{
    namespace bg = boost::geometry;

    typedef bg::model::d2::point_xy<double> point_2d;
    typedef bg::model::polygon<point_2d> polygon_2d;

#if defined(HAVE_SVG)
    std::ofstream stream("05_a_intersection_polygon_example.svg");
    bg::svg_mapper<point_2d> svg(stream, 500, 500);
#endif

    // Define a polygons and fill the outer rings.
    polygon_2d a;
    {
        const double c[][2] = {
            {160, 330}, {60, 260}, {20, 150}, {60, 40}, {190, 20}, {270, 130}, {260, 250}, {160, 330}
        };
        bg::assign_points(a, c);
    }
    bg::correct(a);
    std::cout << "A: " << bg::dsv(a) << std::endl;

    polygon_2d b;
    {
        const double c[][2] = {
            {300, 330}, {190, 270}, {150, 170}, {150, 110}, {250, 30}, {380, 50}, {380, 250}, {300, 330}
        };
        bg::assign_points(b, c);
    }
    bg::correct(b);
    std::cout << "B: " << bg::dsv(b) << std::endl;
#if defined(HAVE_SVG)
    svg.add(a);
    svg.add(b);

    svg.map(a, "opacity:0.6;fill:rgb(0,255,0);");
    svg.map(b, "opacity:0.6;fill:rgb(0,0,255);");
#endif

    // Calculate interesection(s)
    std::vector<polygon_2d> intersection;
    bg::intersection(a, b, intersection);

    std::cout << "Intersection of polygons A and B" << std::endl;
    BOOST_FOREACH(polygon_2d const& polygon, intersection)
    {
        std::cout << bg::dsv(polygon) << std::endl;
#if defined(HAVE_SVG)
        svg.map(polygon, "opacity:0.5;fill:none;stroke:rgb(255,0,0);stroke-width:6");
#endif
    }

    return 0;
}


#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <Eigen/Dense>
#include "../include/model.h"
#include "../include/optimizer.h"
#include "../datasets/include/mnist.h"
#include <matplotlibcpp.h>

using namespace Eigen;
namespace plt = matplotlibcpp;

int main()
{
    using std::cout;
    using std::endl;
    using std::unordered_map;
    using std::vector;
    using namespace MyDL;

    // ----------------------
    // parameters
    // ----------------------
    int num_iters = 6000;        // 今後のために、この辺の値をJSON等から読み込める仕組みを作る(=>アルゴリズムの実装が正しければ、パラメータは設定ファイルで管理)
    double learning_rate = 0.05; // 浮動小数点で宣言すること。うっかりint型にすると0になるので、勾配が更新できない

    int batch_size = 100;
    int input_size = 28 * 28;
    int hidden_size = 50;
    int output_size = 10;

    // ----------------------
    // Data Loader
    // ----------------------
    MatrixXd train_X = MatrixXd::Zero(batch_size, input_size);
    MatrixXd test_X = MatrixXd::Zero(batch_size, input_size);
    MatrixXd train_y = MatrixXd::Zero(batch_size, 10);
    MatrixXd test_y = MatrixXd::Zero(batch_size, 10);

    MnistEigenDataset mnist(batch_size);

    // ----------------------
    // DNN
    // ----------------------
    TwoLayerMLP model(input_size, hidden_size, output_size);

    // ----------------------
    // Optimizer
    // ----------------------

    // SGD optimizer(0.05);
    // Momentum optimizer(0.01, 0.99);
    // AdaGrad optimizer(0.001);
    // RMSprop optimizer(0.001, 0.999);
    Adam optimizer(0.001, 0.9, 0.999);

    // ----------------------
    // データ格納用
    // ----------------------
    vector<MatrixXd> inputs, loss, val_inputs;
    unordered_map<string, MatrixXd> grads;
    double accuracy;
    // ----------------------
    // For Visualization
    // ----------------------
    vector<double> loss_history(num_iters), accuracy_history(num_iters / 10);
    vector<int> loss_counter(num_iters), accuracy_counter(num_iters / 10);

    // ----------------------
    // Learning Loop
    // ----------------------
    for (int i = 0; i < num_iters; i++)
    {
        mnist.next_train(train_X, train_y);
        inputs.push_back(train_X);
        // inputs.push_back(train_y);

        // Back Propagation
        grads = model.gradient(inputs, train_y);

        // Update Prameters
        optimizer.update(model.params, grads);

        // Loss Calculation
        loss = model.loss(inputs, train_y);
        loss_history[i] = loss[0](0);
        loss_counter[i] = i;

        cout << "iter" << i << " loss: " << loss[0] << endl;

        if (i % 10 == 0)
        {
            mnist.next_test(test_X, test_y);
            val_inputs.push_back(test_X);
            // val_inputs.push_back(test_y);
            accuracy = model.accuracy(val_inputs, test_y);
            cout << "accuracy: " << accuracy << endl;
            val_inputs.clear();
            accuracy_history[i / 10] = accuracy;
            accuracy_counter[i / 10] = i / 10;
        }

        inputs.clear();
    }

    return 0;
}


#include <RcppArmadillo.h>
// [[Rcpp::depends("RcppArmadillo")]]
// [[Rcpp::depends("BH")]]

#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/math/special_functions/gamma.hpp>
#include <boost/random.hpp>
#include <boost/random/gamma_distribution.hpp>
#include <boost/math/distributions.hpp>

#include <numeric>
#include <algorithm>
#include <map>
#include <string>
#include <iostream>

using namespace Rcpp;
using namespace arma;
using namespace std;
using namespace boost::multiprecision;
using namespace boost::math;

class LDA {
  public:
    int K; // K: number of topics
  int W; // W: size of Vocabulary
  int D; // D: number of documents
  vector< vector<int> >  w_num;
  vector< vector<int> >  z;
  vector<int> nd_sum;
  vector<int> nw_sum;
  NumericMatrix nd;
  NumericMatrix nw;
  NumericMatrix phi_avg;
  arma::mat PhiProdMat;
  arma::mat n_wd;
  vector< vector < vector<int> > > z_list;
  vector< NumericMatrix > phi_list;
  vector< NumericMatrix > theta_list;
  NumericMatrix theta_avg;
  CharacterVector Vocabulary; // vector storing all (unique) words of vocabulary
  double alpha; // hyper-parameter for Dirichlet prior on theta
  double beta; //  hyper-parameter for Dirichlet prior on phi
  double sigma; // for Langevin sampler
  boost::mt19937 rng; // seed for random sampling
  List a;

  LDA(Reference Obj);

  void collapsedGibbs(int iter, int burnin, int thin);
  void NichollsMH(int iter, int burnin, int thin);
  void LangevinMHSampling(int iter, int burnin, int thin);

  NumericVector DocTopics(int d, int k);
  NumericMatrix Topics(int k);
  CharacterVector TopicTerms(int k, int no);
  CharacterMatrix Terms(int k);
  arma::rowvec rDirichlet(arma::rowvec param, int length);
  arma::rowvec rDirichlet2(arma::rowvec param, int length);
  arma::mat DrawFromProposal(arma::mat phi_current);
  arma::mat InitPhiMat();
  List getPhiList();
  List getZList();

  double PhiDensity2(NumericMatrix phi);
  double PhiDensity(arma::mat phi);

  arma::mat getPhiGradient(arma::mat phi);

  double rgamma_cpp(double alpha);
  double rbeta_cpp(double shape1, double shape2);
  double rnorm_cpp(double mean, double sd);

  double LogPhiProd(arma::mat phi);
  vector<double> LogPhiProd_vec(arma::mat phi);
  arma::mat DrawLangevinProposal(arma::mat phi_current);
  double EvalLangevinProposal(arma::mat PhiFrom, arma::mat PhiTo);
  arma::mat ProjectProposalToSimplex(arma::mat PhiProposal);

  private:
    vector< vector<int> > CreateIntMatrix(List input);

  NumericMatrix get_phis();
  NumericMatrix get_thetas();
  NumericMatrix MatrixToR(NumericMatrix input);
  NumericMatrix avgMatrix(NumericMatrix A, NumericMatrix B, int weight);
  NumericMatrix getTDM(int W, int D, List w_num);

  double ProposalDensity(arma::mat phi);
  double ArrayMax(double array[], int numElements);
  double ArrayMin(double array[], int numElements);

};

LDA::LDA(Reference Obj)
{

  K = as<int>(Obj.field("K"));
  W = as<int>(Obj.field("W"));
  D = as<int>(Obj.field("D"));

  nw = as<NumericMatrix> (Obj.field("nw"));
  nd = as<NumericMatrix> (Obj.field("nd"));

  alpha = Obj.field("alpha");
  beta = Obj.field("beta");
  sigma = 0.00001;

  Vocabulary = Obj.field("Vocabulary");

  List temp_w = Obj.field("w_num");
  List temp_z = Obj.field("z");
  w_num = CreateIntMatrix(temp_w);

  NumericMatrix tdm = getTDM(W, D, temp_w);
  int i = tdm.nrow(), j = tdm.ncol();
  arma::mat n_wd_pointer(tdm.begin(), i, j, false);
  n_wd = n_wd_pointer;

  z = CreateIntMatrix(temp_z);

  nd_sum = as<vector<int> > (Obj.field("nd_sum"));
  nw_sum = as<vector<int> > (Obj.field("nw_sum"));

  a = List::create(Named("z")=z);
};

List LDA::getPhiList()
{
  int iter = phi_list.size();
  List ret(iter);

  for (int i = 0; i<iter; i++)
  {
    ret[i] = phi_list[i];
  }
  return ret;
}

List LDA::getZList()
{
  int length = z_list.size();
  List ret(length);

  for (int i = 0; i<length; i++)
  {
    ret[i] = wrap(z_list[i]);
  }
  return ret;
}

vector< vector<int> > LDA::CreateIntMatrix(List input)
{

  int inputLength = input.size();
  vector< vector<int> > output;

  for(int i=0; i<inputLength; i++) {
    vector<int> test = as<vector<int> > (input[i]);
    output.push_back(test);
  }

  return output;

}

NumericVector LDA::DocTopics(int d, int k)
{
  vector<double> d_theta(K);
  NumericVector d_theta_R = theta_avg(d,_);
  d_theta = as<vector<double> > (d_theta_R);
  NumericVector ret_vector(k);

  for (int i=0;i<k;i++)
  {
    std::vector<double>::iterator result;
    result = std::max_element(d_theta.begin(),d_theta.end());
    int biggest_id = std::distance(d_theta.begin(), result);
    ret_vector[i] = biggest_id;
    d_theta[biggest_id] = 0;
  }

  return ret_vector;
}

NumericMatrix LDA::Topics(int k)
{
  NumericMatrix ret(D,k);
  for (int i = 0; i<D; i++)
  {
    NumericVector temp = DocTopics(i,k);
    ret(i,_) = temp;
  }
  ret = MatrixToR(ret);
  return ret;
}

CharacterVector LDA::TopicTerms(int k, int no)
{
  vector<double> k_phi(W);
  NumericVector k_phi_R = phi_avg(k,_);
  k_phi = as<vector<double> > (k_phi_R);
  NumericVector ret_vector(no);

  for (int i=0;i<no;i++)
  {
    std::vector<double>::iterator result;
    result = std::max_element(k_phi.begin(),k_phi.end());
    int biggest_id = std::distance(k_phi.begin(), result);
    ret_vector[i] = biggest_id;
    k_phi[biggest_id] = 0;
  }

  CharacterVector ret_char_vector(no);

  for (int i=0;i<no;i++)
  {
    ret_char_vector[i] = Vocabulary[ret_vector[i]];
  }

  return ret_char_vector;

}

CharacterMatrix LDA::Terms(int k)
{
  CharacterMatrix ret(K,k);
  for (int i = 0; i < K; i++)
  {
    CharacterVector temp = TopicTerms(i,k);
    ret(i,_) =  temp;
  }
  return ret;
}

NumericMatrix LDA::MatrixToR(NumericMatrix input)
{
  int n = input.nrow(), k = input.ncol();
  NumericMatrix output(n,k);
  for (int i = 0; i<n; i++)
  {
    for (int j = 0; j<k; j++)
    {
      output(i,j) = input(i,j) + 1;
    }
  }
  return output;
}

void LDA::collapsedGibbs(int iter, int burnin, int thin)
{

  double Kd = (double) K;
  double Wd = (double) W;
  double W_Beta  = Wd * beta;
  double K_Alpha = Kd * alpha;

  for (int i = 0; i < iter; ++i)
  {
    for (int d = 0; d < D; ++d)
    {
      for (int w = 0; w < nd_sum[d]; ++w)
      {
        int word = w_num[d][w] - 1;
        int topic = z[d][w] - 1;

        nw(word,topic) -= 1;
        nd(d,topic) -= 1;
        nw_sum[topic] -= 1;
        nd_sum[d] -=  1;

        vector<double>  prob(K);

        for(int j=0; j<K; j++)
        {
          double nw_ij = nw(word,j);
          double nd_dj = nd(d,j);
          prob[j] = (nw_ij + beta) / (nw_sum[j] + W_Beta) *
            (nd_dj + alpha) / (nd_sum[d] + K_Alpha);
        }

        for (int r = 1; r < K; ++r)
        {
          prob[r] = prob[r] + prob[r - 1];
        }

        double u  = prob[K-1] * rand() / double(RAND_MAX);

        int new_topic = 0; // set up new topic

        for (int nt = 0 ; nt < K; ++nt)
        {
          if (prob[nt] > u)
          {
            new_topic = nt;
            break;
          }
        }

        //  assign new z_i to counts
        nw(word,new_topic) +=  1;
        nd(d,new_topic) += 1;
        nw_sum[new_topic] += 1;
        nd_sum[d] += 1;

        z[d][w] = new_topic + 1;

      }

    }

    if (i % thin == 0 && i > burnin)
    {
      z_list.push_back(z);

      NumericMatrix current_phi = get_phis();
      NumericMatrix current_theta = get_thetas();
      phi_list.push_back(current_phi);
      theta_list.push_back(current_theta);

      if(phi_list.size()==1) phi_avg = current_phi;
      else phi_avg =  avgMatrix(phi_avg, current_phi, phi_list.size());

      if(theta_list.size()==1) theta_avg = current_theta;
      else theta_avg =  avgMatrix(theta_avg, current_theta, theta_list.size());

    }

  }
}

NumericMatrix LDA::avgMatrix(NumericMatrix A, NumericMatrix B, int weight)
{
  int nrow = A.nrow();
  int ncol = A.ncol();
  NumericMatrix C(nrow,ncol);

  float wf = (float) weight;
  float propA = (wf-1) / wf;
  float propB = 1 / wf;

  for (int i=0; i<nrow;i++)
  {
    for (int j=0; j<ncol;j++)
    {
      C(i,j) =  propA * A(i,j) + propB * B(i,j);
    }
  }

  return C;
}

NumericMatrix LDA::get_phis()
{

  NumericMatrix phi(K,W);

  for (int k = 0; k < K; k++) {
    for (int w = 0; w < W; w++) {
      phi(k,w) = (nw(w,k) + beta) / (nw_sum[k] + W * beta);
    }
  }

  return phi;
}

NumericMatrix LDA::get_thetas()
{

  NumericMatrix theta(D,K);

  for (int d = 0; d<D; d++) {
    for (int k = 0; k<K; k++) {
      theta(d,k) = (nd(d,k) + alpha) / (nd_sum[d] + K * alpha);
    }
  }
  return theta;
}

NumericMatrix LDA::getTDM(int W, int D, List w_num) {

  NumericMatrix tdm(W,D);
  for (int d=0; d<D; ++d)
    for (int w=0; w<W; ++w)
    {
      int freq = 0;
      vector<int> current_w = as<vector<int> > (w_num[d]);
      int wlen = current_w.size();
      for (int l=0; l<wlen; ++l)
      {
        if(current_w[l] == w + 1) freq += 1;
      }

      tdm(w,d) = freq;
    }
  return tdm;
}

// using R: (unfortunately too slow to call R and convert objects back)
//NumericMatrix LDA::DrawFromProposal()
//  {
  //    Environment MCMCpack("package:MCMCpack");
  //    Function rdirichlet = MCMCpack["rdirichlet"];
  //    return rdirichlet(K,rep(beta,W));
  //  }

double LDA::rbeta_cpp(double shape1, double shape2)
{
  double u  = rand() / double(RAND_MAX);
  beta_distribution<> beta_dist(shape1, shape2);
  return quantile(beta_dist, u);
}

double LDA::rgamma_cpp(double alpha)
{
  boost::gamma_distribution<> dgamma(alpha);
  boost::variate_generator<boost::mt19937&,boost::gamma_distribution<> > ret_gamma( rng, dgamma);
  return ret_gamma();
}

double LDA::rnorm_cpp(double mean, double sd)
{
  boost::normal_distribution<> nd(mean, sd);
  boost::variate_generator<boost::mt19937&,
  boost::normal_distribution<> > ret_norm(rng, nd);
  return ret_norm();
}

arma::rowvec LDA::rDirichlet(arma::rowvec param, int length)
{
  rowvec ret(length);
  for (int l = 0; l<length; l++)
  {
    double beta = param[l];
    ret[l] = rgamma_cpp(beta);
  }
  ret = ret / sum(ret);
  return ret;
}

arma::rowvec LDA::rDirichlet2(arma::rowvec param, int length)
{
  vector<double> ret;
  param *= 10000;
  vector<double> param_vec = conv_to<vector<double> >::from(param);
  Rcout << param_vec[0] << "-";
  int len = length - 1;

  double paramSum = std::accumulate(param_vec.begin()+1,param_vec.end(),(double)0);
  Rcout << paramSum;
  ret.push_back(rbeta_cpp(param_vec[0], paramSum));
  for (int i=1; i<len;i++)
  {
    double paramSum = std::accumulate(param_vec.begin()+i+1,param_vec.end(),(double)0);
    double phi = rbeta_cpp(param_vec[i], paramSum);
    double sumRet = std::accumulate(ret.begin(),ret.end(),(double)0);
    ret.push_back((1-sumRet) * phi);
  }
  double sumRet = std::accumulate(ret.begin(),ret.end(),(double)0);
  ret.push_back(1-sumRet);
  return ret;
}

mat LDA::DrawFromProposal(arma::mat phi_current)
{
  arma::mat phi_sampled(K,W);
  for (int k=0;k<K;k++)
  {
    arma::rowvec phi_current_row = phi_current.row(k);
    arma::rowvec new_row = rDirichlet2(phi_current_row, W);
    // Rcout << new_row;
    phi_sampled.row(k) = new_row;
  }
  return phi_sampled;
}

mat LDA::InitPhiMat()
{
  arma::mat phi(K,W);

  for (int k=0; k<K; k++)
  {
    for (int w=0; w<W; w++)
    {
      phi(k,w) = beta / (W*beta);
    }
  }
  return phi;
}

void LDA::NichollsMH(int iter, int burnin, int thin)
{

  arma::mat phi_current = InitPhiMat();

  for (int t=1;t<iter;t++)
  {

    // Metropolis-Hastings Algorithm:
      // 1. draw from proposal density:
      arma::mat hyperParams = beta + 0.1 * (phi_current - beta);
    arma::mat phi_new = DrawFromProposal(hyperParams);

    // 2. Calculate acceptance probability
    double pi_new = PhiDensity(phi_new);
    double pi_old = PhiDensity(phi_current);
    double q_new = ProposalDensity(phi_new);
    double q_old = ProposalDensity(phi_current);

    double acceptanceMH = exp(pi_new + q_old - pi_old - q_new);
    double alphaMH = min((double)1,acceptanceMH);
    Rcout << "Acceptance Prob:" << alphaMH;

    // draw U[0,1] random variable
    double u  = rand() / double(RAND_MAX);
    if (u<=alphaMH) phi_current = phi_new;
    else phi_current = phi_current;

    if (t % thin == 0 && t > burnin) {
      NumericMatrix phi_add = wrap(phi_current);
      phi_list.push_back(phi_add);
      if(phi_list.size()==1) phi_avg = phi_add;
      else phi_avg =  avgMatrix(phi_avg, phi_add, phi_list.size());
    };

    // Rcout << pi_new;
    // Rcout << pi_old;
    // Rcout << q_new;
    // Rcout << q_old;

  }

}

double LDA::LogPhiProd(arma::mat phi)
{
  arma::mat logPhi = log(phi);
  double sumLik_vec[K];
  double logPhiProd = 0;

  for (int d=0; d<D; d++)
  {
    double sumLik = 0;
    arma::colvec nd = n_wd.col(d);

    for (int k=0; k<K; k++)
    {
      arma::rowvec logPhi_k = logPhi.row(k);
      sumLik_vec[k] = dot(logPhi_k,nd);
    }
    double b = ArrayMax(sumLik_vec,K);

    for (int k=0; k<K; k++)
    {
      sumLik += exp(sumLik_vec[k]-b);
    }

    logPhiProd += b + log(sumLik);
  }

  return logPhiProd;
}

vector<double> LDA::LogPhiProd_vec(arma::mat phi)
{
  vector<double> ret_vec;
  arma::mat logPhi = log(phi);
  arma::mat PhiProdMat_Pointer(K,D);
  double sumLik_vec[K];

  for (int d=0; d<D; d++)
  {
    double sumLik = 0;
    arma::colvec nd = n_wd.col(d);

    for (int k=0; k<K; k++)
    {
      arma::rowvec logPhi_k = logPhi.row(k);
      sumLik_vec[k] = dot(logPhi_k,nd);
      PhiProdMat_Pointer(k,d) = sumLik_vec[k];
    }
    double b = ArrayMax(sumLik_vec,K);

    for (int k=0; k<K; k++)
    {
      sumLik += exp(sumLik_vec[k]-b);
    }

    double ret_vec_d = b + log(sumLik);
    ret_vec.push_back(ret_vec_d);
  }
  PhiProdMat = PhiProdMat_Pointer;
  return ret_vec;
}

// function adapted from Yunmei Chen and Xiaojing Ye (2011)

// [[Rcpp::export]]
vector<double> ProjectOntoSimplex (vector<double> y)
{
  int m = y.size();
  bool bget = false;

  vector<double> s = y;
  std::sort(s.rbegin(), s.rend());

  double tmpsum = 0;
  double tmax = 0;

  for (int i = 0; i<m-1; i++)
  {
    tmpsum = tmpsum + s[i];
    tmax = (tmpsum - 1)/(i+1);
    if (tmax >= s[i+1])
    {
      bget = true;
      break;
    }
  }

  if (bget==false)
  {
    tmax = (tmpsum + s[m-1] - 1)/m;
  }

  vector<double> x;
  for (int j = 0; j<m;j++)
  {
    double elem1 = y[j] - tmax;
    double ret = max(elem1,0.0);
    x.push_back(ret);
  }

  return x;
}

arma::mat LDA::getPhiGradient(arma::mat phi)
{
  arma::mat phi2 = phi;
  arma::mat logPhi = log(phi);
  vector<double> denom_vec = LogPhiProd_vec(phi2);
  arma::mat gradient(K,W);

  for (int z=0;z<K;z++)
  {
    for(int w=0;w<W;w++)
    {

      double dSum = 0;

      for (int d = 0; d<D;d++)
      {
        double nwd = n_wd(w,d);
        if (nwd==0) dSum += 0;
        else
        {
          // Rcout << "nwd:" << nwd;
          arma::colvec nd = n_wd.col(d);
          arma::rowvec logPhi_k = logPhi.row(z);

          double dotProd = PhiProdMat(z,d);
          // Rcout << "dotProd:" << dotProd;
          // Rcout << "Dot Product: " << dotProd;

          double Numerator = log(nwd) + (nwd - 1)*logPhi(z,w) + dotProd - nd[w]*logPhi_k[w];
          // Rcout << Numerator;
          double Denominator = denom_vec[d];
          // Rcout << Denominator;
          dSum += exp(Numerator - Denominator);
        }
      }
      // Rcout << "dSum:" << dSum;
      gradient(z,w) = dSum + (beta - 1) / phi2(z,w);
      //Rcout << gradient(z,w);
    }
  }

  return gradient;
}

arma::mat LDA::DrawLangevinProposal(arma::mat phi_current)
{
  arma::mat PhiProposal(K,W);
  arma::mat PhiGradient = getPhiGradient(phi_current);
  for (int z=0; z<K; z++)
  {
    vector<double> prop_vec;
    for (int w=0; w<W; w++)
    {
      double error = rnorm_cpp(0,sigma);
      double sigma_squared = pow(sigma,2);
      PhiProposal(z,w) = phi_current(z,w) + 0.5 * sigma_squared * PhiGradient(z,w) + error;
    }
  }
  return PhiProposal;
}

double LDA::EvalLangevinProposal(arma::mat PhiFrom, arma::mat PhiTo)
{
  double sigma_squared = pow(sigma,2);
  double logDensity = 0;
  arma::mat PhiGradient = getPhiGradient(PhiFrom);
  for (int z=0; z<K; z++)
  {
    for (int w=0;w<W;w++)
    {
      double gradient_zw = PhiGradient(z,w);
      double mean = PhiFrom(z,w) + 0.5 * sigma_squared * gradient_zw;
      double PhiMeanDiff = PhiTo(z,w) - mean;
      logDensity -= (1/(2*sigma_squared))*pow(PhiMeanDiff,2);
    }
  }
  return logDensity;
}

arma::mat LDA::ProjectProposalToSimplex(arma::mat PhiProposal)
{
  for (int z=0; z<K; z++)
  {
    vector<double> prop_vec = conv_to<vector<double> >::from(PhiProposal.row(z));
    vector<double> Phi_proj_vec = ProjectOntoSimplex(prop_vec);
    PhiProposal.row(z) = conv_to<rowvec>::from(Phi_proj_vec);
  }
  return PhiProposal;
}

void LDA::LangevinMHSampling(int iter, int burnin, int thin)
{
  arma::mat phi_current = InitPhiMat();
  arma::mat phi_current_projected = ProjectProposalToSimplex(phi_current);

  for (int t=1;t<iter;t++)
  {

    // Metropolis Algorithm:
      // 1. draw from Langevin proposal density:
      arma::mat phi_new = DrawLangevinProposal(phi_current_projected);
    arma::mat phi_new_projected = ProjectProposalToSimplex(phi_new);

    // 2. Calculate acceptance probability
    double pi_new = PhiDensity(phi_new_projected);
    double pi_old = PhiDensity(phi_current_projected);
    double q_num = EvalLangevinProposal(phi_new_projected,phi_current);
    double q_denom = EvalLangevinProposal(phi_current_projected,phi_new);

    // Rcout << "Pi_new:" << pi_new;
    // Rcout << "Pi_old:" << pi_old;
    // Rcout << "Q_numerator:" << q_num;
    // Rcout << "Q_denominator:" << q_denom;

    double acceptanceMH = exp(pi_new + q_num - pi_old - q_denom);
    double alphaMH = min((double)1,acceptanceMH);
    // Rcout << "Acceptance Prob:" << alphaMH;

    // draw U[0,1] random variable
    double u  = rand() / double(RAND_MAX);
    if (u<=alphaMH)
    {
      phi_current = phi_new;
      phi_current_projected = phi_new_projected;
    }
    else
    {
      phi_current = phi_current;
      phi_current_projected = phi_current_projected;
    }

    if (t % thin == 0 && t > burnin) {
      NumericMatrix phi_add = wrap(phi_current_projected);
      phi_list.push_back(phi_add);
      if(phi_list.size()==1) phi_avg = phi_add;
      else phi_avg =  avgMatrix(phi_avg, phi_add, phi_list.size());
    };

  }

}

double LDA::ProposalDensity(arma::mat phi)
{
  double logBetaFun = 0;
  double betaSum = 0;
  for (int k=0; k<K;k++)
  {
    for (int w=0;w<W;w++)
    {
      double phi_scalar = phi(k,w);
      logBetaFun += lgamma(phi_scalar);
      betaSum  += phi_scalar;
    }

  }
  // double logBetaFun = K*(W*lgamma(beta)-lgamma(W*beta));
  logBetaFun -= lgamma(betaSum);

  arma::mat logPhi = log(phi);
  arma::mat temp = logPhi * (beta-1);
  double logPhiSum = accu(temp);

  double logDensity = logPhiSum - logBetaFun;
  return logDensity;
}

double LDA::PhiDensity(arma::mat phi)
{
  arma::mat logPhi = log(phi);
  double sumLik_vec[K];
  double logLikelihood = 0;

  for (int d=0; d<D; d++)
  {
    double sumLik = 0;
    arma::colvec nd = n_wd.col(d);

    for (int k=0; k<K; k++)
    {
      arma::rowvec logPhi_k = logPhi.row(k);
      sumLik_vec[k] = dot(logPhi_k,nd);
    }
    double b = ArrayMax(sumLik_vec,K);

    for (int k=0; k<K; k++)
    {
      sumLik += exp(sumLik_vec[k]-b);
    }

    logLikelihood +=  b + log(sumLik);
  }

  logLikelihood += D * log(alpha);
  logLikelihood -= D * log(K*alpha);
  // Rcout << "logLikelihood: " << logLikelihood;

  double logBetaFun = K*(lgamma(W*beta)-W*lgamma(beta));
  // Rcout << "logBetaFun: " << logBetaFun;

  double logPhiSum = 0;

  arma::mat temp = logPhi * (beta-1);
  logPhiSum = accu(temp);

  // Rcout << "LogPhiSum: " << logPhiSum;

  double logProb = logLikelihood + logBetaFun + logPhiSum;
  return logProb;
}

double LDA::PhiDensity2(NumericMatrix phi)
{
  arma::mat phi2 = as<arma::mat>(phi);
  arma::mat logPhi = log(phi2);
  double logLikelihood_vec[D];
  double logLikelihood = 0;

  for (int d=0; d<D; d++)
  {
    double sumLik = 0;
    arma::colvec nd = n_wd.col(d);

    for (int k=0; k<K; k++)
    {
      arma::rowvec logPhi_k = logPhi.row(k);
      double inProd_k = 0;

      for (int w=0; w<W; w++)
      {
        inProd_k += logPhi_k[w] * nd[w];
      }
      // Rcout << inProd_k;
      double sumLik_k = exp(inProd_k) * alpha;
      sumLik += sumLik_k;
    }
    logLikelihood_vec[d] = log(sumLik);
    logLikelihood += logLikelihood_vec[d];
  }

  logLikelihood -= D * log(K*alpha);
  //Rcout << "logLikelihood: " << logLikelihood;

  double logBetaFun = K*(lgamma(W*beta)-W*lgamma(beta));
  //Rcout << "logBetaFun: " << logBetaFun;

  double logPhiSum = 0;

  arma::mat temp = logPhi * (beta-1);
  logPhiSum = accu(temp);

  //Rcout << "LogPhiSum: " << logPhiSum;

  double logProb = logLikelihood + logBetaFun + logPhiSum;
  double Prob = exp(logProb);
  return Prob;
}

double LDA::ArrayMax(double array[], int numElements)
{
  double max = array[0];       // start with max = first element

  for(int i = 1; i<numElements; i++)
  {
    if(array[i] > max)
      max = array[i];
  }
  return max;                // return highest value in array
}

double LDA::ArrayMin(double array[], int numElements)
{
  double min = array[0];       // start with min = first element

  for(int i = 1; i<numElements; i++)
  {
    if(array[i] < min)
      min = array[i];
  }
  return min;                // return smallest value in array
}

// [[Rcpp::export]]
RCPP_MODULE(LDA_module) {
  class_<LDA>( "LDA" )
  .constructor<Reference>()
  //.field( "w_num", &LDA::w_num)
  .field( "a", &LDA::a)
  .field( "nd_sum", &LDA::nd_sum)
  .field("nd",&LDA::nd)
  .field( "nw_sum", &LDA::nw_sum)
  .field("nw",&LDA::nw)
  .field("K", &LDA::K)
  .field("D",&LDA::D)
  .field("phi_avg",&LDA::phi_avg)
  .field("theta_avg",&LDA::theta_avg)
  .method("collapsedGibbs",&LDA::collapsedGibbs)
  .method("Topics",&LDA::Topics)
  .method("Terms",&LDA::Terms)
  .method("NichollsMH",&LDA::NichollsMH)
  .method("DrawFromProposal",&LDA::DrawFromProposal)
  .method("getPhiList",&LDA::getPhiList)
  .method("getZList",&LDA::getZList)
  .method("getPhiGradient",&LDA::getPhiGradient)
  .method("rgamma_cpp",&LDA::rgamma_cpp)
  .method("rbeta_cpp",&LDA::rbeta_cpp)
  .method("InitPhiMat",&LDA::InitPhiMat)
  .method("rDirichlet2",&LDA::rDirichlet2)
  .method("LogPhiProd_vec",&LDA::LogPhiProd_vec)
  .method("DrawLangevinProposal",&LDA::DrawLangevinProposal)
  .method("LangevinMHSampling",&LDA::LangevinMHSampling)
  .method("PhiDensity",&LDA::PhiDensity)
  .method("ProjectProposalToSimplex",&LDA::ProjectProposalToSimplex)
  ;
}


#include <iostream>
#include <math.h>
#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
#include <boost/random.hpp>
#include <boost/random/normal_distribution.hpp>

using namespace std;
using namespace boost::python;

double rand_norm(double mean, double stdev){
  boost::mt19937 rng(rand());

  boost::normal_distribution<> nd(mean, stdev);

  boost::variate_generator<boost::mt19937&,
                           boost::normal_distribution<> > var_nor(rng, nd);

  double d = var_nor();

  return d;
}

double rand_norm_range(double mean, double stdev, double min, double max){
  int max_tries = 1000;
  int tries = 0;

  while (1){
    tries++;

    double x = rand_norm(mean, stdev);
    if ((x >= min) & (x <= max)){
      return x;
    }
    else if (tries >= max_tries){
      cout << "ERROR: exceeded max tries (n="
	   << max_tries
	   << ") to find a random variable" << endl;
      exit(1);
    }
  }
}

double calc_diffusion_simple(double frag_gc, double frag_len){
  /*
    Simple calculation of diffusion based on fragment length
  */
  double diff_coef = 44500;
  double stdev = sqrt(diff_coef / frag_len);
  return frag_gc + rand_norm(0, stdev);
}

double calc_diffusion_GC(double frag_GC, double frag_len,
		         double T, double B, double G, int M){
  /*
    Calculating diffusion in standard deviation of %G+C equivalents.
    Adding diffusion G+C (drawn from normal distribution with s.d.=calculated s.d.)
    to input G+C value.
    Args:
    frag_GC = G+C content of DNA fragment
    frag_len = DNA fragment length (bp)
    T = absolute temperature
    B = beta
    G = G coefficient (see Clay et al., 2003)
    M = molecular weight per base pair of dry cesium DNA
   */

  double frag_BD = frag_GC / 100 * 0.098 + 1.66;
  double R = 8.3145e7;
  double GC_sd = sqrt(pow(100 / 0.098, 2) * ((frag_BD*R*T)/(pow(B,2)*G*M*frag_len)));

  return rand_norm(0, GC_sd);
}

double calc_diffusion_BD(double frag_BD, double frag_len,
		         double T, double B, double G, int M){
  /*
    Calculating diffusion in standard deviation of buoyant_density equivalents (rho).
    Args:
    frag_BD = rho (buoyant density)
    frag_len = fragment length (bp)
    T = absolute temperature
    B = beta
    G = G coefficient (see Clay et al., 2003)
    M = molecular weight per base pair of dry cesium DNA
    Return:
    BD error due to diffusion value drawn from a normal distribution with a
    standard deviation determined by calculated diffusion
   */

  double R = 8.3145e7;
  double sd_BD = sqrt((frag_BD*R*T)/(pow(B,2)*G*M*frag_len));

  return rand_norm(0, sd_BD);
}

double GC2BD(double GC){
  /*
    Calaculate buoyant density from G+C.
    Args:
    GC = % GC of DNA fragment
  */
  return GC / 100.0 * 0.098 + 1.66;
}

double addIncorpBD(double frag_BD, double incorp_perc, double isoMaxBD){
  return incorp_perc / 100 * isoMaxBD + frag_BD;
}

BOOST_PYTHON_MODULE(SIPSimCpp)
{
  def("rand_norm", rand_norm);
  def("rand_norm_range", rand_norm_range);
  def("calc_diffusion_GC", calc_diffusion_GC);
  def("calc_diffusion_BD", calc_diffusion_BD);
  def("GC2BD", GC2BD);
  def("addIncorpBD", addIncorpBD);
}


// ConsoleApplication1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
using namespace std;
#include <ctime>
// Eigen 部分
#include <Eigen/Core>
//映入Cholesky
#include <Eigen/Cholesky>
// 稠密矩阵的代数运算（逆，特征值等）
#include <Eigen/Sparse>
#include <Eigen/Dense>
using namespace Eigen;     // 改成这样亦可 using Eigen::MatrixXd;
using namespace std;

#define  MATRIX_SIZE 4
/*
int main(int argc, char** argv)
{
    // 解方程
    // 我们求解 A * x = b 这个方程
    // 直接求逆自然是最直接的，但是求逆运算量大

    //Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic > A1;
    //A1 = Eigen::MatrixXd::Random(MATRIX_SIZE, MATRIX_SIZE);
    SparseMatrix<double> A1(MATRIX_SIZE, MATRIX_SIZE);
    Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic > b1;
    b1 = Eigen::MatrixXd::Random(MATRIX_SIZE, 1);

    clock_t time_stt = clock(); // 计时
    //Cholesky 解方程

    // 直接求逆
    Eigen::Matrix<double, MATRIX_SIZE, 1> x = A1.inverse() * b1;
    cout << "time use in normal inverse is " << 1000 * (clock() - time_stt) / (double)CLOCKS_PER_SEC << "ms" << endl;
    //cout << x << endl;
    // QR分解colPivHouseholderQr()
    //time_stt = clock();
    x = A1.colPivHouseholderQr().solve(b1);
    cout << "time use in Qr decomposition is " << 1000 * (clock() - time_stt) / (double)CLOCKS_PER_SEC << "ms" << endl;
    //cout << x << endl;
    //QR分解fullPivHouseholderQr()
    //time_stt = clock();
    //x = A1.fullPivHouseholderQr().solve(b1);
    //cout << "time use in Qr decomposition is " << 1000 * (clock() - time_stt) / (double)CLOCKS_PER_SEC << "ms" << endl;
    //cout << x << endl;
    //llt分解 要求矩阵A正定
    time_stt = clock();
    x = A1.llt().solve(b1);
    cout <<"time use in llt decomposition is " <<1000*(clock() - time_stt)/(double)CLOCKS_PER_SEC <<"ms" << endl;
    cout <<x<<endl;
    //ldlt分解  要求矩阵A正或负半定
    time_stt = clock();
    x = A1.ldlt().solve(b1);
    cout <<"time use in ldlt decomposition is " <<1000*(clock() - time_stt)/(double)CLOCKS_PER_SEC <<"ms" << endl;
    cout <<x<<endl;
    //lu分解 partialPivLu()
    time_stt = clock();
    x = A1.partialPivLu().solve(b1);
    cout << "time use in lu decomposition is " << 1000 * (clock() - time_stt) / (double)CLOCKS_PER_SEC << "ms" << endl;
    //cout << x << endl;
    //lu分解（fullPivLu()
    // = clock();
    //x = A1.fullPivLu().solve(b1);
    //cout << "time use in lu decomposition is " << 1000 * (clock() - time_stt) / (double)CLOCKS_PER_SEC << "ms" << endl;
    //cout << x << endl;

    //x = A1.bdcSvd(ComputeThinU | ComputeThinV).solve(b1);
    cout << "time use in svd is " << 1000 * (clock() - time_stt) / (double)CLOCKS_PER_SEC << "ms" << endl;
    //cout << x << endl;
    return 0;

}
*/
// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧:
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件，或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来，若要再次打开此项目，请转到“文件”>“打开”>“项目”并选择 .sln 文件


#include "EdgeOperations2d.h"

#include <wmtk/TriMesh.h>
#include <wmtk/utils/VectorUtils.h>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <wmtk/ExecutionScheduler.hpp>

using namespace Edge2d;
using namespace wmtk;

auto renew = [](auto& m, auto op, auto& tris) {
    auto edges = m.new_edges_after(tris);
    auto optup = std::vector<std::pair<std::string, TriMesh::Tuple>>();
    for (auto& e : edges) optup.emplace_back(op, e);
    return optup;
};

auto edge_locker = [](auto& m, const auto& e, int task_id) -> bool {
    return m.try_set_edge_mutex_two_ring(e, task_id);
};

double EdgeOperations2d::compute_edge_cost_collapse_ar(const TriMesh::Tuple& t, double L) const
{
    double l = (vertex_attrs[t.vid(*this)].pos - vertex_attrs[t.switch_vertex(*this).vid(*this)].pos).norm();
    if (l < (4. / 5.) * L) return ((4. / 5.) * L - l);
    return -1;
}
double EdgeOperations2d::compute_edge_cost_split_ar(const TriMesh::Tuple& t, double L) const
{
    double l = (vertex_attrs[t.vid(*this)].pos - vertex_attrs[t.switch_vertex(*this).vid(*this)].pos).norm();
    if (l > (4. / 3.) * L) return (l - (4. / 3.) * L);
    return -1;
}

double EdgeOperations2d::compute_vertex_valence_ar(const TriMesh::Tuple& t) const
{
    std::vector<std::pair<TriMesh::Tuple, int>> valences(3);
    valences[0] = std::make_pair(t, get_one_ring_tris_for_vertex(t).size());
    auto t2 = t.switch_vertex(*this);
    valences[1] = std::make_pair(t2, get_one_ring_tris_for_vertex(t2).size());
    auto t3 = (t.switch_edge(*this)).switch_vertex(*this);
    valences[2] = std::make_pair(t3, get_one_ring_tris_for_vertex(t3).size());

    if ((t.switch_face(*this)).has_value()) {
        auto t4 = (((t.switch_face(*this)).value()).switch_edge(*this)).switch_vertex(*this);
        valences.emplace_back(t4, get_one_ring_tris_for_vertex(t4).size());
    }
    double cost_before_swap = 0.0;
    double cost_after_swap = 0.0;

    // check if it's internal vertex or bondary vertex
    // navigating starting one edge and getting back to the start

    for (int i = 0; i < valences.size(); i++) {
        TriMesh::Tuple vert = valences[i].first;
        int val = 6;
        auto one_ring_edges = get_one_ring_edges_for_vertex(vert);
        for (auto edge : one_ring_edges) {
            if (is_boundary_edge(edge)) {
                val = 4;
                break;
            }
        }
        cost_before_swap += (double)(valences[i].second - val) * (valences[i].second - val);
        cost_after_swap +=
            (i < 2) ? (double)(valences[i].second - 1 - val) * (valences[i].second - 1 - val)
                    : (double)(valences[i].second + 1 - val) * (valences[i].second + 1 - val);
    }
    return (cost_before_swap - cost_after_swap);
}

std::vector<double> EdgeOperations2d::average_len_valen()
{
    double average_len = 0.0;
    double average_valen = 0.0;
    auto edges = get_edges();
    auto verts = get_vertices();
    double maxlen = std::numeric_limits<double>::min();
    double maxval = std::numeric_limits<double>::min();
    double minlen = std::numeric_limits<double>::max();
    double minval = std::numeric_limits<double>::max();
    for (auto& loc : edges) {
        double currentlen =
            (vertex_attrs[loc.vid(*this)].pos - vertex_attrs[loc.switch_vertex(*this).vid(*this)].pos).norm();
        average_len += currentlen;
        if (maxlen < currentlen) maxlen = currentlen;
        if (minlen > currentlen) minlen = currentlen;
    }
    average_len /= edges.size();
    for (auto& loc : verts) {
        double currentval = get_one_ring_edges_for_vertex(loc).size();
        average_valen += currentval;
        if (maxval < currentval) maxval = currentval;
        if (minval > currentval) minval = currentval;
    }
    average_valen /= verts.size();
    int cnt = 0;
    std::vector<double> rtn{average_len, maxlen, minlen, average_valen, maxval, minval};
    return rtn;
}

std::vector<TriMesh::Tuple> Edge2d::EdgeOperations2d::new_edges_after_swap(
    const TriMesh::Tuple& t) const
{
    std::vector<TriMesh::Tuple> new_edges;
    std::vector<size_t> one_ring_fid;

    new_edges.push_back(t.switch_edge(*this));
    new_edges.push_back((t.switch_face(*this).value()).switch_edge(*this));
    new_edges.push_back((t.switch_vertex(*this)).switch_edge(*this));
    new_edges.push_back(((t.switch_vertex(*this)).switch_face(*this).value()).switch_edge(*this));
    return new_edges;
}

bool EdgeOperations2d::collapse_remeshing(double L)
{
    auto collect_all_ops = std::vector<std::pair<std::string, Tuple>>();
    for (auto& loc : get_edges()) collect_all_ops.emplace_back("edge_collapse", loc);

    auto setup_and_execute = [&](auto executor) {
        executor.renew_neighbor_tuples = renew;
        executor.priority = [&](auto& m, auto _, auto& e) {
            return -m.compute_edge_cost_collapse_ar(e, L);
        };
        executor.lock_vertices = edge_locker;

        executor.is_weight_up_to_date = [](auto& m, auto& ele) {
            auto& [val, op, e] = ele;
            if (val > 0) return false; // priority is negated.
            return true;
        };
        executor(*this, collect_all_ops);
    };
    if (NUM_THREADS > 0) {
        auto executor = wmtk::ExecutePass<EdgeOperations2d, ExecutionPolicy::kSeq>();
        setup_and_execute(executor);
    } else {
        auto executor = wmtk::ExecutePass<EdgeOperations2d, ExecutionPolicy::kPartition>();
        setup_and_execute(executor);
    }

    return true;
}
bool EdgeOperations2d::split_remeshing(double L)
{
    auto collect_all_ops = std::vector<std::pair<std::string, Tuple>>();
    for (auto& loc : get_edges()) collect_all_ops.emplace_back("edge_split", loc);

    auto setup_and_execute = [&](auto executor) {
        executor.num_threads = NUM_THREADS;

        executor.lock_vertices = edge_locker;

        executor.renew_neighbor_tuples = renew;
        executor.priority = [&](auto& m, auto _, auto& e) {
            return m.compute_edge_cost_split_ar(e, L);
        };
        executor.is_weight_up_to_date = [](auto& m, auto& ele) {
            auto& [val, op, e] = ele;
            if (val < 0) return false;
            return true;
        };
        executor(*this, collect_all_ops);
    };
    if (NUM_THREADS > 0) {
        auto executor = wmtk::ExecutePass<EdgeOperations2d, ExecutionPolicy::kSeq>();
        setup_and_execute(executor);
    } else {
        auto executor = wmtk::ExecutePass<EdgeOperations2d, ExecutionPolicy::kPartition>();
        setup_and_execute(executor);
    }

    return true;
}

bool EdgeOperations2d::swap_remeshing()
{
    auto collect_all_ops = std::vector<std::pair<std::string, Tuple>>();
    for (auto& loc : get_edges()) collect_all_ops.emplace_back("edge_swap", loc);

    auto setup_and_execute = [&](auto executor) {
        executor.renew_neighbor_tuples = renew;
        executor.priority = [](auto& m, auto op, const Tuple& e) {
            return m.compute_vertex_valence_ar(e);
        };
        executor.lock_vertices = edge_locker;
        executor.is_weight_up_to_date = [](auto& m, auto& ele) {
            auto& [val, _, e] = ele;
            auto val_energy = (m.compute_vertex_valence_ar(e));
            return (val_energy > 1e-5);
        };
        executor(*this, collect_all_ops);
    };
    if (NUM_THREADS > 0) {
        auto executor = wmtk::ExecutePass<EdgeOperations2d, ExecutionPolicy::kSeq>();
        setup_and_execute(executor);
    } else {
        auto executor = wmtk::ExecutePass<EdgeOperations2d, ExecutionPolicy::kPartition>();
        setup_and_execute(executor);
    }

    return true;
}
double area(EdgeOperations2d& m, std::array<TriMesh::Tuple, 3>& verts)
{
    return ((m.vertex_attrs[verts[0].vid(m)].pos - m.vertex_attrs[verts[2].vid(m)].pos)
                .cross(m.vertex_attrs[verts[1].vid(m)].pos - m.vertex_attrs[verts[2].vid(m)].pos))
               .norm() /
           2.0;
};

Eigen::Vector3d normal(EdgeOperations2d& m, std::array<TriMesh::Tuple, 3>& verts)
{
    return ((m.vertex_attrs[verts[0].vid(m)].pos - m.vertex_attrs[verts[2].vid(m)].pos)
                .cross(m.vertex_attrs[verts[1].vid(m)].pos - m.vertex_attrs[verts[2].vid(m)].pos))
        .normalized();
}

Eigen::Vector3d EdgeOperations2d::tangential_smooth(const Tuple& t)
{
    auto one_ring_tris = get_one_ring_tris_for_vertex(t);
    if (one_ring_tris.size() < 2) return vertex_attrs[t.vid(*this)].pos;
    Eigen::Vector3d after_smooth = smooth(t);
    // get normal and area of each face
    auto area = [](auto& m, auto& verts) {
        return ((m.vertex_attrs[verts[0].vid(m)].pos - m.vertex_attrs[verts[2].vid(m)].pos)
                    .cross(m.vertex_attrs[verts[1].vid(m)].pos - m.vertex_attrs[verts[2].vid(m)].pos))
                   .norm() /
               2.0;
    };
    auto normal = [](auto& m, auto& verts) {
        return ((m.vertex_attrs[verts[0].vid(m)].pos - m.vertex_attrs[verts[2].vid(m)].pos)
                    .cross(m.vertex_attrs[verts[1].vid(m)].pos - m.vertex_attrs[verts[2].vid(m)].pos))
            .normalized();
    };
    auto w0 = 0.0;
    Eigen::Vector3d n0(0.0, 0.0, 0.0);
    for (auto& e : one_ring_tris) {
        auto verts = oriented_tri_vertices(e);
        w0 += area(*this, verts);
        n0 += area(*this, verts) * normal(*this, verts);
    }
    n0 /= w0;
    after_smooth += n0 * n0.transpose() * (vertex_attrs[t.vid(*this)].pos - after_smooth);
    return after_smooth;
}

bool EdgeOperations2d::adaptive_remeshing(double L, int iterations, int sm)
{
    std::vector<double> avg_lens, max_lens, min_lens;
    std::vector<double> avg_valens, max_vals, min_vals;
    int cnt = 0;
    auto properties = average_len_valen();
    while ((properties[0] - L) * (properties[0] - L) > 1e-8 && cnt < iterations) {
        cnt++;
        avg_lens.push_back(properties[0]);
        avg_valens.push_back(properties[3]);
        max_lens.push_back(properties[1]);
        max_vals.push_back(properties[4]);
        min_lens.push_back(properties[2]);
        min_vals.push_back(properties[5]);

        // split
        split_remeshing(L);
        // collpase
        collapse_remeshing(L);

        // swap edges
        swap_remeshing();
        // smoothing
        auto vertices = get_vertices();
        if (sm == 0) {
            for (auto& loc : vertices) vertex_attrs[loc.vid(*this)].pos = smooth(loc);
        } else
            for (auto& loc : vertices) vertex_attrs[loc.vid(*this)].pos = tangential_smooth(loc);

        assert(check_mesh_connectivity_validity());
        consolidate_mesh();
        properties = average_len_valen();
    }
    wmtk::logger().info("avg edge len after each remesh is: ");
    wmtk::vector_print(avg_lens);
    wmtk::logger().info("max edge len after each remesh is: ");
    wmtk::vector_print(max_lens);
    wmtk::logger().info("min edge len after each remesh is: ");
    wmtk::vector_print(min_lens);

    wmtk::logger().info("avg valence after each remesh is: ");
    wmtk::vector_print(avg_valens);
    wmtk::logger().info("max valence after each remesh is: ");
    wmtk::vector_print(max_vals);
    wmtk::logger().info("min valence after each remesh is: ");
    wmtk::vector_print(min_vals);
    return true;
}


#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Constrained_Delaunay_triangulation_2.h>
#include <CGAL/Triangulation_vertex_base_with_info_2.h>
#include <CGAL/point_generators_2.h>
#include <CGAL/algorithm.h>
#include <CGAL/Random.h>
#include <CGAL/Timer.h>

#include <boost/iterator/transform_iterator.hpp>
#include <boost/random.hpp>

#include <cassert>
#include <iostream>

typedef CGAL::Exact_predicates_inexact_constructions_kernel K;

typedef CGAL::Triangulation_vertex_base_2<K>                     Vb;
typedef CGAL::Constrained_triangulation_face_base_2<K>           Fb;
typedef CGAL::Triangulation_data_structure_2<Vb,Fb>              TDS;
typedef CGAL::Exact_predicates_tag                               Itag;
typedef CGAL::Constrained_Delaunay_triangulation_2<K, TDS, Itag> CDT;
typedef CDT::Point          Point;

typedef CGAL::Creator_uniform_2<double, Point>             Creator;
typedef CGAL::Random_points_in_square_2<Point, Creator> Point_generator;

int main(int argc,char** argv )
{
  int n_segments=100000;
  if (argc==2) n_segments=atoi(argv[1]);

  CGAL::Random rand(0);
  std::vector<Point> point_set;
  point_set.reserve(2*n_segments);

  CGAL::cpp11::copy_n(Point_generator(1,rand), 2*n_segments,std::back_inserter(point_set));

  std::cout << point_set.size()/2  << " segments" << std::endl;

  CDT cdt;

  CGAL::Timer time;
  time.start();
  cdt.insert( point_set.begin(),point_set.end() );
  time.stop();

  std::cout << "Inserting points in " << time.time() << std::endl;
  time.reset();

  std::vector<CDT::Vertex_handle> vertex_handles;
  vertex_handles.reserve(2*n_segments);

  for (CDT::Finite_vertices_iterator vit=cdt.finite_vertices_begin(),
                                     vit_end=cdt.finite_vertices_end();vit!=vit_end;++vit)
  {
    vertex_handles.push_back(vit);
  }

  boost::rand48 random;
  boost::random_number_generator<boost::rand48> rng(random);
  std::random_shuffle(vertex_handles.begin(),vertex_handles.end(),rng);

  time.start();
  for (int i=0;i<n_segments;++i){
    cdt.insert_constraint( vertex_handles[2*i],vertex_handles[2*i+1] );
  }
  time.stop();

  std::cout << "Adding constraints in " << time.time() << std::endl;

  std::cout << cdt.number_of_vertices()-static_cast<unsigned>(2*n_segments) << " intersection points\n";

  return 0;
}


#include <iostream>
#include <cmath>
#include <vector>
#include <array>
#include <math.h>
#include "AdaptiveHeat.hpp"
#include "StiffnessMatrix.hpp"
#include <fstream>
#include <string>
#include <boost/math/quadrature/gauss.hpp>
using namespace std;
using namespace boost::math::quadrature;

const double M_PI = 2*acos(0);

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

SpaceMesh smesh;
smesh.GenerateSpaceMesh({0, 0.15,  0.25, 0.5, 1.0});
//smesh.GloballyBisectSpaceMesh();

smesh.PrintSpaceNodes();
std::cout<<smesh.meshsize() <<"\n";
std::cout<<"\n";

TimeMesh tmesh;
tmesh.GenerateUniformTimeMesh(pow(smesh.meshsize(), 2), 1.0);

AdaptiveHeatEquation adaptiveheat;
adaptiveheat.SetSpaceTimeMesh( smesh, tmesh, "soultion1.txt");
adaptiveheat.AdaptiveSolver();

//adaptiveheat.Solve();

//adaptiveheat.PrintSolution();

//adaptiveheat.PrintErrorMesh();
adaptiveheat.PrintSolution();

}

Sover class with method to adapt in space and time/driver.cpp


#include <bits/types/FILE.h>
#include <fstream>
#include <iostream>
#include <stdio.h>
#include <vector>
#include <string>
#include <tgmath.h>
#include "image_ppm.h"
#include <filesystem>
#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

typedef Matrix<u_char, Dynamic, Dynamic> MatrixImg;
typedef Matrix<double, Dynamic, Dynamic> TempMatrixImg;
typedef Vector<u_char, Dynamic> ImgLine;
typedef Matrix<ImgLine, Dynamic, Dynamic> FlattenedImages;

unsigned char max(u_char a, u_char b)
{
    if (a < b)
        return b;
    else
        return a;
}

unsigned char min(u_char a, u_char b)
{
    if (a > b)
        return b;
    else
        return a;
}

double max(double a, double b)
{
    if (a < b)
        return b;
    else
        return a;
}

double min(double a, double b)
{
    if (a > b)
        return b;
    else
        return a;
}

vector<double> projectOnEigenSpace(EigenSolver<TempMatrixImg> &solver, TempMatrixImg &img, int K)
{
    vector<double> res;
    for (int i = 0; i < K; i++)
    {
        double test = ((solver.eigenvectors().col(i).real().transpose()) * img)(0);
        res.push_back(test);
    }
    return res;
}

void writeEigenfaces(EigenSolver<TempMatrixImg> &covMat, MatrixImg &A, int nbrFaces)
{
    // covMat.eigenvalues.
}

void makeEigenSpace(std::string dirIn, std::string dirOut)
{
    int nH, nW;
    vector<OCTET *> set = vector<OCTET *>();

    OCTET *meanImg;
    long long *tempMean;

    long totNumberIm = 0;
    for (auto &file : std::filesystem::directory_iterator(dirIn))
    {
        if (totNumberIm == 0)
        {
            cout << file.path().c_str() << endl;
            lire_nb_lignes_colonnes_image_pgm(file.path().c_str(), &nH, &nW);
            allocation_tableau(meanImg, OCTET, nH * nW);
            tempMean = (long long *)calloc(nH * nW, sizeof(long long));
        }
        // OCTET* im; allocation_tableau(im,OCTET,nH*nW);
        OCTET *im;
        set.push_back(im);
        allocation_tableau(set[totNumberIm], OCTET, nH * nW);
        lire_image_pgm(file.path().c_str(), set[totNumberIm], nH * nW);

        for (int i = 0; i < nH * nW; i++)
        {
            tempMean[i] += set[totNumberIm][i];
        }

        totNumberIm++;
    }

    for (int i = 0; i < nH * nW; i++)
    {
        meanImg[i] = (unsigned char)((double)tempMean[i] / ((double)totNumberIm));
    }
    ecrire_image_pgm(string(dirOut + string("/mean.pgm")).c_str(), meanImg, nH, nW);

    TempMatrixImg bigMatNoMean(nH * nW, totNumberIm);
    for (int i = 0; i < totNumberIm; i++)
    {
        for (int pix = 0; pix < nW * nH; pix++)
        {
            bigMatNoMean(pix, i) = set[i][pix] - meanImg[pix] + 128;
        }
    }

    TempMatrixImg cov = bigMatNoMean.transpose() * bigMatNoMean;
    EigenSolver<TempMatrixImg> eigensolver(cov);

    for (int i = 0; i < eigensolver.eigenvectors().cols(); i++)
    {
        OCTET *im;
        allocation_tableau(im, OCTET, nH * nW);
        TempMatrixImg face = bigMatNoMean * eigensolver.eigenvectors().col(i).real();
        double minD = face(0);
        double maxD = face(0);
        for (int pix = 1; pix < nH * nW; pix++)
        {
            minD = min(minD, face(pix));
            maxD = max(maxD, face(pix));
        }
        for (int pix = 0; pix < nH * nW; pix++)
        {
            // cout<<(face(pix)-minD)*(255.0/(maxD-minD))<<endl;
            im[pix] = (unsigned char)((face(pix) - minD) * (255.0 / (maxD - minD))); // comment fit dans 0 255 ?
        }
        char name[50];
        sprintf(name, "/eigenfaces/im%d.pgm", i);
        ecrire_image_pgm(string(dirOut + string(name)).c_str(), im, nH, nW);
    }
}

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

    std::string nom = string("in");
    string dirIn = string(argv[1]);
    string dirOut = string(argv[2]);
    makeEigenSpace(dirIn,dirOut);

    //      int nH,nW;
    //
    // //     vector<vector<OCTET*>> imageSet = vector<vector<OCTET*>>();
    // //     for (int i=0; i<40;i++){
    // //         imageSet.push_back(vector<OCTET*>());
    // //         for (int j=0;j<10;j++){
    // //             OCTET* im;
    // //             imageSet[i].push_back(im);
    // //         }
    // //     }

    // //     OCTET* meanImg;
    // //     long long* tempMean ;
    // //     int countDirs = 0;
    // //     long totalNumberOfImages=0;
    // int i=0;int j=0;
    //     for (auto & dir : std::filesystem::directory_iterator(nom)){
    //         int countFile=0;
    //         for (auto & file : std::filesystem::directory_iterator(dir.path())){
    //             OCTET* img;
    //             lire_nb_lignes_colonnes_image_pgm(file.path().c_str(),&nH,&nW);
    //             allocation_tableau(img,OCTET,nH*nW);
    //             lire_image_pgm(file.path().c_str(),img,nH*nW);
    //             char name[50];
    //             sprintf(name,"in/%d%d.pgm",i,j);
    //             ecrire_image_pgm(name,img,nH,nW);

    // allocation_tableau(imageSet[countDirs][countFile],OCTET,nH*nW);
    // for (int pix = 0;pix<nH*nW;pix++){
    //     imageSet[countDirs][countFile][pix]=img[pix];
    // }
    // //imageSet[countDirs].push_back(img);
    // if (countDirs==0 && countFile==0){
    //     allocation_tableau(meanImg,OCTET,nH*nW);
    //     tempMean=(long long*)calloc(nH*nW,sizeof(long long));
    // }

    // for (int i=0;i<nH*nW;i++){
    //     tempMean[i]+=img[i];
    // }

    // totalNumberOfImages++;
    // countFile++;

    // //free(img);
    //         j++;
    //     }
    //     i++;

    // }

    //     allocation_tableau(meanImg,OCTET,nH*nW);
    //     for (int i=0;i<nH*nW;i++){
    //         meanImg[i]=(unsigned char)(tempMean[i]/totalNumberOfImages);
    //     }

    //     ecrire_image_pgm("MEAN.pgm",meanImg,nH,nW);

    //     vector<vector<OCTET*>> imagesSansMean = vector<vector<OCTET*>>();
    //     vector<vector<TempMatrixImg>> matrixNoMean = vector<vector<TempMatrixImg>>();

    //     for (int i=0;i<imageSet.size();i++){
    //         imagesSansMean.push_back(vector<OCTET*>());
    //         matrixNoMean.push_back(vector<TempMatrixImg>());
    //         for (int j=0; j<imageSet[i].size();j++){
    //             OCTET* img;
    //             TempMatrixImg mat(nH,nW);
    //             imagesSansMean[i].push_back(img);
    //             allocation_tableau(imagesSansMean[i][j],OCTET,nH*nW);

    //             allocation_tableau(img,OCTET,nH*nW);
    //             for (int pix=0;pix<nH*nW;pix++){
    //                 img[pix]=min(255,max(0,imageSet[i][j][pix]+128-meanImg[pix]));
    //                 imagesSansMean[i][j][pix]=img[pix];

    //             }
    //             for (int x=0;x<nH;x++){
    //                 for (int y=0; y<nW;y++){
    //                     mat(x,y)=img[x*nW+y];
    //                 }
    //             }
    //             matrixNoMean[i].push_back(mat);

    //             //uncomment to write images

    //             //char name[50];
    //             //sprintf(name,"meanless/im%d_%d",i,j);
    //             //ecrire_image_pgm(name,img,nH,nW);
    //         }
    //     }

    //    TempMatrixImg bigMatNoMean(nH*nW,totalNumberOfImages);
    //     for (int i=0;i<imageSet.size();i++){
    //         for (int j=0; j<imageSet[i].size();j++){
    //             for (int pix=0;pix<nH*nW;pix++){
    //                 bigMatNoMean(pix,i*imageSet[0].size()+j)=(double)imagesSansMean[i][j][pix];
    //             }
    //         }
    //     }

    //     TempMatrixImg cov = bigMatNoMean.transpose()*bigMatNoMean;

    //     EigenSolver<TempMatrixImg> eigensolver(cov);
    //     //cout<<eigensolver.eigenvalues()<<endl;

    //     for (int i=0;i<eigensolver.eigenvectors().cols();i++){
    //         OCTET* im; allocation_tableau(im,OCTET,nH*nW);
    //         TempMatrixImg face = bigMatNoMean*eigensolver.eigenvectors().col(i).real();
    //         double minD=face(0);double maxD=face(0);
    //         for (int pix =1; pix<nH*nW;pix++){minD=min(minD,face(pix));maxD=max(maxD,face(pix));}
    //         for (int pix=0;pix<nH*nW;pix++){
    //             //cout<<(face(pix)-minD)*(255.0/(maxD-minD))<<endl;
    //             im[pix]=(unsigned char)((face(pix)-minD)*(255.0/(maxD-minD)));     // comment fit dans 0 255 ?
    //         }
    //         char name[50];
    //         sprintf(name,"eigenfaces/im%d.pgm",i);
    //         ecrire_image_pgm(name,im,nH,nW);

    //     }

    // OCTET* felix; allocation_tableau(felix,OCTET,nH*nW);
    // lire_image_pgm("felixResized.pgm",felix,nH*nW);

    // TempMatrixImg fix(nH,nW) ;
    // for (int i=0;i<nH;i++){
    //     for (int j=0; j<nW;j++){
    //         fix(i,j)=(double)felix[i*nW+j] - meanImg[i*nW+j];
    //     }
    // }

    // vector<double> proj ; proj = projectOnEigenSpace(eigensolver, fix, 40);
    // for (int i=0; i<40; i++){
    //     cout<<proj[i];
    // }
    // cout<<endl;
}