// Copyright (c) 2017 Graphcore Ltd. All rights reserved.
#include "poplibs_support/logging.hpp"
#include <boost/multi_array.hpp>
#include <cassert>
#include <poplibs_test/GeneralMatrixAdd.hpp>
#include <poplibs_test/GeneralMatrixMultiply.hpp>
#include <poplibs_test/Lstm.hpp>
#include <poplibs_test/NonLinearity.hpp>
#include <unordered_map>

// Fwd state array indices
#define LSTM_FWD_STATE_FORGET_GATE 2
#define LSTM_FWD_STATE_CAND_TANH 3
#define LSTM_FWD_STATE_INPUT_GATE 4
#define LSTM_FWD_STATE_OUTPUT_GATE 5
#define LSTM_FWD_STATE_OUTPUT_TANH 6

using IndexRange = boost::multi_array_types::index_range;
using Array1dRef = boost::multi_array_ref<double, 1>;
using Array1dRefUNSIGNED = boost::multi_array_ref<unsigned, 1>;
using Array2dRef = boost::multi_array_ref<double, 2>;
using Array2d = boost::multi_array<double, 2>;
using Array3dRef = boost::multi_array_ref<double, 3>;
using Array4dRef = boost::multi_array_ref<double, 4>;
using Array3d = boost::multi_array<double, 3>;

using namespace poplibs_support;
using namespace poplibs_test;

static void matrixZero(boost::multi_array_ref<double, 2> matA) {
  std::fill(matA.data(), matA.data() + matA.num_elements(), 0.0);
}

/**
 * Process a given unit type within an LSTM given its weights and biases.
 * The non-linearity is also specified although it may be derived from the unit
 */
static void processBasicLstmUnit(const Array2dRef prevOutput,
                                 const Array2dRef input,
                                 const Array3dRef weightsInput,
                                 const Array3dRef weightsOutput,
                                 const Array2dRef biases, Array2dRef output,
                                 unsigned lstmUnitOffset,
                                 popnn::NonLinearityType nonLinearityType) {
  const auto batchSize = prevOutput.shape()[0];
  const auto outputSize = prevOutput.shape()[1];

  /* split weight into two parts:
   * 1) part which weighs only the previous output
   * 2) part which weighs only the input
   */
  Array2d weightsOutputUnit = weightsOutput[lstmUnitOffset];
  Array2d weightsInputUnit = weightsInput[lstmUnitOffset];

  gemm::generalMatrixMultiply(prevOutput, weightsOutputUnit, output, output,
                              1.0, 0, false, false);
  gemm::generalMatrixMultiply(input, weightsInputUnit, output, output, 1.0, 1.0,
                              false, false);
  /* add bias */
  for (auto b = 0U; b != batchSize; ++b) {
    for (auto i = 0U; i != outputSize; ++i) {
      output[b][i] += biases[lstmUnitOffset][i];
    }
  }

  /* apply non-linearity */
  nonLinearity(nonLinearityType, output);
}

/**
 * Apply mask to gates.
 */
static void applySeqMask(const boost::optional<Array1dRefUNSIGNED> &timeSteps,
                         const unsigned step, Array2dRef ionput) {
  if (timeSteps) {
    const auto batchSize = ionput.shape()[0];
    const auto outputSize = ionput.shape()[1];
    for (auto b = 0U; b != batchSize; ++b) {
      for (auto i = 0U; i != outputSize; ++i) {
        auto limit = (*timeSteps)[timeSteps->size() > 1 ? b : 0];
        if (step >= limit) {
          ionput[b][i] = 0;
        }
      }
    }
  }
}

/**
 * Update output for batches that have not reached iteration limit
 */
static void
copyIfStepWithinRange(const boost::optional<Array1dRefUNSIGNED> &timeSteps,
                      const unsigned step,
                      const boost::optional<Array2dRef> &current,
                      const Array2dRef update, Array2dRef dst) {
  const auto batchSize = dst.shape()[0];
  const auto outputSize = dst.shape()[1];
  for (auto b = 0U; b != batchSize; ++b) {
    for (auto i = 0U; i != outputSize; ++i) {
      auto limit = (*timeSteps)[(timeSteps->size() > 1) ? b : 0];
      if (step < limit) {
        dst[b][i] = update[b][i];
      } else if (current) {
        dst[b][i] = (*current)[b][i];
      }
    }
  }
}

static void
copyIfStepWithinRange(const boost::optional<Array1dRefUNSIGNED> &timeSteps,
                      const unsigned step, const Array2dRef update,
                      Array2dRef dst) {
  copyIfStepWithinRange(timeSteps, step, {}, update, dst);
}

static std::unordered_map<BasicLstmCellUnit, unsigned>
getCellMapping(const std::vector<BasicLstmCellUnit> &cellOrder) {
  // build a mapping of the order that the gates are stored in.
  std::unordered_map<BasicLstmCellUnit, unsigned> cellMapping;
  for (unsigned i = 0; i < cellOrder.size(); ++i) {
    auto gate = cellOrder.at(i);
    cellMapping.insert(std::make_pair(gate, i));
  }

  return cellMapping;
}

void poplibs_test::lstm::basicLstmCellForwardPass(
    const Array3dRef input, const Array2dRef biases,
    const Array2dRef prevOutput, const Array3dRef weightsInput,
    const Array3dRef weightsOutput,
    const boost::optional<Array1dRefUNSIGNED> &timeSteps,
    Array2dRef prevCellState, Array4dRef state, Array2dRef lastOutput,
    Array2dRef lastCellState, const std::vector<BasicLstmCellUnit> &cellOrder,
    const popnn::NonLinearityType activation,
    const popnn::NonLinearityType recurrentActivation) {
  const auto sequenceSize = state.shape()[1];
  const auto batchSize = state.shape()[2];
  const auto outputSize = state.shape()[3];
#ifndef NDEBUG
  const auto inputSize = input.shape()[2];
#endif
  assert(state.shape()[0] == LSTM_NUM_FWD_STATES);
  assert(weightsInput.shape()[0] == BASIC_LSTM_CELL_NUM_UNITS);
  assert(weightsInput.shape()[1] == inputSize);
  assert(weightsInput.shape()[2] == outputSize);
  assert(weightsOutput.shape()[0] == BASIC_LSTM_CELL_NUM_UNITS);
  assert(weightsOutput.shape()[1] == outputSize);
  assert(weightsOutput.shape()[2] == outputSize);
  assert(prevCellState.shape()[0] == batchSize);
  assert(prevCellState.shape()[1] == outputSize);
  assert(biases.shape()[0] == BASIC_LSTM_CELL_NUM_UNITS);
  assert(biases.shape()[1] == outputSize);
  assert(prevOutput.shape()[0] == batchSize);
  assert(prevOutput.shape()[1] == outputSize);

  auto cellMapping = getCellMapping(cellOrder);

  Array2d nextOutput = prevOutput;
  Array2d nextCellState = prevCellState;
  for (auto s = 0U; s != sequenceSize; ++s) {
    Array2d inputThisStep = input[s];

    auto cellState = nextCellState;

    /* forget gate */
    Array2d forgetGate(boost::extents[batchSize][outputSize]);
    processBasicLstmUnit(nextOutput, inputThisStep, weightsInput, weightsOutput,
                         biases, forgetGate,
                         cellMapping.at(BASIC_LSTM_CELL_FORGET_GATE),
                         recurrentActivation);
    applySeqMask(timeSteps, s, forgetGate);
    state[LSTM_FWD_STATE_FORGET_GATE][s] = forgetGate;

    /* input gate */
    Array2d inputGate(boost::extents[batchSize][outputSize]);
    processBasicLstmUnit(nextOutput, inputThisStep, weightsInput, weightsOutput,
                         biases, inputGate,
                         cellMapping.at(BASIC_LSTM_CELL_INPUT_GATE),
                         recurrentActivation);
    applySeqMask(timeSteps, s, inputGate);
    state[LSTM_FWD_STATE_INPUT_GATE][s] = inputGate;

    /* new candidate contribution to this cell */
    Array2d candidate(boost::extents[batchSize][outputSize]);
    processBasicLstmUnit(nextOutput, inputThisStep, weightsInput, weightsOutput,
                         biases, candidate,
                         cellMapping.at(BASIC_LSTM_CELL_CANDIDATE), activation);
    applySeqMask(timeSteps, s, candidate);
    state[LSTM_FWD_STATE_CAND_TANH][s] = candidate;

    /* output gate */
    Array2d outputGate(boost::extents[batchSize][outputSize]);
    processBasicLstmUnit(nextOutput, inputThisStep, weightsInput, weightsOutput,
                         biases, outputGate,
                         cellMapping.at(BASIC_LSTM_CELL_OUTPUT_GATE),
                         recurrentActivation);
    applySeqMask(timeSteps, s, outputGate);
    state[LSTM_FWD_STATE_OUTPUT_GATE][s] = outputGate;

    poplibs_test::gemm::hadamardProduct(forgetGate, cellState, cellState);
    poplibs_test::gemm::hadamardProduct(inputGate, candidate, candidate);
    poplibs_test::axpby::add(cellState, candidate, cellState);

    /* need to maintain the cell state for next step */
    Array2d outputThisStep = cellState;
    nonLinearity(activation, outputThisStep);
    state[LSTM_FWD_STATE_OUTPUT_TANH][s] = outputThisStep;
    gemm::hadamardProduct(outputThisStep, outputGate, outputThisStep);

    if (timeSteps) {
      copyIfStepWithinRange(timeSteps, s, outputThisStep, nextOutput);
      copyIfStepWithinRange(timeSteps, s, cellState, nextCellState);
    } else {
      nextOutput = outputThisStep;
      nextCellState = cellState;
    }
    state[LSTM_FWD_STATE_ACTS_IDX][s] = outputThisStep;
    state[LSTM_FWD_STATE_CELL_STATE_IDX][s] = cellState;
  }

  // Save final state
  lastOutput = nextOutput;
  lastCellState = nextCellState;
}

static void computeGradients(const Array2dRef weightIn,
                             const Array2dRef weightPrev, const Array2dRef grad,
                             Array2dRef gradIn, Array2dRef gradPrev, bool acc) {
  double k = acc ? 1.0 : 0.0;
  gemm::generalMatrixMultiply(grad, weightIn, gradIn, gradIn, 1.0, k, false,
                              true);
  gemm::generalMatrixMultiply(grad, weightPrev, gradPrev, gradPrev, 1.0, k,
                              false, true);
}

void poplibs_test::lstm::basicLstmCellBackwardPass(
    bool outputFullSequence, const Array3dRef weightsInput,
    const Array3dRef weightsOutput, const Array3dRef gradsNextLayer,
    const Array2dRef prevCellState, const Array4dRef fwdState,
    const boost::optional<Array2dRef> initOutputGrad,
    const boost::optional<Array2dRef> initCellStateGrad,
    const boost::optional<Array1dRefUNSIGNED> &timeSteps, Array4dRef bwdState,
    Array3dRef gradsPrevLayer, Array2dRef lastGradLayerOut,
    Array2dRef lastGradCellState,
    const std::vector<BasicLstmCellUnit> &cellOrder,
    const popnn::NonLinearityType activation,
    const popnn::NonLinearityType recurrentActivation) {
  const auto sequenceSize = fwdState.shape()[1];
  const auto batchSize = fwdState.shape()[2];
  const auto outputSize = fwdState.shape()[3];
  const auto inputSize = gradsPrevLayer.shape()[2];

  assert(fwdState.shape()[0] == LSTM_NUM_FWD_STATES);
  assert(bwdState.shape()[0] == LSTM_NUM_BWD_STATES);
  assert(weightsInput.shape()[0] == BASIC_LSTM_CELL_NUM_UNITS);
  assert(weightsInput.shape()[1] == inputSize);
  assert(weightsInput.shape()[2] == outputSize);
  assert(weightsOutput.shape()[0] == BASIC_LSTM_CELL_NUM_UNITS);
  assert(weightsOutput.shape()[1] == outputSize);
  assert(weightsOutput.shape()[2] == outputSize);
  assert(prevCellState.shape()[0] == batchSize);
  assert(prevCellState.shape()[1] == outputSize);
  assert(fwdState.shape()[1] == sequenceSize);
  assert(fwdState.shape()[2] == batchSize);
  assert(fwdState.shape()[3] == outputSize);
  assert(bwdState.shape()[1] == sequenceSize);
  assert(bwdState.shape()[2] == batchSize);
  assert(bwdState.shape()[3] == outputSize);
  assert(gradsNextLayer.shape()[0] == sequenceSize);
  assert(gradsNextLayer.shape()[1] == batchSize);
  assert(gradsNextLayer.shape()[2] == outputSize);
  assert(gradsPrevLayer.shape()[0] == sequenceSize);
  assert(gradsPrevLayer.shape()[1] == batchSize);

  auto cellMapping = getCellMapping(cellOrder);

  // gradient of cell state for this step
  Array2d prevGradCellState(boost::extents[batchSize][outputSize]);
  if (initCellStateGrad) {
    prevGradCellState = *initCellStateGrad;
  } else {
    matrixZero(prevGradCellState);
  }

  // gradient of output of this step
  Array2d gradOutput(boost::extents[batchSize][outputSize]);
  if (initOutputGrad) {
    gradOutput = *initOutputGrad;
  } else {
    matrixZero(gradOutput);
  }

  for (auto i = sequenceSize; i != 0; --i) {
    const auto s = i - 1;
    Array2d gradOut(boost::extents[batchSize][outputSize]);
    Array2d sumGradOut(boost::extents[batchSize][outputSize]);

    if (outputFullSequence) {
      gradOut = gradsNextLayer[s];
    } else {
      // Only the last layer receive the gradient
      if (s == sequenceSize - 1)
        gradOut = gradsNextLayer[0];
      else {
        matrixZero(gradOut);
      }
    }
    auto gradCellState = prevGradCellState;
    axpby::add(gradOut, gradOutput, sumGradOut);

    Array2d actOutGate = fwdState[LSTM_FWD_STATE_OUTPUT_GATE][s];
    Array2d gradAtOTanhInp(boost::extents[batchSize][outputSize]);
    gemm::hadamardProduct(actOutGate, sumGradOut, gradAtOTanhInp);

    Array2d actTanhOutGate = fwdState[LSTM_FWD_STATE_OUTPUT_TANH][s];
    Array2d gradAtOutGate(boost::extents[batchSize][outputSize]);
    ;

    gemm::hadamardProduct(actTanhOutGate, sumGradOut, gradAtOutGate);

    bwdNonLinearity(activation, actTanhOutGate, gradAtOTanhInp);

    bwdNonLinearity(recurrentActivation, actOutGate, gradAtOutGate);

    Array2dRef gradAtCellStateSum = gradAtOTanhInp;
    axpby::add(gradAtOTanhInp, gradCellState, gradAtCellStateSum);

    Array2d actInpGate = fwdState[LSTM_FWD_STATE_INPUT_GATE][s];
    Array2d gradAtCand(boost::extents[batchSize][outputSize]);
    ;
    gemm::hadamardProduct(actInpGate, gradAtCellStateSum, gradAtCand);
    Array2d actCand = fwdState[LSTM_FWD_STATE_CAND_TANH][s];
    Array2d gradAtInpGate(boost::extents[batchSize][outputSize]);
    ;
    gemm::hadamardProduct(actCand, gradAtCellStateSum, gradAtInpGate);
    bwdNonLinearity(activation, actCand, gradAtCand);
    bwdNonLinearity(recurrentActivation, actInpGate, gradAtInpGate);

    Array2d actForgetGate = fwdState[LSTM_FWD_STATE_FORGET_GATE][s];
    gemm::hadamardProduct(actForgetGate, gradAtCellStateSum, gradCellState);

    Array2d pCellAct(boost::extents[batchSize][outputSize]);

    if (s == 0) {
      pCellAct = prevCellState;
    } else {
      pCellAct = fwdState[LSTM_FWD_STATE_CELL_STATE_IDX][s - 1];
    }
    Array2d gradAtForgetGate(boost::extents[batchSize][outputSize]);
    ;

    gemm::hadamardProduct(pCellAct, gradAtCellStateSum, gradAtForgetGate);
    bwdNonLinearity(recurrentActivation, actForgetGate, gradAtForgetGate);

    Array2d gradIn(boost::extents[batchSize][inputSize]);
    ;
    Array2d weightsInUnit =
        weightsInput[cellMapping.at(BASIC_LSTM_CELL_FORGET_GATE)];
    Array2d weightsOutUnit =
        weightsOutput[cellMapping.at(BASIC_LSTM_CELL_FORGET_GATE)];
    Array2d nextGradOut(boost::extents[batchSize][outputSize]);
    computeGradients(weightsInUnit, weightsOutUnit, gradAtForgetGate, gradIn,
                     nextGradOut, false);
    weightsInUnit = weightsInput[cellMapping.at(BASIC_LSTM_CELL_INPUT_GATE)];
    weightsOutUnit = weightsOutput[cellMapping.at(BASIC_LSTM_CELL_INPUT_GATE)];
    computeGradients(weightsInUnit, weightsOutUnit, gradAtInpGate, gradIn,
                     nextGradOut, true);
    weightsInUnit = weightsInput[cellMapping.at(BASIC_LSTM_CELL_OUTPUT_GATE)];
    weightsOutUnit = weightsOutput[cellMapping.at(BASIC_LSTM_CELL_OUTPUT_GATE)];
    computeGradients(weightsInUnit, weightsOutUnit, gradAtOutGate, gradIn,
                     nextGradOut, true);
    weightsInUnit = weightsInput[cellMapping.at(BASIC_LSTM_CELL_CANDIDATE)];
    weightsOutUnit = weightsOutput[cellMapping.at(BASIC_LSTM_CELL_CANDIDATE)];
    computeGradients(weightsInUnit, weightsOutUnit, gradAtCand, gradIn,
                     nextGradOut, true);

    if (timeSteps) {
      if (outputFullSequence) {
        copyIfStepWithinRange(timeSteps, s, nextGradOut, gradOutput);
      } else {
        copyIfStepWithinRange(timeSteps, s, sumGradOut, nextGradOut,
                              gradOutput);
      }
      copyIfStepWithinRange(timeSteps, s, gradCellState, prevGradCellState);
    } else {
      gradOutput = nextGradOut;
      prevGradCellState = gradCellState;
    }
    gradsPrevLayer[s] = gradIn;

    // save bwd state for weight update
    bwdState[BASIC_LSTM_CELL_FORGET_GATE][s] = gradAtForgetGate;
    bwdState[BASIC_LSTM_CELL_INPUT_GATE][s] = gradAtInpGate;
    bwdState[BASIC_LSTM_CELL_OUTPUT_GATE][s] = gradAtOutGate;
    bwdState[BASIC_LSTM_CELL_CANDIDATE][s] = gradAtCand;
  }
  lastGradLayerOut = gradOutput;
  lastGradCellState = prevGradCellState;
}

void poplibs_test::lstm::basicLstmCellParamUpdate(
    const Array3dRef prevLayerActs, const Array4dRef fwdState,
    const Array2dRef outputActsInit, const Array4dRef bwdState,
    Array3dRef weightsInputDeltas, Array3dRef weightsOutputDeltas,
    Array2dRef biasDeltas, const std::vector<BasicLstmCellUnit> &cellOrder) {
  const auto sequenceSize = prevLayerActs.shape()[0];
  const auto batchSize = prevLayerActs.shape()[1];
  const auto inputSize = prevLayerActs.shape()[2];
  const auto outputSize = fwdState.shape()[3];

  assert(fwdState.shape()[0] == LSTM_NUM_FWD_STATES);
  assert(fwdState.shape()[1] == sequenceSize);
  assert(fwdState.shape()[2] == batchSize);
  assert(outputActsInit.shape()[0] == batchSize);
  assert(outputActsInit.shape()[1] == outputSize);
  assert(bwdState.shape()[0] == LSTM_NUM_BWD_STATES);
  assert(bwdState.shape()[1] == sequenceSize);
  assert(bwdState.shape()[2] == batchSize);
  assert(bwdState.shape()[3] == outputSize);
  assert(weightsInputDeltas.shape()[0] == BASIC_LSTM_CELL_NUM_UNITS);
  assert(weightsInputDeltas.shape()[1] == inputSize);
  assert(weightsInputDeltas.shape()[2] == outputSize);
  assert(weightsOutputDeltas.shape()[0] == BASIC_LSTM_CELL_NUM_UNITS);
  assert(weightsOutputDeltas.shape()[1] == outputSize);
  assert(weightsOutputDeltas.shape()[2] == outputSize);
  assert(biasDeltas.shape()[0] == BASIC_LSTM_CELL_NUM_UNITS);
  assert(biasDeltas.shape()[1] == outputSize);

  auto cellMapping = getCellMapping(cellOrder);

  for (auto it = weightsInputDeltas.data(),
            end = weightsInputDeltas.data() + weightsInputDeltas.num_elements();
       it != end; ++it) {
    *it = 0;
  }
  for (auto it = weightsOutputDeltas.data(),
            end =
                weightsOutputDeltas.data() + weightsOutputDeltas.num_elements();
       it != end; ++it) {
    *it = 0;
  }
  for (auto it = biasDeltas.data(),
            end = biasDeltas.data() + biasDeltas.num_elements();
       it != end; ++it) {
    *it = 0;
  }

  for (auto i = sequenceSize; i != 0; --i) {
    const auto s = i - 1;
    Array2d outActs(boost::extents[batchSize][outputSize]);
    if (s == 0) {
      outActs = outputActsInit;
    } else {
      outActs = fwdState[LSTM_FWD_STATE_ACTS_IDX][s - 1];
    }
    Array2d inActs = prevLayerActs[s];
    for (auto i = 0; i != BASIC_LSTM_CELL_NUM_UNITS; ++i) {
      const auto unit = static_cast<BasicLstmCellUnit>(i);

      Array2d grad = bwdState[i][s];
      Array2d wInputDeltasUnit(boost::extents[inputSize][outputSize]);

      gemm::generalMatrixMultiply(inActs, grad, wInputDeltasUnit,
                                  wInputDeltasUnit, 1.0, 0, true, false);
      for (auto ic = 0u; ic != inputSize; ++ic) {
        for (auto oc = 0u; oc != outputSize; ++oc) {
          weightsInputDeltas[cellMapping.at(unit)][ic][oc] +=
              wInputDeltasUnit[ic][oc];
        }
      }
      Array2d wOutputDeltasUnit(boost::extents[outputSize][outputSize]);

      gemm::generalMatrixMultiply(outActs, grad, wOutputDeltasUnit,
                                  wOutputDeltasUnit, 1.0, 0, true, false);
      for (auto oc1 = 0u; oc1 != outputSize; ++oc1) {
        for (auto oc2 = 0u; oc2 != outputSize; ++oc2) {
          weightsOutputDeltas[cellMapping.at(unit)][oc1][oc2] +=
              wOutputDeltasUnit[oc1][oc2];
        }
      }

      for (auto oc = 0u; oc != outputSize; ++oc) {
        for (auto b = 0u; b != batchSize; ++b) {
          biasDeltas[cellMapping.at(unit)][oc] += grad[b][oc];
        }
      }
    }
  }
}


#include <queue>
#include <unordered_set>
#include <boost/math/constants/constants.hpp>
#include <CGAL/boost/graph/Euler_operations.h>
#include <Euclid/Geometry/TriMeshGeometry.h>
#include <Euclid/Math/Numeric.h>

namespace Euclid
{

namespace _impl
{

template<typename Mesh>
void init_queue(const Mesh& mesh,
                std::queue<edge_t<Mesh>>& queue,
                std::vector<bool>& inqueue)
{
    auto eimap = get(boost::edge_index, mesh);
    inqueue.resize(num_edges(mesh), false);
    for (auto e : edges(mesh)) {
        if (!is_delaunay(mesh, e)) {
            queue.push(e);
            inqueue[get(eimap, e)] = true;
        }
    }
}

template<typename Mesh>
void init_physical_edges(const Mesh& mesh, std::vector<bool>& physical)
{
    auto eimap = get(boost::edge_index, mesh);
    physical.resize(num_edges(mesh), true);
    for (auto e : edges(mesh)) {
        if (eq_almost(dihedral_angle(e, mesh),
                      boost::math::constants::pi<FT_t<Mesh>>())) {
            physical[get(eimap, e)] = false;
        }
    }
}

template<typename Mesh, typename Visitor>
void flip(Mesh& mesh,
          Visitor& visitor,
          edge_t<Mesh> e,
          std::queue<edge_t<Mesh>>& queue,
          std::vector<bool>& inqueue)
{
    auto eimap = get(boost::edge_index, mesh);
    auto h = halfedge(e, mesh);
    auto ho = opposite(h, mesh);
    auto ha = next(h, mesh);
    auto hb = next(ha, mesh);
    auto hc = next(ho, mesh);
    auto hd = next(hc, mesh);
    auto ea = edge(ha, mesh);
    auto eb = edge(hb, mesh);
    auto ec = edge(hc, mesh);
    auto ed = edge(hd, mesh);
    visitor.on_flipping(mesh, e);
    CGAL::Euler::flip_edge(h, mesh);
    visitor.on_flipped(mesh, e);
    if (!inqueue[get(eimap, ea)]) {
        queue.push(ea);
        inqueue[get(eimap, ea)] = true;
    }
    if (!inqueue[get(eimap, eb)]) {
        queue.push(eb);
        inqueue[get(eimap, eb)] = true;
    }
    if (!inqueue[get(eimap, ec)]) {
        queue.push(ec);
        inqueue[get(eimap, ec)] = true;
    }
    if (!inqueue[get(eimap, ed)]) {
        queue.push(ed);
        inqueue[get(eimap, ed)] = true;
    }
}

template<typename Mesh>
bool is_flip_topologically_ok(const Mesh& mesh, edge_t<Mesh>& e)
{
    auto h = halfedge(e, mesh);
    return !halfedge(target(next(h, mesh), mesh),
                     target(next(opposite(h, mesh), mesh), mesh),
                     mesh)
                .second;
}

template<typename Mesh, typename Visitor>
void split(Mesh& mesh,
           Visitor& visitor,
           edge_t<Mesh> e,
           std::queue<edge_t<Mesh>>& queue,
           std::vector<bool>& inqueue,
           std::vector<bool>& physical_edges,
           std::unordered_set<vertex_t<Mesh>>& split_vertices)
{
    auto hpq = halfedge(e, mesh);
    auto hqp = opposite(hpq, mesh);
    auto hqv = next(hpq, mesh);
    auto hvp = prev(hpq, mesh);
    auto hpu = next(hqp, mesh);
    auto huq = prev(hqp, mesh);

    // do edge split
    visitor.on_splitting(mesh, e);
    auto hps = CGAL::Euler::split_edge(hpq, mesh);
    auto hsq = next(hps, mesh);
    auto hqs = opposite(hsq, mesh);
    auto hsv = CGAL::Euler::split_face(hps, hqv, mesh);
    auto hsu = CGAL::Euler::split_face(hqs, hpu, mesh);
    auto vs = target(hps, mesh);

    SplitSite<Mesh> site;
    site.epq = e;
    site.vs = vs;
    site.hps = hps;
    site.hqs = opposite(hsq, mesh);
    site.hvs = opposite(hsv, mesh);
    site.hus = opposite(hsu, mesh);
    visitor.on_split(mesh, site);

    // update realted buffers
    auto eimap = get(boost::edge_index, mesh);
    auto eqv = edge(hqv, mesh);
    if (!inqueue[get(eimap, eqv)]) {
        queue.push(eqv);
        inqueue[get(eimap, eqv)] = true;
    }
    auto evp = edge(hvp, mesh);
    if (!inqueue[get(eimap, evp)]) {
        queue.push(evp);
        inqueue[get(eimap, evp)] = true;
    }
    auto epu = edge(hpu, mesh);
    if (!inqueue[get(eimap, epu)]) {
        queue.push(epu);
        inqueue[get(eimap, epu)] = true;
    }
    auto euq = edge(huq, mesh);
    if (!inqueue[get(eimap, euq)]) {
        queue.push(euq);
        inqueue[get(eimap, euq)] = true;
    }
    auto esp = edge(hps, mesh);
    auto esq = edge(hsq, mesh);
    auto esu = edge(hsu, mesh);
    auto esv = edge(hsv, mesh);
    // auto isp = get(eimap, esp);
    // auto isq = get(eimap, esq);
    // auto isu = get(eimap, esu);
    // auto isv = get(eimap, esv);
    queue.push(esp);
    queue.push(esq);
    queue.push(esu);
    queue.push(esv);
    inqueue[get(eimap, esq)] = true;
    inqueue.push_back(true);         // esp
    inqueue.push_back(true);         // esv
    inqueue.push_back(true);         // esu
    physical_edges.push_back(true);  // esp
    physical_edges.push_back(false); // esv
    physical_edges.push_back(false); // esu
    split_vertices.insert(vs);

    // determine new vertex position
    auto vpmap = get(boost::vertex_point, mesh);
    auto vp = target(hvp, mesh);
    auto vq = target(huq, mesh);
    auto pp = get(vpmap, vp);
    auto pq = get(vpmap, vq);
    auto pm = CGAL::midpoint(pp, pq);
    auto lm = length(pp - pm);
    auto l = 0.0;
    if (lm > 1.5) {
        l = 2.0;
        while (l < lm) {
            l *= 2.0;
        }
        if (std::abs(l - lm) > std::abs(l * 0.5 - lm)) {
            l *= 0.5;
        }
    }
    else if (lm < 0.75) {
        l = 0.5;
        while (l > lm) {
            l *= 0.5;
        }
        if (std::abs(l - lm) > std::abs(l * 2.0 - lm)) {
            l *= 2.0;
        }
    }
    else {
        l = 1.0;
    }
    if (split_vertices.find(vp) == split_vertices.end()) {
        auto ps = pp + normalized(pm - pp) * l;
        put(vpmap, vs, ps);
    }
    else {
        auto ps = pq + normalized(pm - pq) * l;
        put(vpmap, vs, ps);
    }
}

template<typename Mesh, typename Visitor>
void remesh_delaunay_simple_flip(Mesh& mesh, Visitor& visitor)
{
    visitor.on_started(mesh);
    auto eimap = get(boost::edge_index, mesh);
    std::queue<edge_t<Mesh>> queue;
    std::vector<bool> inqueue;
    init_queue(mesh, queue, inqueue);
    while (!queue.empty()) {
        auto e = queue.front();
        queue.pop();
        inqueue[get(eimap, e)] = false;
        if (!is_delaunay(mesh, e) && is_flip_topologically_ok(mesh, e)) {
            flip(mesh, visitor, e, queue, inqueue);
        }
        else {
            visitor.on_nonflippable(mesh, e);
        }
    }
    visitor.on_finished(mesh);
}

template<typename Mesh, typename Visitor>
void remesh_delaunay_geometry_preserving(Mesh& mesh, Visitor& visitor)
{
    visitor.on_started(mesh);
    auto eimap = get(boost::edge_index, mesh);
    std::queue<edge_t<Mesh>> queue;
    std::vector<bool> inqueue;
    init_queue(mesh, queue, inqueue);
    std::vector<bool> physical_edges;
    init_physical_edges(mesh, physical_edges);
    std::unordered_set<vertex_t<Mesh>> split_vertices;
    while (!queue.empty()) {
        auto e = queue.front();
        queue.pop();
        inqueue[get(eimap, e)] = false;
        if (!is_delaunay(mesh, e)) {
            if (!physical_edges[get(eimap, e)]) {
                flip(mesh, visitor, e, queue, inqueue);
            }
            else {
                visitor.on_nonflippable(mesh, e);
                split(mesh,
                      visitor,
                      e,
                      queue,
                      inqueue,
                      physical_edges,
                      split_vertices);
            }
        }
    }
    visitor.on_finished(mesh);
}

template<typename Mesh, typename Visitor>
void remesh_delaunay_feature_preserving(Mesh& mesh,
                                        Visitor& visitor,
                                        double threshold)
{
    visitor.on_started(mesh);
    auto eimap = get(boost::edge_index, mesh);
    std::queue<edge_t<Mesh>> queue;
    std::vector<bool> inqueue;
    init_queue(mesh, queue, inqueue);
    std::vector<bool> physical_edges;
    init_physical_edges(mesh, physical_edges);
    std::unordered_set<vertex_t<Mesh>> split_vertices;
    while (!queue.empty()) {
        auto e = queue.front();
        queue.pop();
        inqueue[get(eimap, e)] = false;
        if (!is_delaunay(mesh, e)) {
            if (!physical_edges[get(eimap, e)] ||
                dihedral_angle(e, mesh) > threshold) {
                flip(mesh, visitor, e, queue, inqueue);
            }
            else {
                visitor.on_nonflippable(mesh, e);
                split(mesh,
                      visitor,
                      e,
                      queue,
                      inqueue,
                      physical_edges,
                      split_vertices);
            }
        }
    }
    visitor.on_finished(mesh);
}

} // namespace _impl

template<typename Mesh>
bool is_delaunay(const Mesh& mesh, double eps)
{
    for (auto e : edges(mesh)) {
        if (!is_delaunay(mesh, e, eps)) {
            return false;
        }
    }
    return true;
}

template<typename Mesh>
bool is_delaunay(const Mesh& mesh, edge_t<Mesh> e, double eps)
{
    auto cot = static_cast<double>(cotangent_weight(e, mesh));
    return cot > eps;
}

template<typename Mesh>
void remesh_delaunay(Mesh& mesh,
                     RemeshDelaunayScheme scheme,
                     double dihedral_angle)
{
    RemeshDelaunayVisitor<Mesh> visitor;
    remesh_delaunay(mesh, visitor, scheme, dihedral_angle);
}

template<typename Mesh, typename Visitor>
void remesh_delaunay(Mesh& mesh,
                     Visitor& visitor,
                     RemeshDelaunayScheme scheme,
                     double dihedral_angle)
{
    switch (scheme) {
    case RemeshDelaunayScheme::FeaturePreserving:
        _impl::remesh_delaunay_feature_preserving(
            mesh,
            visitor,
            boost::math::constants::pi<double>() -
                dihedral_angle * boost::math::constants::degree<double>());
        break;
    case RemeshDelaunayScheme::GeometryPreserving:
        _impl::remesh_delaunay_geometry_preserving(mesh, visitor);
        break;
    case RemeshDelaunayScheme::SimpleFlip:
    default:
        _impl::remesh_delaunay_simple_flip(mesh, visitor);
        break;
    }
}

} // namespace Euclid


#define _USE_MATH_DEFINES

#include <cstdlib>
#include <iostream>
#include <regex>
#include <fstream>
#include <iterator>
#include <random>
#include <experimental/filesystem>
#include <cmath>
#include <string>

#include <boost/thread/mutex.hpp>
#include <boost/thread/lock_guard.hpp>

#include "Macros.h"
#include "StandardLibrary.h"
#include "DataTypes/Ops/Ops.h"
#include "DataTypes/Ops/StringOps.h"
#include "DataTypes/Ops/ArrayOps.h"
#include "DataTypes/Ops/IntegerOps.h"
#include "Utils/FileStreamHelper.h"

namespace FPTL
{
	namespace Runtime
	{
		namespace {
			void id(SExecutionContext & aCtx)
			{
				// Копируем данные аргументы от начала фрейма.
				for (size_t i = 0; i < aCtx.argNum; ++i)
				{
					aCtx.push(aCtx.getArg(i));
				}
			}

			void tupleLength(SExecutionContext & aCtx)
			{
				aCtx.push(DataBuilders::createInt(static_cast<long long>(aCtx.argNum)));
			}

			void Not(SExecutionContext & aCtx)
			{
				const auto & arg = aCtx.getArg(0);

				aCtx.push(DataBuilders::createBoolean(!arg.getOps()->toInt(arg)));
			}

			void And(SExecutionContext & aCtx)
			{
				const auto & lhs = aCtx.getArg(0);
				const auto & rhs = aCtx.getArg(1);

				aCtx.push(DataBuilders::createBoolean((lhs.getOps()->toInt(lhs) * rhs.getOps()->toInt(rhs))));
			}

			void Or(SExecutionContext & aCtx)
			{
				const auto & lhs = aCtx.getArg(0);
				const auto & rhs = aCtx.getArg(1);

				aCtx.push(DataBuilders::createBoolean((lhs.getOps()->toInt(lhs) + rhs.getOps()->toInt(rhs))));
			}

			void Xor(SExecutionContext & aCtx)
			{
				const auto & lhs = aCtx.getArg(0);
				const auto & rhs = aCtx.getArg(1);

				aCtx.push(DataBuilders::createBoolean((lhs.getOps()->toInt(lhs) ^ rhs.getOps()->toInt(rhs))));
			}

			void equal(SExecutionContext & aCtx)
			{
				const auto & lhs = aCtx.getArg(0);
				const auto & rhs = aCtx.getArg(1);

				aCtx.push(lhs.getOps()->combine(rhs.getOps())->equal(lhs, rhs));
			}

			void notEqual(SExecutionContext & aCtx)
			{
				const auto & lhs = aCtx.getArg(0);
				const auto & rhs = aCtx.getArg(1);

				aCtx.push(
					DataBuilders::createBoolean(
						!lhs.getOps()->combine(rhs.getOps())->equal(lhs, rhs).mIntVal
					)
				);
			}

			void greater(SExecutionContext & aCtx)
			{
				const auto & lhs = aCtx.getArg(0);
				const auto & rhs = aCtx.getArg(1);

				aCtx.push(lhs.getOps()->combine(rhs.getOps())->greater(lhs, rhs));
			}

			void greaterOrEqual(SExecutionContext & aCtx)
			{
				const auto & lhs = aCtx.getArg(0);
				const auto & rhs = aCtx.getArg(1);

				aCtx.push(
					DataBuilders::createBoolean(
						!lhs.getOps()->combine(rhs.getOps())->less(lhs, rhs).mIntVal
					)
				);
			}

			void less(SExecutionContext & aCtx)
			{
				const auto & lhs = aCtx.getArg(0);
				const auto & rhs = aCtx.getArg(1);

				aCtx.push(lhs.getOps()->combine(rhs.getOps())->less(lhs, rhs));
			}

			void lessOrEqual(SExecutionContext & aCtx)
			{
				const auto & lhs = aCtx.getArg(0);
				const auto & rhs = aCtx.getArg(1);

				aCtx.push(
					DataBuilders::createBoolean(
						!lhs.getOps()->combine(rhs.getOps())->greater(lhs, rhs).mIntVal
					)
				);
			}

			void add(SExecutionContext & aCtx)
			{
				const auto & lhs = aCtx.getArg(0);
				const auto & rhs = aCtx.getArg(1);

				aCtx.push(lhs.getOps()->combine(rhs.getOps())->add(lhs, rhs));
			}

			void sub(SExecutionContext & aCtx)
			{
				const auto & lhs = aCtx.getArg(0);
				const auto & rhs = aCtx.getArg(1);

				aCtx.push(lhs.getOps()->combine(rhs.getOps())->sub(lhs, rhs));
			}

			void mul(SExecutionContext & aCtx)
			{
				const auto & lhs = aCtx.getArg(0);
				const auto & rhs = aCtx.getArg(1);

				aCtx.push(lhs.getOps()->combine(rhs.getOps())->mul(lhs, rhs));
			}

			void div(SExecutionContext & aCtx)
			{
				const auto & lhs = aCtx.getArg(0);
				const auto & rhs = aCtx.getArg(1);

				aCtx.push(lhs.getOps()->combine(rhs.getOps())->div(lhs, rhs));
			}

			void mod(SExecutionContext & aCtx)
			{
				const auto & lhs = aCtx.getArg(0);
				const auto & rhs = aCtx.getArg(1);

				aCtx.push(lhs.getOps()->combine(rhs.getOps())->mod(lhs, rhs));
			}

			// Генерирует случайное вещественное число в диапазоне от 0 до 1.
			void rand(SExecutionContext & aCtx)
			{
				static thread_local std::random_device rd;
				static thread_local std::mt19937_64 gen(rd());
				static std::uniform_real_distribution realDistrib;
				aCtx.push(DataBuilders::createDouble(realDistrib(gen)));
			}

			void sqrt(SExecutionContext & aCtx)
			{
				const auto & arg = aCtx.getArg(0);
				aCtx.push(DataBuilders::createDouble(std::sqrt(arg.getOps()->toDouble(arg))));
			}

			void sin(SExecutionContext & aCtx)
			{
				const auto & arg = aCtx.getArg(0);
				aCtx.push(DataBuilders::createDouble(std::sin(arg.getOps()->toDouble(arg))));
			}

			void cos(SExecutionContext & aCtx)
			{
				const auto & arg = aCtx.getArg(0);
				aCtx.push(DataBuilders::createDouble(std::cos(arg.getOps()->toDouble(arg))));
			}

			void tan(SExecutionContext & aCtx)
			{
				const auto & arg = aCtx.getArg(0);
				aCtx.push(DataBuilders::createDouble(std::tan(arg.getOps()->toDouble(arg))));
			}

			void asin(SExecutionContext & aCtx)
			{
				const auto & arg = aCtx.getArg(0);
				aCtx.push(DataBuilders::createDouble(std::asin(arg.getOps()->toDouble(arg))));
			}

			void atan(SExecutionContext & aCtx)
			{
				const auto & arg = aCtx.getArg(0);
				aCtx.push(DataBuilders::createDouble(std::atan(arg.getOps()->toDouble(arg))));
			}

			void round(SExecutionContext & aCtx)
			{
				const auto & arg = aCtx.getArg(0);
				aCtx.push(DataBuilders::createDouble(std::floor(arg.getOps()->toDouble(arg) + 0.5)));
			}

			void log(SExecutionContext & aCtx)
			{
				const auto & arg = aCtx.getArg(0);
				aCtx.push(DataBuilders::createDouble(std::log(arg.getOps()->toDouble(arg))));
			}

			void exp(SExecutionContext & aCtx)
			{
				const auto & arg = aCtx.getArg(0);
				aCtx.push(DataBuilders::createDouble(std::exp(arg.getOps()->toDouble(arg))));
			}

			void loadPi(SExecutionContext & aCtx)
			{
				aCtx.push(DataBuilders::createDouble(M_PI));
			}

			void loadE(SExecutionContext & aCtx)
			{
				aCtx.push(DataBuilders::createDouble(M_E));
			}

			void abs(SExecutionContext & aCtx)
			{
				const auto & arg = aCtx.getArg(0);
				aCtx.push(arg.getOps()->abs(arg));
			}

			void print(const SExecutionContext & aCtx)
			{
				static boost::mutex outputMutex;
				boost::lock_guard<boost::mutex> guard(outputMutex);

				aCtx.print(std::cout);
			}

			void printType(const SExecutionContext & aCtx)
			{
				static boost::mutex outputMutex;
				boost::lock_guard<boost::mutex> guard(outputMutex);

				aCtx.printTypes(std::cout);
			}

			// Преобразование в строку.
			void toString(SExecutionContext & aCtx)
			{
				std::stringstream strStream;
				const auto & arg = aCtx.getArg(0);

				arg.getOps()->print(arg, strStream);

				aCtx.push(StringBuilder::create(aCtx, strStream.str()));
			}

			// Преобразование в вещественное число.
			void toInteger(SExecutionContext & aCtx)
			{
				const auto & arg = aCtx.getArg(0);

				aCtx.push(DataBuilders::createInt(arg.getOps()->toInt(arg)));
			}

			// Преобразование в целое число.
			void toDouble(SExecutionContext & aCtx)
			{
				const auto & arg = aCtx.getArg(0);

				aCtx.push(DataBuilders::createDouble(arg.getOps()->toDouble(arg)));
			}

			// Конкатенация строк.
			void concat(SExecutionContext & aCtx)
			{
				size_t len = 0;
				for (size_t i = 0; i < aCtx.argNum; ++i)
				{
					const auto & arg = aCtx.getArg(i);
					const auto inStr = arg.getOps()->toString(arg);
					len += inStr->length();
				}

				const auto val = StringBuilder::create(aCtx, len);
				const auto str = val.mString->getChars();
				size_t curPos = 0;

				for (size_t i = 0; i < aCtx.argNum; ++i)
				{
					const auto & arg = aCtx.getArg(i);
					const auto inStr = arg.getOps()->toString(arg);
					std::memcpy(str + curPos, inStr->getChars(), inStr->length());
					curPos += inStr->length();
				}

				aCtx.push(val);
			}

			// Длина строки.
			void length(SExecutionContext & aCtx)
			{
				const auto & arg = aCtx.getArg(0);
				const auto str = arg.getOps()->toString(arg);

				aCtx.push(DataBuilders::createInt(static_cast<long long>(str->length())));
			}

			// Поиск по регулярному выражению.
			void search(SExecutionContext & aCtx)
			{
				const auto & arg0 = aCtx.getArg(0);
				const auto & arg1 = aCtx.getArg(1);

				const auto src = arg0.getOps()->toString(arg0);
				const auto regEx = arg1.getOps()->toString(arg1);

				std::regex rx(regEx->str());
				std::cmatch matchResults;

				if (std::regex_search(static_cast<const char *>(src->getChars()), static_cast<const char *>(src->getChars() + src->length()), matchResults, rx))
				{
					for (size_t i = 0; i < rx.mark_count(); i++)
					{
						auto & m = matchResults[i + 1];
						auto val = StringBuilder::create(aCtx, src, m.first - src->contents(), m.second - src->contents());
						aCtx.push(val);
					}
				}
				else
				{
					aCtx.push(DataBuilders::createUndefinedValue());
				}
			}

			// Проверка соответствия по регулярному выражению.
			void match(SExecutionContext & aCtx)
			{
				const auto & arg0 = aCtx.getArg(0);
				const auto & arg1 = aCtx.getArg(1);

				const auto src = arg0.getOps()->toString(arg0);
				const auto regEx = arg1.getOps()->toString(arg1);

				std::regex rx(regEx->str());
				std::cmatch match;

				if (std::regex_match(static_cast<const char *>(src->getChars()), static_cast<const char *>(src->getChars() + src->length()), match, rx))
				{
					for (size_t i = 0; i < rx.mark_count(); ++i)
					{
						const auto & m = match[i + 1];
						const auto val = StringBuilder::create(aCtx, src, m.first - src->contents(), m.second - src->contents());
						aCtx.push(val);
					}
				}
				else
				{
					aCtx.push(DataBuilders::createUndefinedValue());
				}
			}

			// Замена по регулярному выражению.
			void replace(SExecutionContext & aCtx)
			{
				const auto & arg0 = aCtx.getArg(0);
				const auto & arg1 = aCtx.getArg(1);
				const auto & arg2 = aCtx.getArg(2);

				const auto src = arg0.getOps()->toString(arg0);
				const auto pattern = arg1.getOps()->toString(arg1);
				const auto format = arg2.getOps()->toString(arg2);

				const std::regex rx(pattern->str());
				const auto result = std::regex_replace(src->str(), rx, format->str());
				aCtx.push(StringBuilder::create(aCtx, result));
			}

			// Выделение лексемы с начала строки.
			void getToken(SExecutionContext & aCtx)
			{
				const auto & arg0 = aCtx.getArg(0);
				const auto & arg1 = aCtx.getArg(1);

				const auto src = arg0.getOps()->toString(arg0);
				const auto pattern = arg1.getOps()->toString(arg1);

				const std::regex rx("^(?:\\s*)(" + pattern->str() + ")");

				std::cmatch matchResults;

				const auto first = static_cast<const char *>(src->getChars());
				const auto last = static_cast<const char *>(src->getChars() + src->length());

				if (std::regex_search(first, last, matchResults, rx))
				{
					const auto prefix = StringBuilder::create(aCtx, src, matchResults[1].first - src->contents(), matchResults[1].second - src->contents());
					aCtx.push(prefix);

					const auto suffix = StringBuilder::create(aCtx, src, matchResults.suffix().first - src->contents(), matchResults.suffix().second - src->contents());
					aCtx.push(suffix);
				}
				else
				{
					aCtx.push(DataBuilders::createUndefinedValue());
				}
			}

			// Чтение содержимого файла.
			void readFile(SExecutionContext & aCtx)
			{
				// Проверяем имя файла.
				const auto & arg = aCtx.getArg(0);

				const auto fileName = arg.getOps()->toString(arg);

				DataValue val;
				std::string errMsg = "";
				std::fstream input;
				input.exceptions(std::ifstream::failbit | std::ifstream::badbit);
				try
				{
					// Вычисляем размер файла.
					const auto begin = input.tellg();
					input.seekg(0, std::ios::end);
					const auto size = input.tellg() - begin;
					input.seekg(0, std::ios::beg);

					// Резервируем память под файл.
					val = StringBuilder::create(aCtx, size);
					// Читаем данные.
					std::copy(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>(), val.mString->getChars());
					input.close();
				}
				catch (const std::ios_base::failure& exc)
				{
					val = DataBuilders::createUndefinedValue();
					errMsg = Utils::getfStreamError(input);
					if (errMsg == "") errMsg = exc.what();
				}
				aCtx.push(val);
				aCtx.push(StringBuilder::create(aCtx, errMsg));
			}

			void writeToFile(SExecutionContext & aCtx, std::ios::openmode mode)
			{
				// Проверяем имя файла.
				const auto & val = aCtx.getArg(0);
				const auto & file = aCtx.getArg(1);

				const auto fileName = file.getOps()->toString(file);

				Utils::setPermissions(fileName->str());

				DataValue res;
				std::string errMsg = "";
				std::fstream output;
				output.exceptions(std::ifstream::failbit | std::ifstream::badbit);
				try
				{
					output.open(fileName->str(), mode);
					output.precision(std::numeric_limits<double>::max_digits10);
					val.getOps()->write(val, output);
					res = DataBuilders::createBoolean(true);
					output.close();
				}
				catch (const std::ios_base::failure& exc)
				{
					res = DataBuilders::createBoolean(false);
					errMsg = Utils::getfStreamError(output);
					if (errMsg == "") errMsg = exc.what();
				}
				aCtx.push(res);
				aCtx.push(StringBuilder::create(aCtx, errMsg));
			}

			// Создание или перезапись файла.
			void createFile(SExecutionContext & aCtx)
			{
				writeToFile(aCtx, std::ios::out);
			}

			// Запись в конец файла.
			void appendFile(SExecutionContext & aCtx)
			{
				writeToFile(aCtx, std::ios::app);
			}

			// Создание массива.
			void createArray(SExecutionContext & aCtx)
			{
				const auto sizeVal = aCtx.getArg(0);
				const auto& initialVal = aCtx.getArg(1);

				const auto intSize = sizeVal.getOps()->toInt(sizeVal);

#if fptlDebugBuild
				if (sizeVal.getOps() != IntegerOps::get())
					throw BaseOps::invalidOperation(sizeVal.getOps()->getType(sizeVal), "toInt");
				if (intSize <= 0) throw std::invalid_argument(ArrayValue::negativeSizeMsg(intSize));
#endif

				const auto size = static_cast<size_t>(intSize);
				aCtx.push(ArrayValue::create(aCtx, size, initialVal));
			}

			// Чтение элемента из массива.
			void getArrayElement(SExecutionContext & aCtx)
			{
				const auto & arrVal = aCtx.getArg(0);
				const auto & posVal = aCtx.getArg(1);

#if fptlDebugBuild
				ArrayValue::arrayValueCheck(arrVal);
				if (posVal.getOps() != IntegerOps::get())
					throw BaseOps::invalidOperation(posVal.getOps()->getType(posVal), "toInt");
#endif

				const size_t pos = posVal.getOps()->toInt(posVal);
				aCtx.push(ArrayValue::get(arrVal, pos));
			}

			// Запись элемента в массив.
			void setArrayElement(SExecutionContext & aCtx)
			{
				const auto & arrVal = aCtx.getArg(0);
				const auto & posVal = aCtx.getArg(1);
				const auto & val = aCtx.getArg(2);

#if fptlDebugBuild
				ArrayValue::arrayValueCheck(arrVal);
				if (posVal.getOps() != IntegerOps::get())
					throw BaseOps::invalidOperation(posVal.getOps()->getType(posVal), "toInt");
#endif

				const size_t pos = posVal.getOps()->toInt(posVal);

				ArrayValue::set(const_cast<DataValue &>(arrVal), pos, val);

				aCtx.push(arrVal);
			}

			void getArrayLength(SExecutionContext & aCtx)
			{
				const auto & arrVal = aCtx.getArg(0);
				ArrayValue::arrayValueCheck(arrVal);
				aCtx.push(DataBuilders::createInt(ArrayValue::getLen(arrVal)));
			}

			void ArrayConcat(SExecutionContext & aCtx)
			{
				aCtx.push(ArrayValue::concat(aCtx));
			}

			void ArrayCopy(SExecutionContext & aCtx)
			{
				const auto & arrVal = aCtx.getArg(0);
				ArrayValue::arrayValueCheck(arrVal);
				aCtx.push(ArrayValue::copy(aCtx, arrVal));
			}

			void ArrayDot(SExecutionContext & aCtx)
			{
				const auto & arrVal1 = aCtx.getArg(0);
				const auto & arrVal2 = aCtx.getArg(1);
				ArrayValue::arrayValueCheck(arrVal1);
				ArrayValue::arrayValueCheck(arrVal2);
				aCtx.push(ArrayValue::dot(aCtx, arrVal1, arrVal2));
			}

			// Запись элемента в массив.
			void arrayFromFile(SExecutionContext & aCtx)
			{
				auto arrVal = aCtx.getArg(0);
				const auto fileVal = aCtx.getArg(1);

				ArrayValue::arrayValueCheck(arrVal);
				const auto fileName = fileVal.getOps()->toString(fileVal);

				std::string errMsg = "";
				std::fstream input;
				input.exceptions(std::ifstream::failbit | std::ifstream::badbit);
				try
				{
					input.open(fileName->str());
					ArrayValue::fromString(arrVal, static_cast<std::istream&>(input));
					input.close();
				}
				catch (const std::ios_base::failure& exc)
				{
					arrVal = DataBuilders::createUndefinedValue();
					errMsg = Utils::getfStreamError(input);
					if (errMsg == "") errMsg = exc.what();
				}
				aCtx.push(arrVal);
				aCtx.push(StringBuilder::create(aCtx, errMsg));
			}
		} // anonymous namespace

		const std::map<std::string, TFunction> StandardLibrary::mFunctions =
		{
			// Работа с кортежем.
			{"id", &id},
			{"tupleLen", &tupleLength},

			// Арифметические.
			{"add",&add},
			{"sub", &sub},
			{"mul", &mul},
			{"div", &div},
			{"mod", &mod},
			{"abs", &abs},
			{"sqrt", &sqrt},
			{"exp", &exp},
			{"ln", &log},
			{"round", &round},
			{"sin", &sin},
			{"cos", &cos},
			{"tan", &tan},
			{"asin", &asin},
			{"atan", &atan},
			{"Pi", &loadPi},
			{"E", &loadE},
			{"rand", &rand},

			//Логические.
			{"not", &Not},
			{"and", &And},
			{"or", &Or },
			{"xor", &Xor },
			{"equal", &equal},
			{"nequal", &notEqual},
			{"greater", &greater},
			{"gequal", &greaterOrEqual},
			{"less", &less},
			{"lequal", &lessOrEqual},

			// Работа со строками.
			{"length", &length},
			{"cat", &concat},
			{"search", &search},
			{"replace", &replace},
			{"match", &match},
			{"getToken", &getToken},

			// Преобразования типов.
			{"toInt", &toInteger},
			{"toReal", &toDouble},
			{"toString", &toString},

			// Ввод / вывод.
			{"print", &print},
			{"printType", &printType},
			{"readFile", &readFile},
			{"createFile", &createFile},
			{"appendFile", &appendFile},

			// Работа с массивами.
			{"arrayCreate", &createArray},
			{"arrayGet", &getArrayElement},
			{"arraySet", &setArrayElement},
			{"arrayLen", &getArrayLength},
			{"arrayCat", &ArrayConcat},
			{"arrayCopy", &ArrayCopy},
			{"ArrayDot", &ArrayDot},
			{"arrayFromFile", &arrayFromFile}
		};

		StandardLibrary::StandardLibrary() : FunctionLibrary("StdLib")
		{
			addFunctions(mFunctions);
		}
	}
}


/*=========================================================================
 *
 *  Copyright 2011-2013 The University of North Carolina at Chapel Hill
 *  All rights reserved.
 *
 *  Licensed under the MADAI Software License. You may obtain a copy of
 *  this license at
 *
 *         https://madai-public.cs.unc.edu/visualization/software-license/
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
#include <time.h>

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/random/variate_generator.hpp>

#include <madaisys/SystemInformation.hxx>

#include "Random.h"

namespace madai {

struct Random::RandomPrivate {
  /** Typedefs for random number generation */
  //@{
  typedef boost::mt19937               BaseGeneratorType;
  typedef boost::uniform_int< int >    UniformIntDistributionType;
  typedef boost::uniform_int< long >   UniformLongDistributionType;
  typedef boost::uniform_real<>        UniformRealDistributionType;
  typedef boost::normal_distribution<> NormalDistributionType;
  typedef boost::variate_generator<
    BaseGeneratorType&,
    UniformRealDistributionType >      UniformRealGeneratorType;
  //@}

  /** Mersenne Twister random number generator */
  BaseGeneratorType           m_BaseGenerator;

  /** Uniform int distribution */
  UniformIntDistributionType  m_UniformIntDistribution;

  /** Uniform long distribution */
  UniformLongDistributionType  m_UniformLongDistribution;

  /** Uniform real distribution */
  UniformRealDistributionType m_UniformRealDistribution;

  /** Uniform real generator */
  UniformRealGeneratorType    m_UniformRealGenerator;

  /** Normal distribution */
  NormalDistributionType      m_NormalDistribution;

  RandomPrivate();
};

Random::RandomPrivate::RandomPrivate() :
  m_UniformRealGenerator( m_BaseGenerator, m_UniformRealDistribution )
{
}

Random::Random() :
  m_RandomImplementation(new Random::RandomPrivate())
{
  this->Reseed();
}

Random::Random(unsigned long int seed) :
  m_RandomImplementation(new Random::RandomPrivate())
{
  this->Reseed(seed);
}

Random::~Random()
{
  delete m_RandomImplementation;
}

void Random::Reseed(unsigned long int seed)
{
  m_RandomImplementation->m_BaseGenerator.seed( seed );
}

void Random::Reseed()
{
  madaisys::SystemInformation systemInformation;
  this->Reseed(time(NULL) ^ (systemInformation.GetProcessId() << 16));
}

long Random::Integer( long N )
{
  return m_RandomImplementation->m_UniformLongDistribution(
      m_RandomImplementation->m_BaseGenerator, N );
}

long Random::operator()( long N )
{
  return m_RandomImplementation->m_UniformLongDistribution(
      m_RandomImplementation->m_BaseGenerator, N );
}

double Random::Uniform()
{
  return m_RandomImplementation->m_UniformRealGenerator();
}

double Random::Uniform(double min, double max)
{
  return this->Uniform() * ( max - min ) + min;
}

double Random::Gaussian()
{
  return m_RandomImplementation->m_NormalDistribution(
      m_RandomImplementation->m_UniformRealGenerator );
}

double Random::Gaussian(double mean, double standardDeviation)
{
  return standardDeviation * this->Gaussian() + mean;
}

}


#define BOOST_TEST_MODULE pcrblock profile
#include <boost/test/unit_test.hpp>
#include "dal_MathUtils.h"
#include "discr_block.h"
#include "discr_blockdata.h"
#include "block_functions.h"

BOOST_AUTO_TEST_CASE(test)
{
  using namespace block;

  // Create a block.
  discr::Raster raster(3, 2);
  discr::Block block(raster);

  // Create an attribute.
  discr::BlockData<REAL4> date(&block);

  size_t lastTimeStep = 10000;
  REAL4 thickness = 1.0;

  // Fill the block/attribute.
  discr::RasterData<REAL4> value(&raster);

  for(size_t i = 1; i <= lastTimeStep; ++i) {
    for(size_t j = 0; j < raster.nrCells(); ++j) {
      value.cell(j) = i + j;
    }

    date.setDefaultValue(value);
    block.addVoxels(1, thickness);
  }

  // Determine profiles at different heights and test the contents.
  discr::RasterData<REAL4> result(&raster);

  for(size_t i = 1; i <= lastTimeStep; ++i) {
    // Bottom of voxel.
    profile(result, date, (i - 1) * thickness);

    for(size_t j = 0; j < raster.nrCells(); ++j) {
      BOOST_CHECK(dal::comparable(result.cell(j), REAL4(i + j)));
    }

    // Center of voxel.
    profile(result, date, (i - 1) * thickness + 0.5 * thickness);
    BOOST_CHECK(dal::comparable(result.cell(0), REAL4(i)));

    for(size_t j = 0; j < raster.nrCells(); ++j) {
      BOOST_CHECK(dal::comparable(result.cell(j), REAL4(i + j)));
    }

    // Top of voxel == bottom of upper voxel, except for the upper voxel.
    profile(result, date, i * thickness);

    if(i != lastTimeStep) {
      for(size_t j = 0; j < raster.nrCells(); ++j) {
        BOOST_CHECK(dal::comparable(result.cell(j), REAL4(i + j + 1)));
      }
    }
    else {
      for(size_t j = 0; j < raster.nrCells(); ++j) {
        BOOST_CHECK(pcr::isMV(result.cell(j)));
      }
    }
  }
}


/***********************************************************************************************************************
 *  OpenStudio(R), Copyright (c) 2008-2018, Alliance for Sustainable Energy, LLC. All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 *  following conditions are met:
 *
 *  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
 *  disclaimer.
 *
 *  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *  following disclaimer in the documentation and/or other materials provided with the distribution.
 *
 *  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote
 *  products derived from this software without specific prior written permission from the respective party.
 *
 *  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative
 *  works may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without
 *  specific prior written permission from Alliance for Sustainable Energy, LLC.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 *  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES GOVERNMENT, OR ANY CONTRIBUTORS BE LIABLE FOR
 *  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 *  AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 **********************************************************************************************************************/

#ifndef UTILITIES_GEOMETRY_GEOMETRY_HPP
#define UTILITIES_GEOMETRY_GEOMETRY_HPP

#include "../UtilitiesAPI.hpp"

#include <vector>
#include <boost/optional.hpp>

namespace openstudio{

  class Point3d;
  class PointLatLon;
  class Vector3d;

  /// convert degrees to radians
  UTILITIES_API double degToRad(double degrees);

  /// convert radians to degrees
  UTILITIES_API double radToDeg(double radians);

  /// compute area from surface as Point3dVector
  UTILITIES_API boost::optional<double> getArea(const std::vector<Point3d>& points);

  /// compute Newall vector from surface as Point3dVector, direction is same as outward normal
  /// magnitude is twice the area
  UTILITIES_API boost::optional<Vector3d> getNewallVector(const std::vector<Point3d>& points);

  /// compute outward normal from surface as Point3dVector
  UTILITIES_API boost::optional<Vector3d> getOutwardNormal(const std::vector<Point3d>& points);

  /// compute centroid from surface as Point3dVector
  UTILITIES_API boost::optional<Point3d> getCentroid(const std::vector<Point3d>& points);

  /// reorder points to upper-left-corner convention
  UTILITIES_API std::vector<Point3d> reorderULC(const std::vector<Point3d>& points);

  /// removes collinear points, tolerance is for length of cross product after normalizing each line segment
  UTILITIES_API std::vector<Point3d> removeCollinear(const std::vector<Point3d>& points, double tol = 0.001);

  /// return distance between two points
  UTILITIES_API double getDistance(const Point3d& point1, const Point3d& point2);

  /// return distance between a point and a line segment
  /// returns 0 if lineSegment does not have length 2
  UTILITIES_API double getDistancePointToLineSegment(const Point3d& point, const std::vector<Point3d>& lineSegment);

  /// return distance between a point and a triangle
  /// returns 0 if triangle does not have length 3
  UTILITIES_API double getDistancePointToTriangle(const Point3d& point, const std::vector<Point3d>& triangle);

  /// return angle (in radians) between two vectors
  UTILITIES_API double getAngle(const Vector3d& vector1, const Vector3d& vector2);

  /// check if two vectors of points are equal (within tolerance) irregardless of initial ordering.
  UTILITIES_API bool circularEqual(const std::vector<Point3d>& points1, const std::vector<Point3d>& points2, double tol = 0.001);

  /// if point3d is within tol of any existing points then returns existing point
  /// otherwise adds point3d to allPoints and returns point3d
  UTILITIES_API Point3d getCombinedPoint(const Point3d& point3d, std::vector<Point3d>& allPoints, double tol = 0.001);

  /// compute triangulation of vertices, holes are removed in the triangulation
  /// requires that vertices and holes are in clockwise order on the z = 0 plane (i.e. in face coordinates but reversed)
  UTILITIES_API std::vector<std::vector<Point3d> > computeTriangulation(const std::vector<Point3d>& vertices, const std::vector<std::vector<Point3d> >& holes, double tol = 0.001);

  /// move all vertices towards point by distance, pass negative distance to move away from point
  /// no guarantee that resulting polygon will be valid
  UTILITIES_API std::vector<Point3d> moveVerticesTowardsPoint(const std::vector<Point3d>& vertices, const Point3d& point, double distance);

  /// reverse order of vertices
  UTILITIES_API std::vector<Point3d> reverse(const std::vector<Point3d>& vertices);

  /// Sets view and daylighting window, overhang and light shelf vertices by reference.  Returns true if successful, false otherwise.
  UTILITIES_API bool applyViewAndDaylightingGlassRatios(double viewGlassToWallRatio, double daylightingGlassToWallRatio,
                                                        double desiredViewGlassSillHeight, double desiredDaylightingGlassHeaderHeight,
                                                        double exteriorShadingProjectionFactor, double interiorShelfProjectionFactor,
                                                        const std::vector<Point3d>& surfaceVertices, std::vector<Point3d>& viewVertices,
                                                        std::vector<Point3d>& daylightingVertices, std::vector<Point3d>& exteriorShadingVertices,
                                                        std::vector<Point3d>& interiorShelfVertices);

} // openstudio

#endif //UTILITIES_GEOMETRY_GEOMETRY_HPP


//
// OpenTissue, A toolbox for physical based simulation and animation.
// Copyright (C) 2007 Department of Computer Science, University of Copenhagen
//
#include <OpenTissue/configuration.h>

#include <OpenTissue/core/math/optimization/optimization_armijo_backtracking.h>

#include <cmath> // needed for std::cos

#define BOOST_AUTO_TEST_MAIN
#include <OpenTissue/utility/utility_push_boost_filter.h>
#include <boost/test/auto_unit_test.hpp>
#include <boost/test/unit_test_suite.hpp>
#include <boost/test/floating_point_comparison.hpp>
#include <boost/test/test_tools.hpp>
#include <OpenTissue/utility/utility_pop_boost_filter.h>

double f( double const & x)
{
  using std::cos;

  return cos( x );
}

double df( double const & x )
{
  using std::sin;

  return -sin( x );
}

class F
{
public:
  double operator()( double const & x ) const  {    return f(x);  }
};

class dF
{
public:
  double operator()( double const & x ) const  {    return df(x);  }
};

BOOST_AUTO_TEST_SUITE(opentissue_math_big_armijo_backtracking);

BOOST_AUTO_TEST_CASE(no_test_case)
{

}

BOOST_AUTO_TEST_SUITE_END();


///////////////////////////////////////////////////////////////
//  Copyright 2012 John Maddock. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt
//

#ifdef _MSC_VER
#define _SCL_SECURE_NO_WARNINGS
#endif

#include <boost/detail/lightweight_test.hpp>
#include <boost/array.hpp>
#include "test.hpp"

#include <boost/multiprecision/cpp_bin_float.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/random.hpp>

#ifdef TEST_GMP
#include <boost/multiprecision/gmp.hpp>
#endif
#ifdef TEST_TOMMATH
#include <boost/multiprecision/tommath.hpp>
#endif
#ifdef TEST_MPFR
#include <boost/multiprecision/mpfr.hpp>
#endif

int main()
{
   using namespace boost::multiprecision;
   using namespace boost::random;

   independent_bits_engine<mt11213b, 1024, cpp_int> gen;
   mt11213b                                         small_gen;

   for (unsigned i = 0; i < 100; ++i)
   {
      cpp_int c = gen();
      //
      // Integer to integer conversions first:
      //
#ifdef TEST_GMP
      mpz_int z(c);
      cpp_int t(z);
      BOOST_CHECK_EQUAL(t, c);
      z.assign(-c);
      t.assign(-z);
      BOOST_CHECK_EQUAL(t, c);
#endif
#ifdef TEST_TOMMATH
      tom_int tom(c);
      cpp_int t2(tom);
      BOOST_CHECK_EQUAL(t2, c);
      tom.assign(-c);
      t2.assign(-tom);
      BOOST_CHECK_EQUAL(t2, c);
#endif
      //
      // Now integer to float:
      //
      typedef number<cpp_dec_float<500> > dec_float_500;
      dec_float_500                       df(c);
      dec_float_500                       df2(c.str());
      BOOST_CHECK_EQUAL(df, df2);
      df.assign(-c);
      df2 = -df2;
      BOOST_CHECK_EQUAL(df, df2);
#ifdef TEST_GMP
      typedef number<gmp_float<500> > mpf_type;
      mpf_type                        mpf(c);
      mpf_type                        mpf2(c.str());
      BOOST_CHECK_EQUAL(mpf, mpf2);
      mpf.assign(-c);
      mpf2 = -mpf2;
      BOOST_CHECK_EQUAL(mpf, mpf2);
#endif
#ifdef TEST_MPFR
      typedef number<mpfr_float_backend<500> > mpfr_type;
      mpfr_type                                mpfr(c);
      mpfr_type                                mpfr2(c.str());
      BOOST_CHECK_EQUAL(mpfr, mpfr2);
      mpfr.assign(-c);
      mpfr2 = -mpfr2;
      BOOST_CHECK_EQUAL(mpfr, mpfr2);
#endif
      //
      // Now float to float:
      //
      df.assign(c);
      df /= dec_float_500(gen());
      dec_float_500 tol("1e-500");
#ifdef TEST_GMP
      mpf.assign(df);
      mpf2 = static_cast<mpf_type>(df.str());
      BOOST_CHECK_EQUAL(mpf, mpf2);
      df.assign(mpf);
      df2 = static_cast<dec_float_500>(mpf.str());
      BOOST_CHECK(fabs((df - df2) / df) < tol);
#endif
#ifdef TEST_MPFR
      mpfr.assign(df);
      mpfr2 = static_cast<mpfr_type>(df.str());
      BOOST_CHECK_EQUAL(mpfr, mpfr2);
      df.assign(mpfr);
      df2 = static_cast<dec_float_500>(mpfr.str());
      BOOST_CHECK(fabs((df - df2) / df) < tol);
#endif
      //
      // Rational to rational conversions:
      //
      cpp_rational cppr(c, gen()), cppr2, cppr3;
#ifdef TEST_GMP
      mpq_rational mpq(cppr);
      cppr2.assign(mpq);
      BOOST_CHECK_EQUAL(cppr, cppr2);
#endif
#ifdef TEST_TOMMATH
      tom_rational tr(cppr);
      cppr3.assign(tr);
      BOOST_CHECK_EQUAL(cppr, cppr3);
#endif
      //
      // Integer to rational conversions:
      //
#ifdef TEST_GMP
      mpq.assign(c);
      mpq_rational mpq2 = static_cast<mpq_rational>(c.str());
      BOOST_CHECK_EQUAL(mpq, mpq2);
#endif
#ifdef TEST_TOMMATH
      tr.assign(c);
      tom_rational tr2 = static_cast<tom_rational>(c.str());
      BOOST_CHECK_EQUAL(tr, tr2);
#endif
      //
      // Rational to float:
      //
      df.assign(cppr);
      df2.assign(numerator(cppr));
      df2 /= dec_float_500(denominator(cppr));
      BOOST_CHECK(fabs(df - df2) / df2 < tol);
      //
      // Float to rational:
      //
      static const int                                       max_range = std::numeric_limits<double>::digits >= std::numeric_limits<int>::digits ? (std::numeric_limits<int>::max)() : (1 << (std::numeric_limits<double>::digits - 1)) - 1;
      static const int                                       min_range = std::numeric_limits<double>::digits >= std::numeric_limits<int>::digits ? (std::numeric_limits<int>::min)() : -(1 << (std::numeric_limits<double>::digits - 1)) + 1;
      static const boost::random::uniform_int_distribution<> i_val_dist(min_range, max_range);
      static const boost::random::uniform_int_distribution<> i_exp_dist(std::numeric_limits<double>::min_exponent, std::numeric_limits<double>::max_exponent - 2 - std::numeric_limits<int>::digits);
      int                                                    iv   = i_val_dist(small_gen);
      int                                                    eval = i_exp_dist(small_gen);
      double                                                 dv   = iv;
      dv                                                          = ldexp(dv, eval);
      cppr                                                        = dv;
      cppr2                                                       = iv;
      cpp_int cppi                                                = 1;
      cppi <<= abs(eval);
      if (eval < 0)
         cppr2 /= cppi;
      else
         cppr2 *= cppi;
      BOOST_CHECK_EQUAL(cppr, cppr2);
      //
      // Again but with bigger numbers:
      //
      cpp_int                      cppi2 = gen();
      number<cpp_bin_float<1030> > cppbf(cppi2);
      cppbf = ldexp(cppbf, eval);
      cppr.assign(cppbf);
      cppr2 = cppi2;
      if (eval < 0)
         cppr2 /= cppi;
      else
         cppr2 *= cppi;
      BOOST_CHECK_EQUAL(cppr, cppr2);
      //
      // MSVC will compile either the code above, or
      // the code below, but not both in the same file.
      // Other compilers including Intel in msvc-compatibity
      // mode have no such difficulty.  Indeed the fact that
      // the presence of the code below causes the code above to
      // fail to compile strongly suggests a compiler bug.
      //
#if !defined(BOOST_MSVC)
      //
      // Again but with bigger base 10 numbers:
      //
      number<cpp_dec_float<std::numeric_limits<int1024_t>::digits10 + 3> > cppdec2(cppi2);
      cppdec2 = scalbn(cppdec2, eval);
      cppr.assign(cppdec2);
      cppr2 = cppi2;
      cppi  = 10;
      cppi  = pow(cppi, abs(eval));
      if (eval < 0)
         cppr2 /= cppi;
      else
         cppr2 *= cppi;
      BOOST_CHECK_EQUAL(cppr, cppr2);
#endif
   }

   return boost::report_errors();
}


#include "ChiSquared.hxx"

#include <boost/format.hpp>
#include <boost/program_options.hpp>
#include <vector>
#include <iostream>
#include <fstream>
#include <map>

int main(int argc, char * argv[])
{
  std::string S_fname, X_fname;

  namespace po = boost::program_options;

  po::options_description desc("Options:");
  desc.add_options()
    ("help", "help message")
    ("standard", po::value<std::string>(&S_fname)->required(), "Histogram file for null hypothesis")
    ("experiment", po::value<std::string>(&X_fname)->required(), "Histogram file for experimental result");

  po::positional_options_description pos_opts ;
  pos_opts.add("standard", 1);
  pos_opts.add("experiment", 1);

  po::variables_map vm;

  std::string what_am_i("Calculate chi-squared significance from normative and experimental histograms");

  try {
    po::store(po::command_line_parser(argc, argv).options(desc)
	      .positional(pos_opts).run(), vm);

    if(vm.count("help")) {
      std::cout << what_am_i
		<< desc << std::endl;
      exit(-1);
    }

    po::notify(vm);
  }
  catch(po::required_option & e) {
    std::cerr << "ERROR: " << e.what() << std::endl << std::endl;
    std::cerr << "Filter WSPR logs by frequency range\n\t "
	      << desc << std::endl;
    exit(-1);
  }
  catch(po::error & e) {
    std::cerr << "ERROR: " << e.what() << std::endl << std::endl;
    std::cerr << "Filter WSPR logs by frequency range\n\t "
	      << desc << std::endl;
    exit(-1);
  }

  std::ifstream Sst(S_fname);
  std::ifstream Xst(X_fname);

  std::vector<int> S;
  std::vector<int> X;

  for(int n; Sst >> n; ) {
    S.push_back(n);
  }
  for(int n; Xst >> n; ) {
    X.push_back(n);
  }

  // now calculate the chi-sqared score

  std::cout << boost::format("X^2 = %g, significance = %g\n")
    % ChiSquared::pearsonCTS(S, X) % ChiSquared::test(S, X);
}


#include "game.hpp"

#include <tuple>
#include <random>
#include <iostream>
#include <boost/numeric/ublas/io.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include "../util.hpp"
#include "../shaders.hpp"

using namespace conway;

template<class T>
void fillMatrix(ublas::matrix<T> &matrix, const T value)
{
    for (auto itr = matrix.begin1(); itr != matrix.end1(); ++itr) {
        std::fill(itr.begin(), itr.end(), value);
    }
}

template<class T, class Generator>
void generateMatrix(ublas::matrix<T> &matrix, Generator gen)
{
    for (auto itr = matrix.begin1(); itr != matrix.end1(); ++itr) {
        std::generate(itr.begin(), itr.end(), gen);
    }
}

ConwaysGame::ConwaysGame(uint size_x, uint size_y, const glm::mat4 &projection) :
        size_x_(size_x), size_y_(size_y),
        current_grid_(size_x, size_y),
        alternate_grid_(size_x, size_y),
        renderer_(projection, glm::scale(glm::mat4(1.0f),
                  glm::vec3(1/(float)size_x, 1/(float)size_y, 0.0f))),
        is_dirty_(true)
{
    std::default_random_engine random_engine;
    std::uniform_int_distribution<int> distribution(0, 1);

    generateMatrix(current_grid_, [&](){
        return distribution(random_engine) ? State::alive : State::dead;});
    fillMatrix(alternate_grid_, State::dead);

    texture_ = Renderer::createTexture(size_x_, size_y_);
    writeToTexture(texture_);
}

ConwaysGame::~ConwaysGame()
{
    glDeleteTextures(1, &texture_);
}

glm::uvec2 ConwaysGame::getSize() const
{
    return {size_x_, size_y_};
}

void ConwaysGame::draw(GLuint vertex_array)
{
    if (is_dirty_) {
        writeToTexture(texture_);
        is_dirty_ = false;
    }

    renderer_.draw(vertex_array, texture_);
}

void ConwaysGame::writeToTexture(GLuint texture)
{
    // Flips the image along the x axis (texture origin is bottom left)  ¯\_(ツ)_/¯
    glTextureSubImage2D(texture, 0, 0, 0, size_x_, size_y_,
        GL_RED, GL_UNSIGNED_BYTE, current_grid_.data().begin());
}

void ConwaysGame::nextGeneration()
{
    for (auto itr = alternate_grid_.begin1(); itr != alternate_grid_.end1(); ++itr) {
        for (auto cell = itr.begin(); cell != itr.end(); ++cell) {
            int x = cell.index1(), y = cell.index2();
            *cell = nextState(current_grid_(x, y), countNeighbours(x, y));
        }
    }

    is_dirty_ = true;
    std::swap(current_grid_, alternate_grid_);
}

State ConwaysGame::nextState(State state, int neighbours)
{
    if (neighbours == 3) {
        return State::alive;
    } else if (neighbours == 2 && state == State::alive) {
        return State::alive;
    } else {
        return State::dead;
    }
}

int ConwaysGame::countNeighbours(int x, int y) const
{
    int neighbours = 0;

    for (int dx = -1; dx <= 1; ++dx) {
        for (int dy = -1; dy <= 1; ++dy) {
            if (dx == 0 && dy == 0)
                continue;

            const int xdx = x + dx, ydy = y + dy;
            if (0 <= xdx && xdx < size_x_ && 0 <= ydy && ydy < size_y_) {
                neighbours += current_grid_(xdx, ydy);
            }
        }
    }

    return neighbours;
}


//
// Created by Alex Beccaro on 20/12/17.
//
#ifndef PROJECT_EULER_TEMPLATE_CONDITIONS_H
#define PROJECT_EULER_TEMPLATE_CONDITIONS_H

#include <boost/multiprecision/cpp_int.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>

namespace template_conditions {

    template<typename T>
    struct is_any_integral : public std::integral_constant<bool,
            std::is_integral<T>::value ||
            std::is_convertible<T, boost::multiprecision::cpp_int>::value> {};

    template<typename T>
    struct is_any_number : public std::integral_constant<bool,
            is_any_integral<T>::value ||
            std::is_convertible<T, boost::multiprecision::number<boost::multiprecision::cpp_dec_float<
                    std::numeric_limits<unsigned int>::max()>>>::value> {};

}

#endif //PROJECT_EULER_TEMPLATE_CONDITIONS_H


// MIT License
//
// Copyright (c) 2019 worldwide-asset-exchange
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#pragma once

#include <cstddef>
#include <iterator>
#include <stdexcept>
#include <string>
#include <vector>

#include <boost/multiprecision/cpp_int.hpp>
#include <fc/crypto/sha256.hpp>

namespace wax {
    //// Simple RSA signer class
    class rsa_signer {
    public:
        /// @param private_exp Non empty hex string
        /// @param modulus Non empty hex string, the 1st char cannot be 0 (zero)
        /// @exception std::invalid_argument if strings are empty, non hex or
        ///            1st char in modulus is 0
        rsa_signer(const std::string& private_exp, const std::string& modulus)
            : private_exp_{ "0x" + check_emptiness(private_exp) }
            , modulus_{ "0x" + check_modulus(check_emptiness(modulus)) }
            , modulus_size_{ modulus.size() } {
        }

        /// @param message Message/Data to sign
        /// @return Signed message as hex string
        /// @exception std::invalid_argument if message is empty
        std::string sign(const std::string& message) const {
            using namespace boost::multiprecision;

            check_emptiness(message);
            fc::sha256 message_hash(fc::sha256::hash(message.data(), message.size()));

            std::string pkcs1_encoding =
                pkcs1_encode(modulus_size_, bytes_to_string(message_hash.data(), message_hash.data_size()));

            cpp_int pkcs1_encoding_big{ "0x" + pkcs1_encoding };
            cpp_int modexp = powm(pkcs1_encoding_big, private_exp_, modulus_);

            std::vector<char> result;
            export_bits(modexp, std::back_inserter(result), 8);

            return bytes_to_string(result.data(), result.size());
        }

        /// @todo Add getters/setter for private_exp and modulus

    // Implementation
    private:
        boost::multiprecision::cpp_int private_exp_;
        boost::multiprecision::cpp_int modulus_;
        std::size_t                    modulus_size_;

        static std::string bytes_to_string(const char* in, std::size_t size) {
            std::string out;
            const char* hex = "0123456789abcdef";
            for (std::size_t i = 0; i < size; i++) {
                out += hex[(in[i]>>4) & 0xF];
                out += hex[in[i] & 0xF];
            }
            return out;
        }

        static const std::string& check_emptiness(const std::string& str) {
            if (str.empty()) throw std::invalid_argument("String cannot be empty");
            return str;
        }

        static const std::string& check_modulus(const std::string& modulus) {
            if (modulus[0] == '0') throw std::invalid_argument("No leading zeroes allowed in modulus");
            return modulus;
        }

        /**
         * @dev Generates the pkcs1 encoding for an already hashed (via sha256) message.
         * @param modulus_length [size_t] the length in hex digits of the modulus, not including any leading zeroes
         * @param message_hash [string] the sha256 hash of the message intended for signing over
         * @returns [string] the pkcs1 encoding for the message hash given the modulus length
         *
         * For more on RSA signatures, see:
         * https://www.emc.com/collateral/white-papers/h11300-pkcs-1v2-2-rsa-cryptography-standard-wp.pdf
         * Page 39 is extra useful for understanding the padding scheme
         */
        static std::string pkcs1_encode(std::size_t modulus_length, const std::string& message_hash) {
            // pkcs1 padding constant indicating sha256 was used as the message digest for signing
            const char* PKCS1_SHA256 = "003031300d060960864801650304020105000420";

            // Prepend the hash type identifier for sha256
            std::string pkcs1_encoding = PKCS1_SHA256 + message_hash;

            // Prepend all f's and a starting 1 so that the final result is as long as the modulus, minus 3
            pkcs1_encoding = std::string(modulus_length - pkcs1_encoding.size() - 3, 'f').append(pkcs1_encoding);

            // First byte must be 1
            pkcs1_encoding[0] = '1';
            return pkcs1_encoding;
        }

    }; // class rsa_signer

} // namespace wax


// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2014 Benoit Steiner <benoit.steiner.goog@gmail.com>
//
// 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 <numeric>

#include "main.h"

#include <Eigen/CXX11/Tensor>

using Eigen::Tensor;
using Eigen::RowMajor;

static void test_1d()
{
  Tensor<float, 1> vec1(6);
  Tensor<float, 1, RowMajor> vec2(6);

  vec1(0) = 4.0;  vec2(0) = 0.0;
  vec1(1) = 8.0;  vec2(1) = 1.0;
  vec1(2) = 15.0; vec2(2) = 2.0;
  vec1(3) = 16.0; vec2(3) = 3.0;
  vec1(4) = 23.0; vec2(4) = 4.0;
  vec1(5) = 42.0; vec2(5) = 5.0;

  float data3[6];
  TensorMap<Tensor<float, 1>> vec3(data3, 6);
  vec3 = vec1.sqrt();
  float data4[6];
  TensorMap<Tensor<float, 1, RowMajor>> vec4(data4, 6);
  vec4 = vec2.square();
  float data5[6];
  TensorMap<Tensor<float, 1, RowMajor>> vec5(data5, 6);
  vec5 = vec2.cube();

  VERIFY_IS_APPROX(vec3(0), sqrtf(4.0));
  VERIFY_IS_APPROX(vec3(1), sqrtf(8.0));
  VERIFY_IS_APPROX(vec3(2), sqrtf(15.0));
  VERIFY_IS_APPROX(vec3(3), sqrtf(16.0));
  VERIFY_IS_APPROX(vec3(4), sqrtf(23.0));
  VERIFY_IS_APPROX(vec3(5), sqrtf(42.0));

  VERIFY_IS_APPROX(vec4(0), 0.0f);
  VERIFY_IS_APPROX(vec4(1), 1.0f);
  VERIFY_IS_APPROX(vec4(2), 2.0f * 2.0f);
  VERIFY_IS_APPROX(vec4(3), 3.0f * 3.0f);
  VERIFY_IS_APPROX(vec4(4), 4.0f * 4.0f);
  VERIFY_IS_APPROX(vec4(5), 5.0f * 5.0f);

  VERIFY_IS_APPROX(vec5(0), 0.0f);
  VERIFY_IS_APPROX(vec5(1), 1.0f);
  VERIFY_IS_APPROX(vec5(2), 2.0f * 2.0f * 2.0f);
  VERIFY_IS_APPROX(vec5(3), 3.0f * 3.0f * 3.0f);
  VERIFY_IS_APPROX(vec5(4), 4.0f * 4.0f * 4.0f);
  VERIFY_IS_APPROX(vec5(5), 5.0f * 5.0f * 5.0f);

  vec3 = vec1 + vec2;
  VERIFY_IS_APPROX(vec3(0), 4.0f + 0.0f);
  VERIFY_IS_APPROX(vec3(1), 8.0f + 1.0f);
  VERIFY_IS_APPROX(vec3(2), 15.0f + 2.0f);
  VERIFY_IS_APPROX(vec3(3), 16.0f + 3.0f);
  VERIFY_IS_APPROX(vec3(4), 23.0f + 4.0f);
  VERIFY_IS_APPROX(vec3(5), 42.0f + 5.0f);
}

static void test_2d()
{
  float data1[6];
  TensorMap<Tensor<float, 2>> mat1(data1, 2, 3);
  float data2[6];
  TensorMap<Tensor<float, 2, RowMajor>> mat2(data2, 2, 3);

  mat1(0,0) = 0.0;
  mat1(0,1) = 1.0;
  mat1(0,2) = 2.0;
  mat1(1,0) = 3.0;
  mat1(1,1) = 4.0;
  mat1(1,2) = 5.0;

  mat2(0,0) = -0.0;
  mat2(0,1) = -1.0;
  mat2(0,2) = -2.0;
  mat2(1,0) = -3.0;
  mat2(1,1) = -4.0;
  mat2(1,2) = -5.0;

  Tensor<float, 2> mat3(2,3);
  Tensor<float, 2, RowMajor> mat4(2,3);
  mat3 = mat1.abs();
  mat4 = mat2.abs();

  VERIFY_IS_APPROX(mat3(0,0), 0.0f);
  VERIFY_IS_APPROX(mat3(0,1), 1.0f);
  VERIFY_IS_APPROX(mat3(0,2), 2.0f);
  VERIFY_IS_APPROX(mat3(1,0), 3.0f);
  VERIFY_IS_APPROX(mat3(1,1), 4.0f);
  VERIFY_IS_APPROX(mat3(1,2), 5.0f);

  VERIFY_IS_APPROX(mat4(0,0), 0.0f);
  VERIFY_IS_APPROX(mat4(0,1), 1.0f);
  VERIFY_IS_APPROX(mat4(0,2), 2.0f);
  VERIFY_IS_APPROX(mat4(1,0), 3.0f);
  VERIFY_IS_APPROX(mat4(1,1), 4.0f);
  VERIFY_IS_APPROX(mat4(1,2), 5.0f);
}

static void test_3d()
{
  Tensor<float, 3> mat1(2,3,7);
  Tensor<float, 3, RowMajor> mat2(2,3,7);

  float val = 1.0f;
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 7; ++k) {
        mat1(i,j,k) = val;
        mat2(i,j,k) = val;
        val += 1.0f;
      }
    }
  }

  Tensor<float, 3> mat3(2,3,7);
  mat3 = mat1 + mat1;
  Tensor<float, 3, RowMajor> mat4(2,3,7);
  mat4 = mat2 * 3.14f;
  Tensor<float, 3> mat5(2,3,7);
  mat5 = mat1.inverse().log();
  Tensor<float, 3, RowMajor> mat6(2,3,7);
  mat6 = mat2.pow(0.5f) * 3.14f;
  Tensor<float, 3> mat7(2,3,7);
  mat7 = mat1.cwiseMax(mat5 * 2.0f).exp();
  Tensor<float, 3, RowMajor> mat8(2,3,7);
  mat8 = (-mat2).exp() * 3.14f;
  Tensor<float, 3, RowMajor> mat9(2,3,7);
  mat9 = mat2 + 3.14f;
  Tensor<float, 3, RowMajor> mat10(2,3,7);
  mat10 = mat2 - 3.14f;
  Tensor<float, 3, RowMajor> mat11(2,3,7);
  mat11 = mat2 / 3.14f;

  val = 1.0f;
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 7; ++k) {
        VERIFY_IS_APPROX(mat3(i,j,k), val + val);
        VERIFY_IS_APPROX(mat4(i,j,k), val * 3.14f);
        VERIFY_IS_APPROX(mat5(i,j,k), logf(1.0f/val));
        VERIFY_IS_APPROX(mat6(i,j,k), sqrtf(val) * 3.14f);
        VERIFY_IS_APPROX(mat7(i,j,k), expf((std::max)(val, mat5(i,j,k) * 2.0f)));
        VERIFY_IS_APPROX(mat8(i,j,k), expf(-val) * 3.14f);
        VERIFY_IS_APPROX(mat9(i,j,k), val + 3.14f);
        VERIFY_IS_APPROX(mat10(i,j,k), val - 3.14f);
        VERIFY_IS_APPROX(mat11(i,j,k), val / 3.14f);
        val += 1.0f;
      }
    }
  }
}

static void test_constants()
{
  Tensor<float, 3> mat1(2,3,7);
  Tensor<float, 3> mat2(2,3,7);
  Tensor<float, 3> mat3(2,3,7);

  float val = 1.0f;
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 7; ++k) {
        mat1(i,j,k) = val;
        val += 1.0f;
      }
    }
  }
  mat2 = mat1.constant(3.14f);
  mat3 = mat1.cwiseMax(7.3f).exp();

  val = 1.0f;
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 7; ++k) {
        VERIFY_IS_APPROX(mat2(i,j,k), 3.14f);
        VERIFY_IS_APPROX(mat3(i,j,k), expf((std::max)(val, 7.3f)));
        val += 1.0f;
      }
    }
  }
}

static void test_boolean()
{
  const int kSize = 31;
  Tensor<int, 1> vec(kSize);
  std::iota(vec.data(), vec.data() + kSize, 0);

  // Test ||.
  Tensor<bool, 1> bool1 = vec < vec.constant(1) || vec > vec.constant(4);
  for (int i = 0; i < kSize; ++i) {
    bool expected = i < 1 || i > 4;
    VERIFY_IS_EQUAL(bool1[i], expected);
  }

  // Test &&, including cast of operand vec.
  Tensor<bool, 1> bool2 = vec.cast<bool>() && vec < vec.constant(4);
  for (int i = 0; i < kSize; ++i) {
    bool expected = bool(i) && i < 4;
    VERIFY_IS_EQUAL(bool2[i], expected);
  }

  // Compilation tests:
  // Test Tensor<bool> against results of cast or comparison; verifies that
  // CoeffReturnType is set to match Op return type of bool for Unary and Binary
  // Ops.
  Tensor<bool, 1> bool3 = vec.cast<bool>() && bool2;
  bool3 = vec < vec.constant(4) && bool2;
}

static void test_functors()
{
  Tensor<float, 3> mat1(2,3,7);
  Tensor<float, 3> mat2(2,3,7);
  Tensor<float, 3> mat3(2,3,7);

  float val = 1.0f;
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 7; ++k) {
        mat1(i,j,k) = val;
        val += 1.0f;
      }
    }
  }
  mat2 = mat1.inverse().unaryExpr(&asinf);
  mat3 = mat1.unaryExpr(&tanhf);

  val = 1.0f;
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 7; ++k) {
        VERIFY_IS_APPROX(mat2(i,j,k), asinf(1.0f / mat1(i,j,k)));
        VERIFY_IS_APPROX(mat3(i,j,k), tanhf(mat1(i,j,k)));
        val += 1.0f;
      }
    }
  }
}

static void test_type_casting()
{
  Tensor<bool, 3> mat1(2,3,7);
  Tensor<float, 3> mat2(2,3,7);
  Tensor<double, 3> mat3(2,3,7);
  mat1.setRandom();
  mat2.setRandom();

  mat3 = mat1.cast<double>();
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 7; ++k) {
        VERIFY_IS_APPROX(mat3(i,j,k), mat1(i,j,k) ? 1.0 : 0.0);
      }
    }
  }

  mat3 = mat2.cast<double>();
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 7; ++k) {
        VERIFY_IS_APPROX(mat3(i,j,k), static_cast<double>(mat2(i,j,k)));
      }
    }
  }
}

static void test_select()
{
  Tensor<float, 3> selector(2,3,7);
  Tensor<float, 3> mat1(2,3,7);
  Tensor<float, 3> mat2(2,3,7);
  Tensor<float, 3> result(2,3,7);

  selector.setRandom();
  mat1.setRandom();
  mat2.setRandom();
  result = (selector > selector.constant(0.5f)).select(mat1, mat2);

  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 7; ++k) {
        VERIFY_IS_APPROX(result(i,j,k), (selector(i,j,k) > 0.5f) ? mat1(i,j,k) : mat2(i,j,k));
      }
    }
  }
}

template <typename Scalar>
void test_minmax_nan_propagation_templ() {
  for (int size = 1; size < 17; ++size) {
    const Scalar kNan = std::numeric_limits<Scalar>::quiet_NaN();
    Tensor<Scalar, 1> vec_nan(size);
    Tensor<Scalar, 1> vec_zero(size);
    Tensor<Scalar, 1> vec_res(size);
    vec_nan.setConstant(kNan);
    vec_zero.setZero();
    vec_res.setZero();

    // Test that we propagate NaNs in the tensor when applying the
    // cwiseMax(scalar) operator, which is used for the Relu operator.
    vec_res = vec_nan.cwiseMax(Scalar(0));
    for (int i = 0; i < size; ++i) {
      VERIFY((numext::isnan)(vec_res(i)));
    }

    // Test that NaNs do not propagate if we reverse the arguments.
    vec_res = vec_zero.cwiseMax(kNan);
    for (int i = 0; i < size; ++i) {
      VERIFY_IS_EQUAL(vec_res(i), Scalar(0));
    }

    // Test that we propagate NaNs in the tensor when applying the
    // cwiseMin(scalar) operator.
    vec_res.setZero();
    vec_res = vec_nan.cwiseMin(Scalar(0));
    for (int i = 0; i < size; ++i) {
      VERIFY((numext::isnan)(vec_res(i)));
    }

    // Test that NaNs do not propagate if we reverse the arguments.
    vec_res = vec_zero.cwiseMin(kNan);
    for (int i = 0; i < size; ++i) {
      VERIFY_IS_EQUAL(vec_res(i), Scalar(0));
    }
  }
}

static void test_clip()
{
  Tensor<float, 1> vec(6);
  vec(0) = 4.0;
  vec(1) = 8.0;
  vec(2) = 15.0;
  vec(3) = 16.0;
  vec(4) = 23.0;
  vec(5) = 42.0;

  float kMin = 20;
  float kMax = 30;

  Tensor<float, 1> vec_clipped(6);
  vec_clipped = vec.clip(kMin, kMax);
  for (int i = 0; i < 6; ++i) {
    VERIFY_IS_EQUAL(vec_clipped(i), numext::mini(numext::maxi(vec(i), kMin), kMax));
  }
}

static void test_minmax_nan_propagation()
{
  test_minmax_nan_propagation_templ<float>();
  test_minmax_nan_propagation_templ<double>();
}

EIGEN_DECLARE_TEST(cxx11_tensor_expr)
{
  CALL_SUBTEST(test_1d());
  CALL_SUBTEST(test_2d());
  CALL_SUBTEST(test_3d());
  CALL_SUBTEST(test_constants());
  CALL_SUBTEST(test_boolean());
  CALL_SUBTEST(test_functors());
  CALL_SUBTEST(test_type_casting());
  CALL_SUBTEST(test_select());
  CALL_SUBTEST(test_clip());

// Nan propagation does currently not work like one would expect from std::max/std::min,
// so we disable it for now
#if !EIGEN_ARCH_ARM_OR_ARM64
  CALL_SUBTEST(test_minmax_nan_propagation());
#endif
}


#include "scan_matching_benchmark/test_set_generator.h"

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <boost/filesystem.hpp>
#include <cartographer/sensor/voxel_filter.h>

TestSetGenerator::TestSetGenerator():
  resolution_(0.1) {}

TestSetGenerator::TestSetGenerator(float resolution):
  resolution_(resolution){}

void TestSetGenerator::generateCube(cartographer::sensor::PointCloud& cloud, float size) {
  cloud.clear();
  float min_xyz = -size / 2.0;
  float max_xyz = size / 2.0;

  for(float x = min_xyz; x <= max_xyz; x += resolution_) {
    for(float y = min_xyz; y <= max_xyz; y += resolution_) {
      float z = min_xyz;
      cloud.emplace_back(Eigen::Vector3f(x, y, z));
      z = max_xyz;
      cloud.emplace_back(Eigen::Vector3f(x, y, z));
    }
  }
  for(float x = min_xyz; x <= max_xyz; x += resolution_) {
    for(float z = min_xyz; z <= max_xyz; z += resolution_) {
      float y = min_xyz;
      cloud.emplace_back(Eigen::Vector3f(x, y, z));
      y = max_xyz;
      cloud.emplace_back(Eigen::Vector3f(x, y, z));
    }
  }

  for(float y = min_xyz; y <= max_xyz; y += resolution_) {
    for(float z = min_xyz; z <= max_xyz; z += resolution_) {
      float x = min_xyz;
      cloud.emplace_back(Eigen::Vector3f(x, y, z));
      x = max_xyz;
      cloud.emplace_back(Eigen::Vector3f(x, y, z));
    }
  }
}

void TestSetGenerator::generateCuboid(cartographer::sensor::PointCloud& cloud, float size_x, float size_y, float size_z) {
  std::random_device r;
  std::default_random_engine e1(r());
  std::normal_distribution<float> normal_distribution(0, 0.01);

  cloud.clear();
  float min_x = -size_x / 2.0;
  float max_x = size_x / 2.0;
  float min_y = -size_y / 2.0;
  float max_y = size_y / 2.0;
  float min_z = -size_z / 2.0;
  float max_z = size_z / 2.0;

  for(float x = min_x; x <= max_x; x += resolution_) {
    for(float y = min_y; y <= max_y; y += resolution_) {
      float z = min_z;
      cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
      z = max_z;
      cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
    }
  }
  for(float x = min_x; x <= max_x; x += resolution_) {
    for(float z = min_z; z <= max_z; z += resolution_) {
      float y = min_y;
      cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
      y = max_y;
      cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
    }
  }

  for(float y = min_y; y <= max_y; y += resolution_) {
    for(float z = min_z; z <= max_z; z += resolution_) {
      float x = min_x;
      cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
      x = max_x;
      cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
    }
  }
}

void TestSetGenerator::generateCylinder(cartographer::sensor::PointCloud& cloud, float radius, float height) {
  std::random_device r;
  std::default_random_engine e1(r());
  std::normal_distribution<float> normal_distribution(0, 0.01);

  cloud.clear();
  float min_z = -height / 2.0;
  float max_z = height / 2.0;
  float angular_resolution = 2.0 * std::asin(resolution_ / (2 * radius));

  for(float x = - radius; x <= radius; x += resolution_) {
    for(float y = - radius; y <= radius; y += resolution_) {
      if(x*x+y*y < radius*radius)
      {
        float z = min_z;
        cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
        z = max_z;
        cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
      }
    }
  }

  for(float alpha = 0; alpha < 2.0 * M_PI; alpha += angular_resolution) {
    for(float z = min_z; z <= max_z; z += resolution_) {
      float x = std::cos(alpha) * radius;
      float y = std::sin(alpha) * radius;
      cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
    }
  }
}

void TestSetGenerator::generateHalfCylinderHalfCube(cartographer::sensor::PointCloud& cloud, float size_x, float size_y, float size_z) {

  std::random_device r;
  std::default_random_engine e1(r());
  std::normal_distribution<float> normal_distribution(0, 0.01);

  cloud.clear();
  float min_x = -size_x / 2.0;
  float max_x = size_x / 2.0;
  float min_y = -size_y / 2.0;
  float max_y = size_y / 2.0;
  float min_z = -size_z / 2.0;
  float max_z = size_z / 2.0;

  //Half Cube x=>0
  for(float x = 0; x <= max_x; x += resolution_) {
    for(float y = min_y; y <= max_y; y += resolution_) {
      float z = min_z;
      cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
      z = max_z;
      cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
    }
  }
  for(float x = 0; x <= max_x; x += resolution_) {
    for(float z = min_z; z <= max_z; z += resolution_) {
      float y = min_y;
      cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
      y = max_y;
      cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
    }
  }

  for(float y = min_y; y <= max_y; y += resolution_) {
    for(float z = min_z; z <= max_z; z += resolution_) {
      float x = max_x;
      cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
    }
  }

  //Half Cylinder x<=0
  float angular_resolution = 2.0 * std::asin(resolution_ / (size_x));
  float radius = 0.5 * size_x;

  for(float x = - radius; x < 0; x += resolution_) {
    for(float y = - radius; y <= radius; y += resolution_) {
      if(x*x+y*y < radius*radius)
      {
        float z = min_z;
        cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
        z = max_z;
        cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
      }
    }
  }

  for(float alpha = 0; alpha < 2.0 * M_PI; alpha += angular_resolution) {
    for(float z = min_z; z <= max_z; z += resolution_) {
      float x = std::cos(alpha) * radius;
      float y = std::sin(alpha) * radius;
      if(x < 0)
        cloud.emplace_back(Eigen::Vector3f(x + normal_distribution(e1), y + normal_distribution(e1), z + normal_distribution(e1)));
    }
  }

}

void loadPCD(cartographer::sensor::PointCloud& cloud, std::string dir)
{

  pcl::PointCloud<pcl::PointXYZ>::Ptr pcl_cloud (new pcl::PointCloud<pcl::PointXYZ>);
  if (pcl::io::loadPCDFile<pcl::PointXYZ> (/*"/home/kevin/Downloads/gas_station_benchmark/423.043000000.pcd"*/dir, *pcl_cloud) == -1) //* load the file
  {
    PCL_ERROR ("Couldn't read file test_pcd.pcd \n");
    return ;
  }
  std::cout << "Loaded "
            << pcl_cloud->width * pcl_cloud->height
            << " data points from " << dir<< std::endl;
  for (size_t i = 0; i < pcl_cloud->points.size (); ++i) {
    cloud.push_back(Eigen::Vector3f({pcl_cloud->points[i].x, pcl_cloud->points[i].y, pcl_cloud->points[i].z}));
  }

}

void TestSetGenerator::loadPCDDir(cartographer::sensor::PointCloud& cloud, std::string dir) {
  cloud.clear();

  cartographer::sensor::PointCloud cloud_unfiltered;
  boost::filesystem::path p ("/home/kevin/Downloads/gas_station_benchmark/");

  try
  {
    if (boost::filesystem::exists(p))    // does p actually exist?
    {
      if (boost::filesystem::is_regular_file(p))        // is p a regular file?
        std::cout << p << " size is " << boost::filesystem::file_size(p) << '\n';

      else if (boost::filesystem::is_directory(p))      // is p a directory?
      {
        std::cout << "Reading files from " << p;
        for(boost::filesystem::directory_iterator it(p);
            it != boost::filesystem::directory_iterator();
            it++)
        {
          if (it->path().extension() == ".pcd") {
            loadPCD(cloud_unfiltered, it->path().string());
          }
        }
      }

      else
        std::cout << p << " exists, but is neither a regular file nor a directory\n";
    }
    else
      std::cout << p << " does not exist\n";
  }

  catch (const boost::filesystem::filesystem_error& ex)
  {
    std::cout << ex.what() << '\n';
  }

  cloud = cartographer::sensor::VoxelFiltered(cloud_unfiltered, 0.05);
  std::cout << "filter: " << cloud_unfiltered.size() <<" "<< cloud.size()<<std::endl;;

  // return (0);

}