// Copyright (c) 2019 Graphcore Ltd. All rights reserved.
#include <boost/multi_array.hpp>
#include <cassert>
#include <poplibs_test/GeneralMatrixAdd.hpp>
#include <poplibs_test/GeneralMatrixMultiply.hpp>
#include <poplibs_test/Gru.hpp>
#include <poplibs_test/NonLinearity.hpp>
#include <unordered_map>

//#define DEBUG_TENSOR

using IndexRange = boost::multi_array_types::index_range;
using Array1dRef = boost::multi_array_ref<double, 1>;
using Array1dRefINT = boost::multi_array_ref<int, 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_test;

// Fwd state array indices
#define GRU_FWD_STATE_RESET_GATE 0
#define GRU_FWD_STATE_UPDATE_GATE 1
#define GRU_FWD_STATE_CANDIDATE 2
#define GRU_FWD_STATE_OUTPUT 3

#define GRU_BWD_STATE_RESET_GATE 0
#define GRU_BWD_STATE_UPDATE_GATE 1
#define GRU_BWD_STATE_CANDIDATE 2

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

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

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

/**
 * Process a given unit type within an GRU given its weights and biases.
 * The non-linearity is also specified although it may be derived from the unit
 */
static void processBasicGruUnit(const Array2dRef prevOutput,
                                const Array2dRef input,
                                const Array3dRef weightsInput,
                                const Array3dRef weightsOutput,
                                const Array2dRef biases, Array2dRef output,
                                unsigned gruUnitOffset,
                                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[gruUnitOffset];
  Array2d weightsInputUnit = weightsInput[gruUnitOffset];

  gemm::generalMatrixMultiply(prevOutput, weightsOutputUnit, output, 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[gruUnitOffset][i];
    }
  }

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

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

  return cellMapping;
}

/**
 * Apply mask to gates.
 */
static void applySeqMask(const boost::optional<Array1dRefINT> &realTimeSteps,
                         const int step, bool outputFullSequence,
                         Array2dRef ionput) {
  if (realTimeSteps) {
    assert(outputFullSequence);

    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) {
        if ((*realTimeSteps)[b] <= step) {
          ionput[b][i] = 0;
        }
      }
    }
  }
}

/**
 * Apply attention to update gate.
 */
static void applyAtt(const boost::optional<Array2dRef> attScores,
                     const int step, Array2dRef ionput) {
  if (attScores) {
    const auto batchSize = ionput.shape()[0];
    const auto outputSize = ionput.shape()[1];
    for (auto b = 0U; b != batchSize; ++b) {
      auto score = 1.0 - (*attScores)[b][step];
      for (auto i = 0U; i != outputSize; ++i) {
        ionput[b][i] *= score;
      }
    }
  }
}

static void applyAttBwd(const boost::optional<Array2dRef> attScores,
                        const Array2dRef u, const int step, Array2dRef d_u,
                        Array2dRef u0, Array2dRef attGrad) {
  if (attScores) {
    const auto batchSize = d_u.shape()[0];
    const auto outputSize = d_u.shape()[1];
    for (auto b = 0U; b != batchSize; ++b) {
      auto d_u_scale = 1.0f - (*attScores)[b][step];
      auto u0_scale = 1.0 / d_u_scale;
      attGrad[b][step] = 0;
      for (auto i = 0U; i != outputSize; ++i) {
        u0[b][i] = u[b][i] * u0_scale;
        attGrad[b][step] -= u0[b][i] * d_u[b][i];
        d_u[b][i] *= d_u_scale;
      }
    }
  }
}

static void printMatrix2d(FILE *fp, std::string msg, Array2dRef in) {
  if (!fp)
    return;

  fprintf(fp, "%s: {\n", msg.c_str());
  unsigned matRows = in.shape()[0];
  unsigned matCols = in.shape()[1];

  for (auto r = 0U; r != matRows; ++r) {
    fprintf(fp, " {");
    for (auto c = 0U; c != matCols; ++c) {
      if (c != matCols - 1)
        fprintf(fp, "%f,", in[r][c]);
      else
        fprintf(fp, "%f}\n", in[r][c]);
    }
  }
  fprintf(fp, "}\n");
}

static void printMatrix3d(FILE *fp, std::string msg, Array3dRef in) {
  if (!fp)
    return;

  fprintf(fp, "%s: {\n", msg.c_str());
  unsigned matRows = in.shape()[0];
  unsigned matCols = in.shape()[1];
  unsigned matInner = in.shape()[2];

  for (auto r = 0U; r != matRows; ++r) {
    fprintf(fp, " {\n");
    for (auto c = 0U; c != matCols; ++c) {
      fprintf(fp, "  {");
      for (auto i = 0U; i != matInner; ++i) {
        if (i != matInner - 1)
          fprintf(fp, "%f,", in[r][c][i]);
        else
          fprintf(fp, "%f}\n", in[r][c][i]);
      }
    }
    fprintf(fp, " }\n");
  }
  fprintf(fp, "}\n");
}

void poplibs_test::gru::basicGruCellForwardPass(
    bool outputFullSequence, const Array3dRef input, const Array2dRef biases,
    const Array2dRef prevOutput, const Array3dRef weightsInput,
    const Array3dRef weightsOutput,
    const boost::optional<boost::multi_array_ref<double, 2>> &attScoresOpt,
    const boost::optional<boost::multi_array_ref<int, 1>> &realTimeStepsOpt,
    Array4dRef state, const std::vector<BasicGruCellUnit> &cellOrder,
    bool resetAfter, const boost::optional<Array2dRef> recurrantBiases) {
  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] == GRU_NUM_FWD_STATES);
  assert(weightsInput.shape()[0] == BASIC_GRU_CELL_NUM_UNITS);
  assert(weightsInput.shape()[1] == inputSize);
  assert(weightsInput.shape()[2] == outputSize);
  assert(weightsOutput.shape()[0] == BASIC_GRU_CELL_NUM_UNITS);
  assert(weightsOutput.shape()[1] == outputSize);
  assert(weightsOutput.shape()[2] == outputSize);
  assert(biases.shape()[0] == BASIC_GRU_CELL_NUM_UNITS);
  assert(biases.shape()[1] == outputSize);
  assert(prevOutput.shape()[0] == batchSize);
  assert(prevOutput.shape()[1] == outputSize);
  assert(recurrantBiases.is_initialized() == resetAfter);
  if (resetAfter) {
    assert(recurrantBiases.get().shape()[0] == BASIC_GRU_CELL_NUM_UNITS);
    assert(recurrantBiases.get().shape()[1] == outputSize);
  }

  auto cellMapping = getCellMapping(cellOrder);

  FILE *fp = NULL;
#ifdef DEBUG_TENSOR
  fp = fopen("fwd.txt", "w");
#endif

  printMatrix3d(fp, "fwd weightsInput", weightsInput);
  printMatrix3d(fp, "fwd weightsOutput", weightsOutput);
  printMatrix2d(fp, "fwd bias", biases);
  if (resetAfter) {
    printMatrix2d(fp, "fwd recurrant bias", recurrantBiases.get());
  }

  Array2d totalBiases(boost::extents[BASIC_GRU_CELL_NUM_UNITS][outputSize]);
  if (resetAfter) {
    /* Sum biases for use in update gate and reset gate */
    poplibs_test::axpby::add(biases, recurrantBiases.get(), totalBiases);
  }

  for (auto s = 0U; s != sequenceSize; ++s) {
    if (fp)
      fprintf(fp, "fwd Loop: {%d}\n", s);
    Array2d ysm1 = s == 0 ? state[GRU_FWD_STATE_ACTS_IDX][s]
                          : state[GRU_FWD_STATE_ACTS_IDX][s - 1];
    Array2d prevOutputThisStep = s == 0 ? prevOutput : ysm1;
    Array2d inputThisStep = input[s];

    printMatrix2d(fp, "fwd h_prev", prevOutputThisStep);
    printMatrix2d(fp, "fwd input", inputThisStep);

    /* update gate */
    Array2d updateGate(boost::extents[batchSize][outputSize]);
    processBasicGruUnit(prevOutputThisStep, inputThisStep, weightsInput,
                        weightsOutput, (resetAfter ? totalBiases : biases),
                        updateGate, cellMapping.at(BASIC_GRU_CELL_UPDATE_GATE),
                        popnn::NonLinearityType::SIGMOID);
    applyAtt(attScoresOpt, s, updateGate);
    applySeqMask(realTimeStepsOpt, s, outputFullSequence, updateGate);
    state[GRU_FWD_STATE_UPDATE_GATE_IDX][s] = updateGate;

    /* reset gate */
    Array2d resetGate(boost::extents[batchSize][outputSize]);
    processBasicGruUnit(prevOutputThisStep, inputThisStep, weightsInput,
                        weightsOutput, (resetAfter ? totalBiases : biases),
                        resetGate, cellMapping.at(BASIC_GRU_CELL_RESET_GATE),
                        popnn::NonLinearityType::SIGMOID);
    applySeqMask(realTimeStepsOpt, s, outputFullSequence, resetGate);
    state[GRU_FWD_STATE_RESET_GATE_IDX][s] = resetGate;

    /* candidate */
    Array2d candidate(boost::extents[batchSize][outputSize]);
    Array2d tmp1(boost::extents[batchSize][outputSize]);

    if (resetAfter) {
      auto candidateOffset = cellMapping.at(BASIC_GRU_CELL_CANDIDATE);
      /* apply weights to previous output */
      Array2d &&w_c_out = weightsOutput[candidateOffset];
      poplibs_test::gemm::generalMatrixMultiply(prevOutputThisStep, w_c_out,
                                                tmp1, false, false);
      /* add recurrant bias */
      for (auto b = 0U; b != batchSize; ++b) {
        for (auto i = 0U; i != outputSize; ++i) {
          tmp1[b][i] += recurrantBiases.get()[candidateOffset][i];
        }
      }
      /* apply reset gate */
      poplibs_test::gemm::hadamardProduct(resetGate, tmp1, candidate);
      /* apply weights to input */
      Array2d &&w_c_in = weightsInput[candidateOffset];
      poplibs_test::gemm::generalMatrixMultiply(
          inputThisStep, w_c_in, candidate, candidate, 1.0, 1.0, false, false);
      /* add bias */
      for (auto b = 0U; b != batchSize; ++b) {
        for (auto i = 0U; i != outputSize; ++i) {
          candidate[b][i] += biases[candidateOffset][i];
        }
      }
      /* apply non-linearity */
      nonLinearity(popnn::NonLinearityType::TANH, candidate);
    } else {
      poplibs_test::gemm::hadamardProduct(resetGate, prevOutputThisStep, tmp1);
      processBasicGruUnit(tmp1, inputThisStep, weightsInput, weightsOutput,
                          (resetAfter ? totalBiases : biases), candidate,
                          cellMapping.at(BASIC_GRU_CELL_CANDIDATE),
                          popnn::NonLinearityType::TANH);
    }
    applySeqMask(realTimeStepsOpt, s, outputFullSequence, candidate);

    state[GRU_FWD_STATE_CANDIDATE_IDX][s] = candidate;

    printMatrix2d(fp, "fwd resetGate", resetGate);
    printMatrix2d(fp, "fwd updateGate", updateGate);
    printMatrix2d(fp, "fwd candidate", candidate);

    /* output */
    Array2d matOne(boost::extents[batchSize][outputSize]);
    matrixOne(matOne);

    Array2d updateGateComp(boost::extents[batchSize][outputSize]);
    poplibs_test::axpby::add(matOne, updateGate, updateGateComp, 1.0, -1.0);
    Array2d s1(boost::extents[batchSize][outputSize]);
    Array2d s2(boost::extents[batchSize][outputSize]);
    poplibs_test::gemm::hadamardProduct(updateGate, prevOutputThisStep, s1);
    poplibs_test::gemm::hadamardProduct(updateGateComp, candidate, s2);

    Array2d outputThisStep(boost::extents[batchSize][outputSize]);
    poplibs_test::axpby::add(s1, s2, outputThisStep);
    applySeqMask(realTimeStepsOpt, s, outputFullSequence, outputThisStep);

    state[GRU_FWD_STATE_ACTS_IDX][s] = outputThisStep;
    printMatrix2d(fp, "fwd output", outputThisStep);
  }
  if (fp)
    fclose(fp);
}

static Array2d getSlice(Array2d &in, int offset, int size) {
  int batchSize = in.shape()[0];

  Array2d out(boost::extents[batchSize][size]);
  for (int i = 0; i < batchSize; i++) {
    for (int j = 0; j < size; j++) {
      out[i][j] = in[i][j + offset];
    }
  }

  return out;
}

static Array2d concatMatrix2D(const Array2d matA, const Array2d matB,
                              int dimension) {
  const auto matARows = matA.shape()[0];
  const auto matACols = matA.shape()[1];

  const auto matBRows = matB.shape()[0];
  const auto matBCols = matB.shape()[1];

  if (dimension == 0) {
    Array2d matC(boost::extents[matARows + matBRows][matACols]);
    if (matACols != matBCols)
      return matC;
    for (unsigned int i = 0; i < matARows; i++) {
      for (unsigned int j = 0; j < matACols; j++) {
        matC[i][j] = matA[i][j];
      }
    }
    for (unsigned int i = 0; i < matBRows; i++) {
      for (unsigned int j = 0; j < matBCols; j++) {
        matC[i + matARows][j] = matB[i][j];
      }
    }
    return matC;
  } else if (dimension == 1) {
    Array2d matC(boost::extents[matARows][matACols + matBCols]);
    if (matARows != matBRows)
      return matC;
    for (unsigned int i = 0; i < matARows; i++) {
      for (unsigned int j = 0; j < matACols; j++) {
        matC[i][j] = matA[i][j];
      }
    }
    for (unsigned int i = 0; i < matBRows; i++) {
      for (unsigned int j = 0; j < matBCols; j++) {
        matC[i][j + matACols] = matB[i][j];
      }
    }

    return matC;
  } else {
    // not implemented
    assert(0);
    Array2d matC(boost::extents[matARows][matACols + matBCols]);
    return matC;
  }
}

void poplibs_test::gru::basicGruCellBackwardPass(
    bool outputFullSequence, const Array3dRef weightsInput,
    const Array3dRef weightsOutput, const Array3dRef gradsNextLayer,
    const Array4dRef fwdState, const Array2dRef outputActsInit,
    const boost::optional<Array1dRefINT> &realTimeStepsOpt,
    const boost::optional<Array2dRef> &attScoresOpt,
    const boost::optional<Array2dRef> &attScoresGradsOpt, Array4dRef bwdState,
    Array3dRef gradsPrevLayer, const std::vector<BasicGruCellUnit> &cellOrder,
    bool resetAfter, const boost::optional<Array2dRef> recurrantBiases) {
  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] == GRU_NUM_FWD_STATES);
  assert(bwdState.shape()[0] == GRU_NUM_BWD_STATES);
  assert(weightsInput.shape()[0] == BASIC_GRU_CELL_NUM_UNITS);
  assert(weightsInput.shape()[1] == inputSize);
  assert(weightsInput.shape()[2] == outputSize);
  assert(weightsOutput.shape()[0] == BASIC_GRU_CELL_NUM_UNITS);
  assert(weightsOutput.shape()[1] == outputSize);
  assert(weightsOutput.shape()[2] == 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);
  assert(recurrantBiases.is_initialized() == resetAfter);
  if (resetAfter) {
    assert(recurrantBiases.get().shape()[0] == BASIC_GRU_CELL_NUM_UNITS);
    assert(recurrantBiases.get().shape()[1] == outputSize);
  }

  auto cellMapping = getCellMapping(cellOrder);

  // gradient of output of this step
  Array2d gradOutput(boost::extents[batchSize][outputSize]);
  matrixZero(gradOutput);

  Array2d matOne(boost::extents[batchSize][outputSize]);
  matrixOne(matOne);

  const Array2d w_c_in = weightsInput[cellMapping.at(BASIC_GRU_CELL_CANDIDATE)];
  const Array2d w_c_out =
      weightsOutput[cellMapping.at(BASIC_GRU_CELL_CANDIDATE)];
  Array2d w_ru = concatMatrix2D(
      concatMatrix2D(weightsInput[cellMapping.at(BASIC_GRU_CELL_RESET_GATE)],
                     weightsOutput[cellMapping.at(BASIC_GRU_CELL_RESET_GATE)],
                     0),
      concatMatrix2D(weightsInput[cellMapping.at(BASIC_GRU_CELL_UPDATE_GATE)],
                     weightsOutput[cellMapping.at(BASIC_GRU_CELL_UPDATE_GATE)],
                     0),
      1);
  FILE *fp = NULL;
#ifdef DEBUG_TENSOR
  fp = fopen("bwd.txt", "w");
#endif

  for (auto i = sequenceSize; i != 0; --i) {
    const auto s = i - 1;
    if (fp)
      fprintf(fp, "bwd Loop: {%ld}\n", (long int)s);

    Array2d d_h(boost::extents[batchSize][outputSize]);
    Array2d gradOut(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);
      }
    }

    printMatrix2d(fp, "bwd outGrad", gradOutput);
    printMatrix2d(fp, "bwd gradNextLayer", gradOut);
    axpby::add(gradOut, gradOutput, d_h);

    Array2d u = fwdState[GRU_FWD_STATE_UPDATE_GATE][s];
    Array2d r = fwdState[GRU_FWD_STATE_RESET_GATE][s];
    Array2d c = fwdState[GRU_FWD_STATE_CANDIDATE][s];

    printMatrix2d(fp, "bwd d_h", d_h);
    printMatrix2d(fp, "bwd r", r);
    printMatrix2d(fp, "bwd u", u);
    printMatrix2d(fp, "bwd c", c);

    Array2d u_comp(boost::extents[batchSize][outputSize]);
    poplibs_test::axpby::add(matOne, u, u_comp, 1.0, -1.0);
    Array2d d_c(boost::extents[batchSize][outputSize]);
    gemm::hadamardProduct(u_comp, d_h, d_c);
    bwdNonLinearity(popnn::NonLinearityType::TANH, c, d_c);
    applySeqMask(realTimeStepsOpt, s, outputFullSequence, d_c);

    Array2d h_prev(boost::extents[batchSize][outputSize]);
    if (s == 0) {
      h_prev = outputActsInit;
    } else {
      h_prev = fwdState[GRU_FWD_STATE_ACTS_IDX][s - 1];
    }

    printMatrix2d(fp, "bwd h_prev", h_prev);

    Array2d h_prev_c(boost::extents[batchSize][outputSize]);
    poplibs_test::axpby::add(h_prev, c, h_prev_c, 1.0, -1.0);
    Array2d d_u(boost::extents[batchSize][outputSize]);
    gemm::hadamardProduct(d_h, h_prev_c, d_u);

    if (attScoresOpt) {
      Array2d u0(boost::extents[batchSize][outputSize]);
      applyAttBwd(attScoresOpt, u, s, d_u, u0, *attScoresGradsOpt);
      bwdNonLinearity(popnn::NonLinearityType::SIGMOID, u0, d_u);
      printMatrix2d(fp, "bwd d_u", d_u);
      printMatrix2d(fp, "bwd attGrad", *attScoresGradsOpt);
    } else {
      bwdNonLinearity(popnn::NonLinearityType::SIGMOID, u, d_u);
      printMatrix2d(fp, "bwd d_u", d_u);
    }

    Array2d d_r(boost::extents[batchSize][outputSize]);
    Array2d d_x2(boost::extents[batchSize][inputSize]);
    Array2d d_h_prev2(boost::extents[batchSize][outputSize]);
    gemm::generalMatrixMultiply(d_c, w_c_in, d_x2, false, true);
    if (resetAfter) {
      Array2d h_prev2(boost::extents[batchSize][outputSize]);
      Array2d d_cr(boost::extents[batchSize][outputSize]);
      gemm::generalMatrixMultiply(h_prev, w_c_out, h_prev2, false, false);
      for (unsigned i = 0; i < batchSize; i++) {
        for (unsigned j = 0; j < outputSize; j++) {
          h_prev2[i][j] +=
              recurrantBiases
                  .get()[cellMapping.at(BASIC_GRU_CELL_CANDIDATE)][j];
        }
      }
      gemm::hadamardProduct(d_c, h_prev2, d_r);
      gemm::hadamardProduct(d_c, r, d_cr);
      gemm::generalMatrixMultiply(d_cr, w_c_out, d_h_prev2, false, true);
    } else {
      Array2d d_h_prev2r(boost::extents[batchSize][outputSize]);
      // [2nd_component_of_d_x d_h_prevr] = d_c X w_c^T
      gemm::generalMatrixMultiply(d_c, w_c_out, d_h_prev2r, false, true);
      gemm::hadamardProduct(d_h_prev2r, h_prev, d_r);
      gemm::hadamardProduct(d_h_prev2r, r, d_h_prev2);
    }
    bwdNonLinearity(popnn::NonLinearityType::SIGMOID, r, d_r);

    // [1st_component_of_d_x 1st_component_of_d_h_prev] = [d_r d_u] X w_ru^T
    Array2d d_r_d_u = concatMatrix2D(d_r, d_u, 1);
    Array2d d_x1_h_prev1(boost::extents[batchSize][inputSize + outputSize]);
    gemm::generalMatrixMultiply(d_r_d_u, w_ru, d_x1_h_prev1, false, true);
    Array2d d_x1 = getSlice(d_x1_h_prev1, 0, inputSize);
    Array2d d_h_prev1 = getSlice(d_x1_h_prev1, inputSize, outputSize);

    Array2d d_x(boost::extents[batchSize][inputSize]);
    poplibs_test::axpby::add(d_x1, d_x2, d_x);

    Array2d d_h_prev(boost::extents[batchSize][outputSize]);
    {
      gemm::hadamardProduct(d_h, u, d_h_prev);
      poplibs_test::axpby::add(d_h_prev, d_h_prev1, d_h_prev, 1.0, 1.0);
      poplibs_test::axpby::add(d_h_prev, d_h_prev2, d_h_prev, 1.0, 1.0);
    }
    gradOutput = d_h_prev;
    gradsPrevLayer[s] = d_x;

    // save bwd state for weight update
    bwdState[BASIC_GRU_CELL_UPDATE_GATE][s] = d_u;
    bwdState[BASIC_GRU_CELL_RESET_GATE][s] = d_r;
    bwdState[BASIC_GRU_CELL_CANDIDATE][s] = d_c;
  }
  if (fp)
    fclose(fp);
}

void poplibs_test::gru::basicGruCellParamUpdate(
    const Array3dRef prevLayerActs, const Array4dRef fwdState,
    const Array2dRef outputActsInit, const Array4dRef bwdState,
    Array3dRef weightsInputDeltas, Array3dRef weightsOutputDeltas,
    Array2dRef biasDeltas, const std::vector<BasicGruCellUnit> &cellOrder,
    bool resetAfter, boost::optional<Array2dRef> recurrantBiasDeltas) {
  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] == GRU_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] == GRU_NUM_BWD_STATES);
  assert(bwdState.shape()[1] == sequenceSize);
  assert(bwdState.shape()[2] == batchSize);
  assert(bwdState.shape()[3] == outputSize);
  assert(weightsInputDeltas.shape()[0] == BASIC_GRU_CELL_NUM_UNITS);
  assert(weightsInputDeltas.shape()[1] == inputSize);
  assert(weightsInputDeltas.shape()[2] == outputSize);
  assert(weightsOutputDeltas.shape()[0] == BASIC_GRU_CELL_NUM_UNITS);
  assert(weightsOutputDeltas.shape()[1] == outputSize);
  assert(weightsOutputDeltas.shape()[2] == outputSize);
  assert(biasDeltas.shape()[0] == BASIC_GRU_CELL_NUM_UNITS);
  assert(biasDeltas.shape()[1] == outputSize);
  assert(recurrantBiasDeltas.is_initialized() == resetAfter);

  auto cellMapping = getCellMapping(cellOrder);

  matrixZero(weightsInputDeltas);
  matrixZero(weightsOutputDeltas);
  matrixZero(biasDeltas);
  if (resetAfter) {
    matrixZero(recurrantBiasDeltas.get());
  }
  /*
    d_w_r = x_h_prev^T * d_r

    d_w_u = x_h_prev^T * d_u

    d_w_c = x_h_prevr^T * d_c_bar

    d_b_ru = sum of d_r_bar_u_bar along axis = 0

    d_b_c = sum of d_c_bar along axis = 0
  */
  for (auto i = sequenceSize; i != 0; --i) {
    const auto s = i - 1;
    Array2d h_prev(boost::extents[batchSize][outputSize]);
    if (s == 0) {
      h_prev = outputActsInit;
    } else {
      h_prev = fwdState[GRU_FWD_STATE_ACTS_IDX][s - 1];
    }
    Array2d x = prevLayerActs[s];
    Array2d x_h_prev = concatMatrix2D(x, h_prev, 1);
    Array2d r = fwdState[GRU_FWD_STATE_RESET_GATE_IDX][s];

    Array2d d_r = bwdState[BASIC_GRU_CELL_RESET_GATE][s];
    Array2d d_u = bwdState[BASIC_GRU_CELL_UPDATE_GATE][s];
    Array2d d_c = bwdState[BASIC_GRU_CELL_CANDIDATE][s];
    Array2d d_cr(boost::extents[batchSize][outputSize]);
    Array2d d_w_r(boost::extents[inputSize + outputSize][outputSize]);
    Array2d d_w_u(boost::extents[inputSize + outputSize][outputSize]);
    Array2d d_w_c(boost::extents[inputSize + outputSize][outputSize]);
    gemm::generalMatrixMultiply(x_h_prev, d_r, d_w_r, true, false);
    gemm::generalMatrixMultiply(x_h_prev, d_u, d_w_u, true, false);

    if (resetAfter) {
      Array2d d_w_c_in(boost::extents[inputSize][outputSize]);
      Array2d d_w_c_out(boost::extents[outputSize][outputSize]);

      gemm::hadamardProduct(d_c, r, d_cr);
      gemm::generalMatrixMultiply(h_prev, d_cr, d_w_c_out, true, false);
      gemm::generalMatrixMultiply(x, d_c, d_w_c_in, true, false);
      d_w_c = concatMatrix2D(d_w_c_in, d_w_c_out, 0);
    } else {
      Array2d h_prevr(boost::extents[batchSize][outputSize]);
      gemm::hadamardProduct(h_prev, r, h_prevr);
      Array2d x_h_prevr = concatMatrix2D(x, h_prevr, 1);
      gemm::generalMatrixMultiply(x_h_prevr, d_c, d_w_c, true, false);
    }

    for (unsigned int m = 0; m < inputSize; m++) {
      for (unsigned int n = 0; n < outputSize; n++) {
        weightsInputDeltas[cellMapping.at(BASIC_GRU_CELL_RESET_GATE)][m][n] +=
            d_w_r[m][n];
        weightsInputDeltas[cellMapping.at(BASIC_GRU_CELL_UPDATE_GATE)][m][n] +=
            d_w_u[m][n];
        weightsInputDeltas[cellMapping.at(BASIC_GRU_CELL_CANDIDATE)][m][n] +=
            d_w_c[m][n];
      }
    }
    for (unsigned int m = 0; m < outputSize; m++) {
      for (unsigned int n = 0; n < outputSize; n++) {
        weightsOutputDeltas[cellMapping.at(BASIC_GRU_CELL_RESET_GATE)][m][n] +=
            d_w_r[m + inputSize][n];
        weightsOutputDeltas[cellMapping.at(BASIC_GRU_CELL_UPDATE_GATE)][m][n] +=
            d_w_u[m + inputSize][n];
        weightsOutputDeltas[cellMapping.at(BASIC_GRU_CELL_CANDIDATE)][m][n] +=
            d_w_c[m + inputSize][n];
      }
    }
    for (unsigned int m = 0; m < outputSize; m++) {
      for (unsigned int n = 0; n < batchSize; n++) {
        biasDeltas[cellMapping.at(BASIC_GRU_CELL_RESET_GATE)][m] += d_r[n][m];
        biasDeltas[cellMapping.at(BASIC_GRU_CELL_UPDATE_GATE)][m] += d_u[n][m];
        biasDeltas[cellMapping.at(BASIC_GRU_CELL_CANDIDATE)][m] += d_c[n][m];

        if (resetAfter) {
          recurrantBiasDeltas
              .get()[cellMapping.at(BASIC_GRU_CELL_RESET_GATE)][m] += d_r[n][m];
          recurrantBiasDeltas
              .get()[cellMapping.at(BASIC_GRU_CELL_UPDATE_GATE)][m] +=
              d_u[n][m];
          recurrantBiasDeltas
              .get()[cellMapping.at(BASIC_GRU_CELL_CANDIDATE)][m] += d_cr[n][m];
        }
      }
    }
  }
}


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

  @copyright 2016 NumScale SAS
  @copyright 2016 J.T. Lapreste

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

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

 /*!

    @ingroup group-trigonometric
    Function object implementing rem_pio2 capabilities

    Computes the remainder modulo \f$\pi/2\f$.

    @par Semantic:

    For every parameter of floating type T

    @code
    T r, rc;
    as_integer<T> n;
    n = rem_pio2<Range>(x, r);
    @endcode

    is similar to:

    @code
    as_integer<T> n = idivround2even(x, Pio_2<T>());
    T r =  remainder(x, Pio_2<T>());
    @endcode

    @par Note:

    The Range parameter is a type among big_,  medium_,  small_,  very_small_
    that allow to statically choose the computation process.

  **/
  const boost::dispatch::functor<tag::rem_pio2_> rem_pio2 = {};
} }
#endif

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

#endif


#include <algorithm>
#include <array>
#include <iostream>
#include <random>
#include <string>
#include <tuple>
#include <vector>

#include <boost/timer.hpp>

constexpr int kana_num = 44;
constexpr int value_max = 100000;

using table = std::array<std::array<int, kana_num>, kana_num>;
using sumt = std::array<int, kana_num>;

table input() {
  table tb;
  for (int i = 0; i < kana_num; ++i)
    for (int j = 0; j < kana_num; ++j)
      std::cin >> tb[i][j];
  return tb;
}

table erase_loop(table tb) {
  for (int i = 0; i < kana_num; ++i) {
    for (int j = 0; j < kana_num; ++j) {
      if (i != j) {
        int diff = std::min(tb[i][j], tb[j][i]);
        tb[i][j] -= diff;
        tb[j][i] -= diff;
      } else {
        tb[i][i] = tb[i][i] % 2;
      }
    }
  }
  return tb;
}

sumt sum_table(table tb) {
  sumt s;
  std::fill(std::begin(s), std::end(s), 0);
  for (int i = 0; i < kana_num; ++i)
    for (int j = 0; j < kana_num; ++j)
      s[i] += tb[i][j];
  return s;
}

int score(const sumt &st, int pos, int) {
  return st[pos] ? 0 : -value_max;
}

int score2(const sumt &st, int pos, int co_depth) {
  return (st[pos] ? 0 : -value_max) * std::pow(0.99, (double)co_depth);
}

template <typename Func>
int alphabeta(const table &tb, const sumt &st, int pos, int depth,
    int alpha, int beta, Func func, int co_depth = 0) {
  if (depth == 0) return func(st, pos, co_depth);
  for (int i = 0; i < kana_num; ++i) {
    if (tb[pos][i] != 0) {
      auto cp = tb;
      auto cps = st;
      --cp[pos][i];
      --cps[pos];
      alpha = std::max(alpha,
          -alphabeta(cp, cps, i, depth - 1, -beta, -alpha, func, co_depth + 1));
      if (alpha >= beta) return alpha;
    }
  }
  return alpha;
}

template <typename Func>
int alphabeta2(const table &tb, const sumt &st, int pos, int depth,
    int alpha, int beta, Func func, int co_depth = 0) {
  if (depth == 0) return func(st, pos, co_depth);
  bool is_lose = true;
  for (int i = 0; i < kana_num; ++i) {
    if (tb[pos][i] != 0) {
      auto cp = tb;
      auto cps = st;
      is_lose = false;
      --cp[pos][i];
      --cps[pos];
      alpha = std::max(alpha,
          -alphabeta2(cp, cps, i, depth - 1, -beta, -alpha,
              func, co_depth + 1));
      if (alpha >= beta) return alpha;
    }
  }
  if (is_lose)
    return std::max(alpha,
        int(-value_max * std::pow(0.99, (double)co_depth)));
  else
    return alpha;
}

std::vector<int> mate_or_escape(const table &tb, const sumt &st, int pos) {
  std::vector<int> next_pos;
  boost::timer tm;
  for (int depth = 0; depth < 800 && tm.elapsed() < 1.0; ++depth) {
    next_pos.clear();
    int alpha = -value_max;
    for (int i = 0; i < kana_num; ++i) {
      if (tb[pos][i] != 0) {
        auto cp = tb;
        auto cps = st;
        --cp[pos][i];
        --cps[pos];
        int s = -alphabeta2(cp, cps, i, depth, -value_max, value_max, score2);
        if (s >= alpha) {
          if (s > alpha) {
            alpha = s;
            next_pos.clear();
          }
          next_pos.push_back(i);
        }
      }
    }
    std::cerr << "depth: " << depth << ", score: " << alpha << std::endl;
  }
  return next_pos;
}

int main(int argc, char **argv) {
  std::random_device rd;
  std::mt19937 mt(rd());
  int pos;
  std::cin >> pos;
  auto tb = input();
  auto st = sum_table(tb);
  auto el = erase_loop(tb);
  auto es = sum_table(el);
  std::vector<int> next_pos;
  boost::timer tm;
  bool to_mate = false;
  for (int depth = 0; depth < 800 && tm.elapsed() < 1.0; ++depth) {
    next_pos.clear();
    int alpha = -value_max;
    for (int i = 0; i < kana_num; ++i) {
      if (tb[pos][i] != 0) {
        auto cp = el;
        auto cps = es;
        if (cp[pos][i]) {
          --cp[pos][i];
          --cps[pos];
        } else {
          ++cp[i][pos];
          ++cps[i];
        }
        int s = -alphabeta(cp, cps, i, depth, -value_max, value_max, score);
        if (s >= alpha) {
          if (s > alpha) {
            alpha = s;
            next_pos.clear();
          }
          next_pos.push_back(i);
        }
      }
    }
    std::cerr << "depth: " << depth << ", score: " << alpha << std::endl;
    if (alpha == -value_max)
      to_mate = true;
  }
  if (to_mate)
    next_pos = mate_or_escape(tb, st, pos);
  std::uniform_int_distribution<> dis(0, next_pos.size() - 1);
  std::cout << next_pos[dis(mt)] << std::endl;
  return 0;
}


//
//  result.cpp
//  libhsapi
//
//  Created by horned-sungem on 2018/7/2.
//  Copyright © 2018年 Senscape. All rights reserved.
//

#include "result.hpp"
#include "result.hpp"
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include <string>
#include <vector>
#include <math.h>

namespace hs {

Result::Result(const std::string &cnn_type)
    : classification_result(nullptr), detection_result(nullptr) {
    if (!cnn_type.compare("tinyyolo_v1") || !cnn_type.compare("mobilenetssd")) {
        detection_result = std::make_shared<DetectionResult>();
    } else {
        classification_result = std::make_shared<ClassificationResult>();
    }
}

ClassificationResultPtr Result::getClassificationResult() {
    return classification_result;
}

DetectionResultPtr Result::getDetectionResult() { return detection_result; }

void Result::parseYoloResult(const std::vector<float> &result,
                             const std::vector<std::string> categories,
                             int img_width, int img_height) {
    constexpr int grid_width = 7;
    constexpr int grid_height = 7;
    constexpr int bbox_num = 2;
    constexpr float prob_threshold = 0.2;
    constexpr int bbox_conf_num = grid_width * grid_height * bbox_num;
    int class_num = categories.size();
    int prob_num = grid_width * grid_height * class_num;
    ItemInBBoxArrayPtr objs_in_bboxes = std::make_shared<ItemInBBoxArray>();

    for (int i = 0; i < grid_height; i++) {
        for (int j = 0; j < grid_width; j++) {
            for (int k = 0; k < bbox_num; k++) {
                int index = i * grid_width * bbox_num + j * bbox_num + k;
                ItemInBBox obj_in_bbox;

                std::vector<float> probs(result.begin() + index / 2 * class_num,
                                         result.begin() +
                                             index / 2 * class_num + class_num);
                float scale = result[prob_num + index];
                std::vector<float>::iterator max_iter =
                    std::max_element(std::begin(probs), std::end(probs));
                obj_in_bbox.item.probability = *max_iter * scale;

                if (obj_in_bbox.item.probability > prob_threshold) {
                    obj_in_bbox.item.category =
                        categories[std::distance(std::begin(probs), max_iter)];
                    int x_center =
                        ((result[prob_num + bbox_conf_num + index * 4] + j) /
                         7.0) *
                        img_width;
                    int y_center =
                        ((result[prob_num + bbox_conf_num + index * 4 + 1] +
                          i) /
                         7.0) *
                        img_height;
                    obj_in_bbox.bbox.width =
                        (result[prob_num + bbox_conf_num + index * 4 + 2]) *
                        (result[prob_num + bbox_conf_num + index * 4 + 2]) *
                        img_width;
                    obj_in_bbox.bbox.height =
                        (result[prob_num + bbox_conf_num + index * 4 + 3]) *
                        (result[prob_num + bbox_conf_num + index * 4 + 3]) *
                        img_height;
                    obj_in_bbox.bbox.x =
                        (x_center - 0.5 * obj_in_bbox.bbox.width) < 0
                            ? 0
                            : (x_center - 0.5 * obj_in_bbox.bbox.width);
                    obj_in_bbox.bbox.y =
                        (y_center - 0.5 * obj_in_bbox.bbox.height) < 0
                            ? 0
                            : (y_center - 0.5 * obj_in_bbox.bbox.height);
                    objs_in_bboxes->push_back(obj_in_bbox);
                }
            }
        }
    }

    NMS(objs_in_bboxes);

    if (!detection_result->items_in_boxes.empty()) {
        detection_result->items_in_boxes.clear();
    }

    for (auto item : *objs_in_bboxes) {
        detection_result->items_in_boxes.push_back(item);
    }
}

void Result::parseSSDResult(const std::vector<float> &result,
                            const std::vector<std::string> categories,
                            int img_width, int img_height) {
    constexpr int num_in_group = 7;
    int num_detection = result.at(0);
    ItemInBBoxArrayPtr objs_in_bboxes = std::make_shared<ItemInBBoxArray>();

    for (int i = 0; i < num_detection; i++) {
        int category_id = result[(i + 1) * num_in_group + 1];
        float probability = result[(i + 1) * num_in_group + 2];
        int xmin = result[(i + 1) * num_in_group + 3] * img_width;
        int ymin = result[(i + 1) * num_in_group + 4] * img_height;
        int xmax = result[(i + 1) * num_in_group + 5] * img_width;
        int ymax = result[(i + 1) * num_in_group + 6] * img_height;

        ItemInBBox obj_in_bbox;
        if (std::isnan(category_id) || std::isnan(probability) ||
            std::isnan(xmin) || xmin < 0 || xmin > img_width ||
            std::isnan(ymin) || ymin < 0 || ymin > img_height ||
            std::isnan(xmax) || xmax < 0 || xmax > img_width ||
            std::isnan(ymax) || ymax < 0 || ymax > img_height) {
            continue;
        } else {
            obj_in_bbox.item.category = categories.at(category_id);
            obj_in_bbox.item.probability = probability;
            obj_in_bbox.bbox.width = xmax - xmin;
            obj_in_bbox.bbox.height = ymax - ymin;
            obj_in_bbox.bbox.x = xmin;
            obj_in_bbox.bbox.y = ymin;
            objs_in_bboxes->push_back(obj_in_bbox);
        }
    }

    NMS(objs_in_bboxes);

    if (!detection_result->items_in_boxes.empty()) {
        detection_result->items_in_boxes.clear();
    }

    for (auto item : *objs_in_bboxes) {
        detection_result->items_in_boxes.push_back(item);
    }
}

void Result::setClassificationResult(Item item) {
    classification_result->items.push_back(item);
}

void Result::setClassificationResult(float time) {
    classification_result->time_taken = time;
}

void Result::setDetectionResult(ItemInBBox item) {
    detection_result->items_in_boxes.push_back(item);
}

void Result::setDetectionResult(float time) {
    detection_result->time_taken = time;
}

void Result::NMS(ItemInBBoxArrayPtr objs_in_bboxes) {
    constexpr float iou_threshold = 0.5;

    auto cmp = [](const ItemInBBox &a, const ItemInBBox &b) {
        return a.item.probability > b.item.probability;
    };
    std::sort(objs_in_bboxes->begin(), objs_in_bboxes->end(), cmp);

    for (auto iter1 = objs_in_bboxes->begin(); iter1 != objs_in_bboxes->end();
         iter1++) {
        if (iter1->item.probability == 0) {
            continue;
        }
        for (auto iter2 = iter1 + 1; iter2 != objs_in_bboxes->end(); iter2++) {
            if (IOU(*iter1, *iter2) > iou_threshold) {
                iter2->item.probability = 0;
            }
        }
    }

    for (auto iter = objs_in_bboxes->begin(); iter != objs_in_bboxes->end();) {
        if (iter->item.probability == 0) {
            iter = objs_in_bboxes->erase(iter);
        } else {
            iter++;
        }
    }
}

float Result::IOU(ItemInBBox box1, ItemInBBox box2) {
    int xmax = (box1.bbox.x + box1.bbox.width < box2.bbox.x + box2.bbox.width)
                   ? box1.bbox.x + box1.bbox.width
                   : box2.bbox.x + box2.bbox.width;
    int xmin = (box1.bbox.x > box2.bbox.x) ? box1.bbox.x : box2.bbox.x;
    int ymax = (box1.bbox.y + box1.bbox.height < box2.bbox.y + box2.bbox.height)
                   ? box1.bbox.y + box1.bbox.height
                   : box2.bbox.y + box2.bbox.height;
    int ymin = (box1.bbox.y > box2.bbox.y) ? box1.bbox.y : box2.bbox.y;
    int inter_w = xmax - xmin;
    int inter_h = ymax - ymin;
    int inter_area = 0;
    if (inter_w > 0 && inter_h > 0) {
        inter_area = inter_w * inter_h;
    }
    return inter_area * 1.0 /
           (box1.bbox.width * box1.bbox.height +
            box2.bbox.width * box2.bbox.height - inter_area);
}

} // namespace hs


// #include <boost/python.hpp>
#include <fstream>
#include <iomanip>
#include <string>
#include <numeric>
#include <algorithm>
#include <unistd.h>
#include <cmath>
#include <chrono>
#include <random>
#include <iostream>
#include <assert.h>
#include <vector>

#ifdef __cplusplus
extern "C"
{
#endif
   #include <cblas.h>
#ifdef __cplusplus
}
#endif

// using namespace boost:python;
using namespace std;

#define NUM_ITER 1000

int n_nodes;
int n_edges;
int **edges;
float eta = 0.1;
float regu = 0.01;
float **X;
int d = 100;

// BOOST_PYTHON_MODULE(learn_embedding_gf)
// {
//     // Add regular functions to the module.
//     def("learn_embedding", learn_embedding);
// }

void load_network()
{
    ifstream f;
    char ch;
    int v_i, v_j, row;

    f.open("kabutar.txt");
    f >> n_nodes;
    f >> n_edges;
    edges = (int **)malloc(n_edges*sizeof(int *));
    for(int i = 0; i < n_edges; i++)
        edges[i] = (int *)malloc(2*sizeof(int));

    X = (float **)malloc(n_nodes*sizeof(float *));
    for(int i = 0; i < n_nodes; i++)
        X[i] = (float *)malloc(d*sizeof(float));

    assert(edges != NULL);
    assert(X != NULL);

    row = 0;
    while(f >> v_i >> ch >> v_j)
    {
        edges[row][0] = v_i;
        edges[row][1] = v_j;
        row++;
    }
}

void gen_rand_network(int n, int m)
{
    int v_i, v_j, row;
    n_nodes = n;
    n_edges = m;
    edges = (int **)malloc(n_edges*sizeof(int *));
    for(int i = 0; i < n_edges; i++)
        edges[i] = (int *)malloc(2*sizeof(int));

    X = (float **)malloc(n_nodes*sizeof(float *));
    for(int i = 0; i < n_nodes; i++)
        X[i] = (float *)malloc(d*sizeof(float));

    assert(edges != NULL);
    assert(X != NULL);
    std::cout << "Assigned memory to edges and embedding" << std::endl;
    row = 0;
    while(row < n_edges)
    {
        edges[row][0] = rand()%n_nodes;
        edges[row][1] = rand()%n_nodes;
        row++;
    }
}

void init_embedding()
{
    unsigned seed = chrono::system_clock::now().time_since_epoch().count();
    default_random_engine generator (seed);
    uniform_real_distribution<double> distribution (0.0,1.0);
    for (int i = 0; i < n_nodes; i++)
        for(int j = 0; j < d; j++)
            X[i][j] = distribution(generator);
}

void learn_embedding(std::string f_name, int d, float eta, float regu, float max_iter)
{
    int v_i, v_j;
    float i_j_dot;
    for(int iter_id = 0; iter_id < NUM_ITER; iter_id++)
    {
        if(iter_id%100 == 0)
            std::cout << "Iter id: " << iter_id << std::endl;
        for(int edge_id = 0; edge_id < n_edges; edge_id++)
        {
            v_i = edges[edge_id][0];
            v_j = edges[edge_id][1];
            if(v_j >= v_i)
                continue;
            i_j_dot = cblas_sdot(d, X[v_i], 1, X[v_j], 1);
            // cblas_scopy(d, X[v_i], 1, blas_temp, 1);
            cblas_scal(d, -eta*regu, X[v_i]);
            cblas_saxpy(d, eta*(w_ij - i_j_dot), X[v_j], 1, X[v_i], 1)
            // i_j_dot = 0;
            // for(int d_id = 0; d_id < d; d_id++)
            //     i_j_dot += X[v_i][d_id]*X[v_j][d_id];
            // for(int d_id = 0; d_id < d; d_id++)
            //     X[v_i][d_id] -= regu*X[v_i][d_id] - (1 - i_j_dot)*X[v_j][d_id];
        }
    }
    return X;
}

int main(int argc, char *argv[])
{
    clock_t t;
    srand(time(NULL));

    std::cout << "Generating network" << std::endl;
    gen_rand_network(2e6, 1e7);

    std::cout << "Randomly initialize embedding matrix" << std::endl;
    init_embedding();

    t = clock();
    std::cout << "Learning embedding" << std::endl;
    learn_embedding();
    t = clock() - t;
    std::cout << "Learnt embedding. Time taken:" << ((float)t)/CLOCKS_PER_SEC << " secs" << std::endl;
}


// Copyright 2009 The Trustees of Indiana University.

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

//  Authors: Jeremiah Willcock
//           Douglas Gregor
//           Andrew Lumsdaine
#ifndef BOOST_GRAPH_TOPOLOGY_HPP
#define BOOST_GRAPH_TOPOLOGY_HPP

#include <boost/config/no_tr1/cmath.hpp>
#include <cmath>
#include <boost/random/uniform_01.hpp>
#include <boost/random/linear_congruential.hpp>
#include <boost/math/constants/constants.hpp> // For root_two
#include <boost/algorithm/minmax.hpp>
#include <boost/config.hpp> // For BOOST_STATIC_CONSTANT
#include <boost/math/special_functions/hypot.hpp>

// Classes and concepts to represent points in a space, with distance and move
// operations (used for Gurson-Atun layout), plus other things like bounding
// boxes used for other layout algorithms.

namespace boost
{

/***********************************************************
 * Topologies                                              *
 ***********************************************************/
template < std::size_t Dims > class convex_topology
{
public: // For VisualAge C++
    struct point
    {
        BOOST_STATIC_CONSTANT(std::size_t, dimensions = Dims);
        point() {}
        double& operator[](std::size_t i) { return values[i]; }
        const double& operator[](std::size_t i) const { return values[i]; }

    private:
        double values[Dims];
    };

public: // For VisualAge C++
    struct point_difference
    {
        BOOST_STATIC_CONSTANT(std::size_t, dimensions = Dims);
        point_difference()
        {
            for (std::size_t i = 0; i < Dims; ++i)
                values[i] = 0.;
        }
        double& operator[](std::size_t i) { return values[i]; }
        const double& operator[](std::size_t i) const { return values[i]; }

        friend point_difference operator+(
            const point_difference& a, const point_difference& b)
        {
            point_difference result;
            for (std::size_t i = 0; i < Dims; ++i)
                result[i] = a[i] + b[i];
            return result;
        }

        friend point_difference& operator+=(
            point_difference& a, const point_difference& b)
        {
            for (std::size_t i = 0; i < Dims; ++i)
                a[i] += b[i];
            return a;
        }

        friend point_difference operator-(const point_difference& a)
        {
            point_difference result;
            for (std::size_t i = 0; i < Dims; ++i)
                result[i] = -a[i];
            return result;
        }

        friend point_difference operator-(
            const point_difference& a, const point_difference& b)
        {
            point_difference result;
            for (std::size_t i = 0; i < Dims; ++i)
                result[i] = a[i] - b[i];
            return result;
        }

        friend point_difference& operator-=(
            point_difference& a, const point_difference& b)
        {
            for (std::size_t i = 0; i < Dims; ++i)
                a[i] -= b[i];
            return a;
        }

        friend point_difference operator*(
            const point_difference& a, const point_difference& b)
        {
            point_difference result;
            for (std::size_t i = 0; i < Dims; ++i)
                result[i] = a[i] * b[i];
            return result;
        }

        friend point_difference operator*(const point_difference& a, double b)
        {
            point_difference result;
            for (std::size_t i = 0; i < Dims; ++i)
                result[i] = a[i] * b;
            return result;
        }

        friend point_difference operator*(double a, const point_difference& b)
        {
            point_difference result;
            for (std::size_t i = 0; i < Dims; ++i)
                result[i] = a * b[i];
            return result;
        }

        friend point_difference operator/(
            const point_difference& a, const point_difference& b)
        {
            point_difference result;
            for (std::size_t i = 0; i < Dims; ++i)
                result[i] = (b[i] == 0.) ? 0. : a[i] / b[i];
            return result;
        }

        friend double dot(const point_difference& a, const point_difference& b)
        {
            double result = 0;
            for (std::size_t i = 0; i < Dims; ++i)
                result += a[i] * b[i];
            return result;
        }

    private:
        double values[Dims];
    };

public:
    typedef point point_type;
    typedef point_difference point_difference_type;

    double distance(point a, point b) const
    {
        double dist = 0.;
        for (std::size_t i = 0; i < Dims; ++i)
        {
            double diff = b[i] - a[i];
            dist = boost::math::hypot(dist, diff);
        }
        // Exact properties of the distance are not important, as long as
        // < on what this returns matches real distances; l_2 is used because
        // Fruchterman-Reingold also uses this code and it relies on l_2.
        return dist;
    }

    point move_position_toward(point a, double fraction, point b) const
    {
        point result;
        for (std::size_t i = 0; i < Dims; ++i)
            result[i] = a[i] + (b[i] - a[i]) * fraction;
        return result;
    }

    point_difference difference(point a, point b) const
    {
        point_difference result;
        for (std::size_t i = 0; i < Dims; ++i)
            result[i] = a[i] - b[i];
        return result;
    }

    point adjust(point a, point_difference delta) const
    {
        point result;
        for (std::size_t i = 0; i < Dims; ++i)
            result[i] = a[i] + delta[i];
        return result;
    }

    point pointwise_min(point a, point b) const
    {
        BOOST_USING_STD_MIN();
        point result;
        for (std::size_t i = 0; i < Dims; ++i)
            result[i] = min BOOST_PREVENT_MACRO_SUBSTITUTION(a[i], b[i]);
        return result;
    }

    point pointwise_max(point a, point b) const
    {
        BOOST_USING_STD_MAX();
        point result;
        for (std::size_t i = 0; i < Dims; ++i)
            result[i] = max BOOST_PREVENT_MACRO_SUBSTITUTION(a[i], b[i]);
        return result;
    }

    double norm(point_difference delta) const
    {
        double n = 0.;
        for (std::size_t i = 0; i < Dims; ++i)
            n = boost::math::hypot(n, delta[i]);
        return n;
    }

    double volume(point_difference delta) const
    {
        double n = 1.;
        for (std::size_t i = 0; i < Dims; ++i)
            n *= delta[i];
        return n;
    }
};

template < std::size_t Dims, typename RandomNumberGenerator = minstd_rand >
class hypercube_topology : public convex_topology< Dims >
{
    typedef uniform_01< RandomNumberGenerator, double > rand_t;

public:
    typedef typename convex_topology< Dims >::point_type point_type;
    typedef typename convex_topology< Dims >::point_difference_type
        point_difference_type;

    explicit hypercube_topology(double scaling = 1.0)
    : gen_ptr(new RandomNumberGenerator)
    , rand(new rand_t(*gen_ptr))
    , scaling(scaling)
    {
    }

    hypercube_topology(RandomNumberGenerator& gen, double scaling = 1.0)
    : gen_ptr(), rand(new rand_t(gen)), scaling(scaling)
    {
    }

    point_type random_point() const
    {
        point_type p;
        for (std::size_t i = 0; i < Dims; ++i)
            p[i] = (*rand)() * scaling;
        return p;
    }

    point_type bound(point_type a) const
    {
        BOOST_USING_STD_MIN();
        BOOST_USING_STD_MAX();
        point_type p;
        for (std::size_t i = 0; i < Dims; ++i)
            p[i] = min BOOST_PREVENT_MACRO_SUBSTITUTION(
                scaling, max BOOST_PREVENT_MACRO_SUBSTITUTION(-scaling, a[i]));
        return p;
    }

    double distance_from_boundary(point_type a) const
    {
        BOOST_USING_STD_MIN();
        BOOST_USING_STD_MAX();
#ifndef BOOST_NO_STDC_NAMESPACE
        using std::abs;
#endif
        BOOST_STATIC_ASSERT(Dims >= 1);
        double dist = abs(scaling - a[0]);
        for (std::size_t i = 1; i < Dims; ++i)
            dist = min BOOST_PREVENT_MACRO_SUBSTITUTION(
                dist, abs(scaling - a[i]));
        return dist;
    }

    point_type center() const
    {
        point_type result;
        for (std::size_t i = 0; i < Dims; ++i)
            result[i] = scaling * .5;
        return result;
    }

    point_type origin() const
    {
        point_type result;
        for (std::size_t i = 0; i < Dims; ++i)
            result[i] = 0;
        return result;
    }

    point_difference_type extent() const
    {
        point_difference_type result;
        for (std::size_t i = 0; i < Dims; ++i)
            result[i] = scaling;
        return result;
    }

private:
    shared_ptr< RandomNumberGenerator > gen_ptr;
    shared_ptr< rand_t > rand;
    double scaling;
};

template < typename RandomNumberGenerator = minstd_rand >
class square_topology : public hypercube_topology< 2, RandomNumberGenerator >
{
    typedef hypercube_topology< 2, RandomNumberGenerator > inherited;

public:
    explicit square_topology(double scaling = 1.0) : inherited(scaling) {}

    square_topology(RandomNumberGenerator& gen, double scaling = 1.0)
    : inherited(gen, scaling)
    {
    }
};

template < typename RandomNumberGenerator = minstd_rand >
class rectangle_topology : public convex_topology< 2 >
{
    typedef uniform_01< RandomNumberGenerator, double > rand_t;

public:
    rectangle_topology(double left, double top, double right, double bottom)
    : gen_ptr(new RandomNumberGenerator)
    , rand(new rand_t(*gen_ptr))
    , left(std::min BOOST_PREVENT_MACRO_SUBSTITUTION(left, right))
    , top(std::min BOOST_PREVENT_MACRO_SUBSTITUTION(top, bottom))
    , right(std::max BOOST_PREVENT_MACRO_SUBSTITUTION(left, right))
    , bottom(std::max BOOST_PREVENT_MACRO_SUBSTITUTION(top, bottom))
    {
    }

    rectangle_topology(RandomNumberGenerator& gen, double left, double top,
        double right, double bottom)
    : gen_ptr()
    , rand(new rand_t(gen))
    , left(std::min BOOST_PREVENT_MACRO_SUBSTITUTION(left, right))
    , top(std::min BOOST_PREVENT_MACRO_SUBSTITUTION(top, bottom))
    , right(std::max BOOST_PREVENT_MACRO_SUBSTITUTION(left, right))
    , bottom(std::max BOOST_PREVENT_MACRO_SUBSTITUTION(top, bottom))
    {
    }

    typedef typename convex_topology< 2 >::point_type point_type;
    typedef typename convex_topology< 2 >::point_difference_type
        point_difference_type;

    point_type random_point() const
    {
        point_type p;
        p[0] = (*rand)() * (right - left) + left;
        p[1] = (*rand)() * (bottom - top) + top;
        return p;
    }

    point_type bound(point_type a) const
    {
        BOOST_USING_STD_MIN();
        BOOST_USING_STD_MAX();
        point_type p;
        p[0] = min BOOST_PREVENT_MACRO_SUBSTITUTION(
            right, max BOOST_PREVENT_MACRO_SUBSTITUTION(left, a[0]));
        p[1] = min BOOST_PREVENT_MACRO_SUBSTITUTION(
            bottom, max BOOST_PREVENT_MACRO_SUBSTITUTION(top, a[1]));
        return p;
    }

    double distance_from_boundary(point_type a) const
    {
        BOOST_USING_STD_MIN();
        BOOST_USING_STD_MAX();
#ifndef BOOST_NO_STDC_NAMESPACE
        using std::abs;
#endif
        double dist = abs(left - a[0]);
        dist = min BOOST_PREVENT_MACRO_SUBSTITUTION(dist, abs(right - a[0]));
        dist = min BOOST_PREVENT_MACRO_SUBSTITUTION(dist, abs(top - a[1]));
        dist = min BOOST_PREVENT_MACRO_SUBSTITUTION(dist, abs(bottom - a[1]));
        return dist;
    }

    point_type center() const
    {
        point_type result;
        result[0] = (left + right) / 2.;
        result[1] = (top + bottom) / 2.;
        return result;
    }

    point_type origin() const
    {
        point_type result;
        result[0] = left;
        result[1] = top;
        return result;
    }

    point_difference_type extent() const
    {
        point_difference_type result;
        result[0] = right - left;
        result[1] = bottom - top;
        return result;
    }

private:
    shared_ptr< RandomNumberGenerator > gen_ptr;
    shared_ptr< rand_t > rand;
    double left, top, right, bottom;
};

template < typename RandomNumberGenerator = minstd_rand >
class cube_topology : public hypercube_topology< 3, RandomNumberGenerator >
{
    typedef hypercube_topology< 3, RandomNumberGenerator > inherited;

public:
    explicit cube_topology(double scaling = 1.0) : inherited(scaling) {}

    cube_topology(RandomNumberGenerator& gen, double scaling = 1.0)
    : inherited(gen, scaling)
    {
    }
};

template < std::size_t Dims, typename RandomNumberGenerator = minstd_rand >
class ball_topology : public convex_topology< Dims >
{
    typedef uniform_01< RandomNumberGenerator, double > rand_t;

public:
    typedef typename convex_topology< Dims >::point_type point_type;
    typedef typename convex_topology< Dims >::point_difference_type
        point_difference_type;

    explicit ball_topology(double radius = 1.0)
    : gen_ptr(new RandomNumberGenerator)
    , rand(new rand_t(*gen_ptr))
    , radius(radius)
    {
    }

    ball_topology(RandomNumberGenerator& gen, double radius = 1.0)
    : gen_ptr(), rand(new rand_t(gen)), radius(radius)
    {
    }

    point_type random_point() const
    {
        point_type p;
        double dist_sum;
        do
        {
            dist_sum = 0.0;
            for (std::size_t i = 0; i < Dims; ++i)
            {
                double x = (*rand)() * 2 * radius - radius;
                p[i] = x;
                dist_sum += x * x;
            }
        } while (dist_sum > radius * radius);
        return p;
    }

    point_type bound(point_type a) const
    {
        BOOST_USING_STD_MIN();
        BOOST_USING_STD_MAX();
        double r = 0.;
        for (std::size_t i = 0; i < Dims; ++i)
            r = boost::math::hypot(r, a[i]);
        if (r <= radius)
            return a;
        double scaling_factor = radius / r;
        point_type p;
        for (std::size_t i = 0; i < Dims; ++i)
            p[i] = a[i] * scaling_factor;
        return p;
    }

    double distance_from_boundary(point_type a) const
    {
        double r = 0.;
        for (std::size_t i = 0; i < Dims; ++i)
            r = boost::math::hypot(r, a[i]);
        return radius - r;
    }

    point_type center() const
    {
        point_type result;
        for (std::size_t i = 0; i < Dims; ++i)
            result[i] = 0;
        return result;
    }

    point_type origin() const
    {
        point_type result;
        for (std::size_t i = 0; i < Dims; ++i)
            result[i] = -radius;
        return result;
    }

    point_difference_type extent() const
    {
        point_difference_type result;
        for (std::size_t i = 0; i < Dims; ++i)
            result[i] = 2. * radius;
        return result;
    }

private:
    shared_ptr< RandomNumberGenerator > gen_ptr;
    shared_ptr< rand_t > rand;
    double radius;
};

template < typename RandomNumberGenerator = minstd_rand >
class circle_topology : public ball_topology< 2, RandomNumberGenerator >
{
    typedef ball_topology< 2, RandomNumberGenerator > inherited;

public:
    explicit circle_topology(double radius = 1.0) : inherited(radius) {}

    circle_topology(RandomNumberGenerator& gen, double radius = 1.0)
    : inherited(gen, radius)
    {
    }
};

template < typename RandomNumberGenerator = minstd_rand >
class sphere_topology : public ball_topology< 3, RandomNumberGenerator >
{
    typedef ball_topology< 3, RandomNumberGenerator > inherited;

public:
    explicit sphere_topology(double radius = 1.0) : inherited(radius) {}

    sphere_topology(RandomNumberGenerator& gen, double radius = 1.0)
    : inherited(gen, radius)
    {
    }
};

template < typename RandomNumberGenerator = minstd_rand > class heart_topology
{
    // Heart is defined as the union of three shapes:
    // Square w/ corners (+-1000, -1000), (0, 0), (0, -2000)
    // Circle centered at (-500, -500) radius 500*sqrt(2)
    // Circle centered at (500, -500) radius 500*sqrt(2)
    // Bounding box (-1000, -2000) - (1000, 500*(sqrt(2) - 1))

    struct point
    {
        point()
        {
            values[0] = 0.0;
            values[1] = 0.0;
        }
        point(double x, double y)
        {
            values[0] = x;
            values[1] = y;
        }

        double& operator[](std::size_t i) { return values[i]; }
        double operator[](std::size_t i) const { return values[i]; }

    private:
        double values[2];
    };

    bool in_heart(point p) const
    {
#ifndef BOOST_NO_STDC_NAMESPACE
        using std::abs;
#endif

        if (p[1] < abs(p[0]) - 2000)
            return false; // Bottom
        if (p[1] <= -1000)
            return true; // Diagonal of square
        if (boost::math::hypot(p[0] - -500, p[1] - -500)
            <= 500. * boost::math::constants::root_two< double >())
            return true; // Left circle
        if (boost::math::hypot(p[0] - 500, p[1] - -500)
            <= 500. * boost::math::constants::root_two< double >())
            return true; // Right circle
        return false;
    }

    bool segment_within_heart(point p1, point p2) const
    {
        // Assumes that p1 and p2 are within the heart
        if ((p1[0] < 0) == (p2[0] < 0))
            return true; // Same side of symmetry line
        if (p1[0] == p2[0])
            return true; // Vertical
        double slope = (p2[1] - p1[1]) / (p2[0] - p1[0]);
        double intercept = p1[1] - p1[0] * slope;
        if (intercept > 0)
            return false; // Crosses between circles
        return true;
    }

    typedef uniform_01< RandomNumberGenerator, double > rand_t;

public:
    typedef point point_type;

    heart_topology()
    : gen_ptr(new RandomNumberGenerator), rand(new rand_t(*gen_ptr))
    {
    }

    heart_topology(RandomNumberGenerator& gen)
    : gen_ptr(), rand(new rand_t(gen))
    {
    }

    point random_point() const
    {
        point result;
        do
        {
            result[0] = (*rand)()
                    * (1000
                        + 1000 * boost::math::constants::root_two< double >())
                - (500 + 500 * boost::math::constants::root_two< double >());
            result[1] = (*rand)()
                    * (2000
                        + 500
                            * (boost::math::constants::root_two< double >()
                                - 1))
                - 2000;
        } while (!in_heart(result));
        return result;
    }

    // Not going to provide clipping to bounding region or distance from
    // boundary

    double distance(point a, point b) const
    {
        if (segment_within_heart(a, b))
        {
            // Straight line
            return boost::math::hypot(b[0] - a[0], b[1] - a[1]);
        }
        else
        {
            // Straight line bending around (0, 0)
            return boost::math::hypot(a[0], a[1])
                + boost::math::hypot(b[0], b[1]);
        }
    }

    point move_position_toward(point a, double fraction, point b) const
    {
        if (segment_within_heart(a, b))
        {
            // Straight line
            return point(a[0] + (b[0] - a[0]) * fraction,
                a[1] + (b[1] - a[1]) * fraction);
        }
        else
        {
            double distance_to_point_a = boost::math::hypot(a[0], a[1]);
            double distance_to_point_b = boost::math::hypot(b[0], b[1]);
            double location_of_point = distance_to_point_a
                / (distance_to_point_a + distance_to_point_b);
            if (fraction < location_of_point)
                return point(a[0] * (1 - fraction / location_of_point),
                    a[1] * (1 - fraction / location_of_point));
            else
                return point(b[0]
                        * ((fraction - location_of_point)
                            / (1 - location_of_point)),
                    b[1]
                        * ((fraction - location_of_point)
                            / (1 - location_of_point)));
        }
    }

private:
    shared_ptr< RandomNumberGenerator > gen_ptr;
    shared_ptr< rand_t > rand;
};

} // namespace boost

#endif // BOOST_GRAPH_TOPOLOGY_HPP


#include "OT/BaseSSOT.h"
#include "OT/Base/PvwBaseOT.h"
#include "Crypto/PRNG.h"
#include "Crypto/sha1.h"
#include "Network/Channel.h"
#include "cryptopp/osrng.h"
#include "Common/Log.h"
#include "OT/IknpOtExtReceiver4k.h"
#include "OT/IknpOtExtSender4k.h"

#include <stdio.h>
#include <iostream>
#include <fstream>
#include <thread>
#include <mutex>
#include "OT/Base/naor-pinkas.h"

#include <boost/thread/tss.hpp>

using namespace std;
namespace bOPRF {
//#define PRINT_BASEOT_RESULT
	const char* role_to_str1(OTRole role)
	{
		if (role == Receiver)
			return "Receiver";
		if (role == Sender)
			return "Sender";
		return "Both";
	}

//	#define BASEOT_DEBUG

		// Run the PVW OTs
	void Exec_SSOT(
		std::array<std::array<std::array<block, 2>, BASE_OT_COUNT>, 4> &sendMsg,
		BitVector& SSOT_Base_Choice,
		std::array<std::array<block, BASE_OT_COUNT>, 4>& recvMsg,
		PRNG& G,
		Channel& channel,
		OTRole role)
	{
		u64 n = SSOT_Base_Choice.size();
	//	Log::out <<  "Starting base SSOTs as " << role_to_str1(role) << ", n = " << n << Log::endl;
		vector<ByteStream> strm(2);
		IknpOtExtSender4k sender;
		IknpOtExtReceiver4k recv;

		//for BaseOT 128
		std::array<block, BASE_OT_COUNT> baseRecvMsg;
		BitVector baseChoices(BASE_OT_COUNT);
		baseChoices.randomize(G);
		std::array<std::array<block, 2>, BASE_OT_COUNT> baseSenderMsg;

		if (role & Receiver)
		{
			// Generate my receiver inputs
			SSOT_Base_Choice.randomize(G);
			//PvwBaseOT baseOTs(channel, OTRole::Sender);
			//baseOTs.exec_base(G);
			//baseOTs.check();
			//	recv.setBaseOts(baseOTs.sender_inputs);

			NaorPinkas base;
			base.Sender(baseSenderMsg, channel, G, 2);
			recv.setBaseOts(baseSenderMsg);
			recv.Extend(SSOT_Base_Choice, recvMsg, G, channel);
		}

		if (role & Sender)
		{
			// Generate my receiver inputs
			/*PvwBaseOT baseOTs(channel, OTRole::Receiver);
			baseOTs.exec_base(G);
			baseOTs.check();*/
			NaorPinkas base;
			base.Receiver(baseRecvMsg, baseChoices, channel, G, 2);
			sender.setBaseOts(baseRecvMsg, baseChoices);
			sender.Extend(sendMsg, G, channel);
		}
	}

	void BaseSSOT::exec_base(PRNG& G)
	{
		Exec_SSOT(sender_inputs, receiver_inputs, receiver_outputs, G, mChannel, mOTRole);

#ifdef PRINT_BASEOT_RESULT
		for (u64 blkIdx = 0; blkIdx < 4; ++blkIdx)
		{
			for (int i = 0; i < BASE_OT_COUNT; i++)
			{
				if (mOTRole & Sender)
				{
								Log::out << "[" << i << ",0]--" << sender_inputs[blkIdx][i][0] << Log::endl;
								Log::out << "[" << i << ",1]--" << sender_inputs[blkIdx][i][1] << Log::endl;

					}
					if (mOTRole & Receiver)
							Log::out << (int)receiver_inputs[blkIdx*BASE_OT_COUNT+ i] << "-- " << receiver_outputs[blkIdx][i] << Log::endl;

			}
		}
#endif
	}

	void BaseSSOT::check()
	{
		ByteStream os;
		block tmp;
		for (u64 blkIdx = 0; blkIdx < 4; ++blkIdx)
		{
			for (int i = 0; i < BASE_OT_COUNT; i++)
			{
				if (mOTRole & Sender)
				{
					// send both inputs over
					os.append(sender_inputs[blkIdx][i][0]);
					os.append(sender_inputs[blkIdx][i][1]);

					if (eq(sender_inputs[blkIdx][i][0], sender_inputs[blkIdx][i][1]))
						throw std::runtime_error("rt error at " LOCATION);

					mChannel.asyncSendCopy(os);
				}

				if (mOTRole & Receiver)
				{
					mChannel.recv(os);

					os.consume((u8*)&tmp, sizeof(block));

					if (receiver_inputs[blkIdx*BASE_OT_COUNT+i] == 1)
					{
						os.consume((u8*)&tmp, sizeof(block));
					}

					if (neq(tmp, receiver_outputs[blkIdx][i]))
					{
						Log::out << "Base Incorrect OT" << Log::endl;
						Log::out << "I        have " << receiver_outputs[blkIdx][i] << Log::endl;
						Log::out << "but they have " << tmp << Log::endl;

						throw std::runtime_error("Exit");;
					}
				}
				os.setp(0);
			}
		}
	}
}


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_TRIGONOMETRIC_FUNCTIONS_SCALAR_TANGENT_HPP_INCLUDED
#define NT2_TRIGONOMETRIC_FUNCTIONS_SCALAR_TANGENT_HPP_INCLUDED
#include <nt2/trigonometric/functions/tangent.hpp>
#include <nt2/trigonometric/functions/scalar/impl/trigo.hpp>
#include <nt2/trigonometric/functions/simd/common/impl/trigo.hpp>
#include <boost/simd/sdk/simd/meta/is_native.hpp>
#include <nt2/include/functions/simd/tofloat.hpp>
#include <nt2/sdk/meta/as_floating.hpp>

namespace nt2 { namespace ext
{
  NT2_FUNCTOR_IMPLEMENTATION( nt2::tag::tangent_<mode>, tag::cpu_
                            , (A0)(mode)
                            , (scalar_< arithmetic_<A0> >)
                            )
  {

    typedef typename boost::dispatch::meta::as_floating<A0>::type result_type;

    NT2_FUNCTOR_CALL(1)
    {
      return nt2::tangent<mode>(nt2::tofloat(a0));
    }
  };

  NT2_FUNCTOR_IMPLEMENTATION( nt2::tag::tangent_<mode> , tag::cpu_
                            , (A0)(mode)
                            , (scalar_< floating_<A0> >)
                            )
  {
    typedef A0 result_type;
    NT2_FUNCTOR_CALL(1)
    {
      return impl::trig_base<A0,radian_tag,tag::not_simd_type,mode>::tana(a0);
    }
  };
} }

#endif


// The contents of this file are in the public domain. See LICENSE_FOR_EXAMPLE_PROGRAMS.txt
/*
    This program was used to train the resnet34_1000_imagenet_classifier.dnn
    network used by the dnn_imagenet_ex.cpp example program.

    You should be familiar with dlib's DNN module before reading this example
    program.  So read dnn_introduction_ex.cpp and dnn_introduction2_ex.cpp first.
*/

#include <dlib/dnn.h>
#include <iostream>
#include <dlib/data_io.h>
#include <dlib/image_transforms.h>
#include <dlib/dir_nav.h>
#include <iterator>
#include <thread>

using namespace std;
using namespace dlib;

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

template <template <int,template<typename>class,int,typename> class block, int N, template<typename>class BN, typename SUBNET>
using residual = add_prev1<block<N,BN,1,tag1<SUBNET>>>;

template <template <int,template<typename>class,int,typename> class block, int N, template<typename>class BN, typename SUBNET>
using residual_down = add_prev2<avg_pool<2,2,2,2,skip1<tag2<block<N,BN,2,tag1<SUBNET>>>>>>;

template <int N, template <typename> class BN, int stride, typename SUBNET>
using block  = BN<con<N,3,3,1,1,relu<BN<con<N,3,3,stride,stride,SUBNET>>>>>;

template <int N, typename SUBNET> using res       = relu<residual<block,N,bn_con,SUBNET>>;
template <int N, typename SUBNET> using ares      = relu<residual<block,N,affine,SUBNET>>;
template <int N, typename SUBNET> using res_down  = relu<residual_down<block,N,bn_con,SUBNET>>;
template <int N, typename SUBNET> using ares_down = relu<residual_down<block,N,affine,SUBNET>>;

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

// training network type
using net_type = loss_multiclass_log<fc<1000,avg_pool_everything<
                            res<512,res<512,res_down<512,
                            res<256,res<256,res<256,res<256,res<256,res_down<256,
                            res<128,res<128,res<128,res_down<128,
                            res<64,res<64,res<64,
                            max_pool<3,3,2,2,relu<bn_con<con<64,7,7,2,2,
                            input_rgb_image_sized<227>
                            >>>>>>>>>>>>>>>>>>>>>>>;

// testing network type (replaced batch normalization with fixed affine transforms)
using anet_type = loss_multiclass_log<fc<1000,avg_pool_everything<
                            ares<512,ares<512,ares_down<512,
                            ares<256,ares<256,ares<256,ares<256,ares<256,ares_down<256,
                            ares<128,ares<128,ares<128,ares_down<128,
                            ares<64,ares<64,ares<64,
                            max_pool<3,3,2,2,relu<affine<con<64,7,7,2,2,
                            input_rgb_image_sized<227>
                            >>>>>>>>>>>>>>>>>>>>>>>;

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

rectangle make_random_cropping_rect_resnet(
    const matrix<rgb_pixel>& img,
    dlib::rand& rnd
)
{
    // figure out what rectangle we want to crop from the image
    double mins = 0.466666666, maxs = 0.875;
    auto scale = mins + rnd.get_random_double()*(maxs-mins);
    auto size = scale*std::min(img.nr(), img.nc());
    rectangle rect(size, size);
    // randomly shift the box around
    point offset(rnd.get_random_32bit_number()%(img.nc()-rect.width()),
                 rnd.get_random_32bit_number()%(img.nr()-rect.height()));
    return move_rect(rect, offset);
}

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

void randomly_crop_image (
    const matrix<rgb_pixel>& img,
    matrix<rgb_pixel>& crop,
    dlib::rand& rnd
)
{
    auto rect = make_random_cropping_rect_resnet(img, rnd);

    // now crop it out as a 227x227 image.
    extract_image_chip(img, chip_details(rect, chip_dims(227,227)), crop);

    // Also randomly flip the image
    if (rnd.get_random_double() > 0.5)
        crop = fliplr(crop);

    // And then randomly adjust the colors.
    apply_random_color_offset(crop, rnd);
}

void randomly_crop_images (
    const matrix<rgb_pixel>& img,
    dlib::array<matrix<rgb_pixel>>& crops,
    dlib::rand& rnd,
    long num_crops
)
{
    std::vector<chip_details> dets;
    for (long i = 0; i < num_crops; ++i)
    {
        auto rect = make_random_cropping_rect_resnet(img, rnd);
        dets.push_back(chip_details(rect, chip_dims(227,227)));
    }

    extract_image_chips(img, dets, crops);

    for (auto&& img : crops)
    {
        // Also randomly flip the image
        if (rnd.get_random_double() > 0.5)
            img = fliplr(img);

        // And then randomly adjust the colors.
        apply_random_color_offset(img, rnd);
    }
}

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

struct image_info
{
    string filename;
    string label;
    long numeric_label;
};

std::vector<image_info> get_imagenet_train_listing(
    const std::string& images_folder
)
{
    std::vector<image_info> results;
    image_info temp;
    temp.numeric_label = 0;
    // We will loop over all the label types in the dataset, each is contained in a subfolder.
    auto subdirs = directory(images_folder).get_dirs();
    // But first, sort the sub directories so the numeric labels will be assigned in sorted order.
    std::sort(subdirs.begin(), subdirs.end());
    for (auto subdir : subdirs)
    {
        // Now get all the images in this label type
        temp.label = subdir.name();
        for (auto image_file : subdir.get_files())
        {
            temp.filename = image_file;
            results.push_back(temp);
        }
        ++temp.numeric_label;
    }
    return results;
}

std::vector<image_info> get_imagenet_val_listing(
    const std::string& imagenet_root_dir,
    const std::string& validation_images_file
)
{
    ifstream fin(validation_images_file);
    string label, filename;
    std::vector<image_info> results;
    image_info temp;
    temp.numeric_label = -1;
    while(fin >> label >> filename)
    {
        temp.filename = imagenet_root_dir+"/"+filename;
        if (!file_exists(temp.filename))
        {
            cerr << "file doesn't exist! " << temp.filename << endl;
            exit(1);
        }
        if (label != temp.label)
            ++temp.numeric_label;

        temp.label = label;
        results.push_back(temp);
    }

    return results;
}

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

int main(int argc, char** argv) try
{
    if (argc != 3)
    {
        cout << "To run this program you need a copy of the imagenet ILSVRC2015 dataset and" << endl;
        cout << "also the file http://dlib.net/files/imagenet2015_validation_images.txt.bz2" << endl;
        cout << endl;
        cout << "With those things, you call this program like this: " << endl;
        cout << "./dnn_imagenet_train_ex /path/to/ILSVRC2015 imagenet2015_validation_images.txt" << endl;
        return 1;
    }

    cout << "\nSCANNING IMAGENET DATASET\n" << endl;

    auto listing = get_imagenet_train_listing(string(argv[1])+"/Data/CLS-LOC/train/");
    cout << "images in dataset: " << listing.size() << endl;
    const auto number_of_classes = listing.back().numeric_label+1;
    if (listing.size() == 0 || number_of_classes != 1000)
    {
        cout << "Didn't find the imagenet dataset. " << endl;
        return 1;
    }

    set_dnn_prefer_smallest_algorithms();

    const double initial_learning_rate = 0.1;
    const double weight_decay = 0.0001;
    const double momentum = 0.9;

    net_type net;
    dnn_trainer<net_type> trainer(net,sgd(weight_decay, momentum));
    trainer.be_verbose();
    trainer.set_learning_rate(initial_learning_rate);
    trainer.set_synchronization_file("imagenet_trainer_state_file.dat", std::chrono::minutes(10));
    // This threshold is probably excessively large.  You could likely get good results
    // with a smaller value but if you aren't in a hurry this value will surely work well.
    trainer.set_iterations_without_progress_threshold(20000);

    std::vector<matrix<rgb_pixel>> samples;
    std::vector<unsigned long> labels;

    // Start a bunch of threads that read images from disk and pull out random crops.  It's
    // important to be sure to feed the GPU fast enough to keep it busy.  Using multiple
    // thread for this kind of data preparation helps us do that.  Each thread puts the
    // crops into the data queue.
    dlib::pipe<std::pair<image_info,matrix<rgb_pixel>>> data(200);
    auto f = [&data, &listing](time_t seed)
    {
        dlib::rand rnd(time(0)+seed);
        matrix<rgb_pixel> img;
        std::pair<image_info, matrix<rgb_pixel>> temp;
        while(data.is_enabled())
        {
            temp.first = listing[rnd.get_random_32bit_number()%listing.size()];
            load_image(img, temp.first.filename);
            randomly_crop_image(img, temp.second, rnd);
            data.enqueue(temp);
        }
    };
    std::thread data_loader1([f](){ f(1); });
    std::thread data_loader2([f](){ f(2); });
    std::thread data_loader3([f](){ f(3); });
    std::thread data_loader4([f](){ f(4); });

    // The main training loop.  Keep making mini-batches and giving them to the trainer.
    // We will run until the learning rate has dropped by a factor of 1e-3.
    while(trainer.get_learning_rate() >= initial_learning_rate*1e-3)
    {
        samples.clear();
        labels.clear();

        // make a 160 image mini-batch
        std::pair<image_info, matrix<rgb_pixel>> img;
        while(samples.size() < 160)
        {
            data.dequeue(img);

            samples.push_back(std::move(img.second));
            labels.push_back(img.first.numeric_label);
        }

        trainer.train_one_step(samples, labels);
    }

    // Training done, tell threads to stop and make sure to wait for them to finish before
    // moving on.
    data.disable();
    data_loader1.join();
    data_loader2.join();
    data_loader3.join();
    data_loader4.join();

    // also wait for threaded processing to stop in the trainer.
    trainer.get_net();

    net.clean();
    cout << "saving network" << endl;
    serialize("resnet34.dnn") << net;

    // Now test the network on the imagenet validation dataset.  First, make a testing
    // network with softmax as the final layer.  We don't have to do this if we just wanted
    // to test the "top1 accuracy" since the normal network outputs the class prediction.
    // But this snet object will make getting the top5 predictions easy as it directly
    // outputs the probability of each class as its final output.
    softmax<anet_type::subnet_type> snet; snet.subnet() = net.subnet();

    cout << "Testing network on imagenet validation dataset..." << endl;
    int num_right = 0;
    int num_wrong = 0;
    int num_right_top1 = 0;
    int num_wrong_top1 = 0;
    dlib::rand rnd(time(0));
    // loop over all the imagenet validation images
    for (auto l : get_imagenet_val_listing(argv[1], argv[2]))
    {
        dlib::array<matrix<rgb_pixel>> images;
        matrix<rgb_pixel> img;
        load_image(img, l.filename);
        // Grab 16 random crops from the image.  We will run all of them through the
        // network and average the results.
        const int num_crops = 16;
        randomly_crop_images(img, images, rnd, num_crops);
        // p(i) == the probability the image contains object of class i.
        matrix<float,1,1000> p = sum_rows(mat(snet(images.begin(), images.end())))/num_crops;

        // check top 1 accuracy
        if (index_of_max(p) == l.numeric_label)
            ++num_right_top1;
        else
            ++num_wrong_top1;

        // check top 5 accuracy
        bool found_match = false;
        for (int k = 0; k < 5; ++k)
        {
            long predicted_label = index_of_max(p);
            p(predicted_label) = 0;
            if (predicted_label == l.numeric_label)
            {
                found_match = true;
                break;
            }

        }
        if (found_match)
            ++num_right;
        else
            ++num_wrong;
    }
    cout << "val top5 accuracy:  " << num_right/(double)(num_right+num_wrong) << endl;
    cout << "val top1 accuracy:  " << num_right_top1/(double)(num_right_top1+num_wrong_top1) << endl;
}
catch(std::exception& e)
{
    cout << e.what() << endl;
}


#ifdef TORSTEN_MPI

#include <stan/math.hpp>
#include <stan/math/rev/core.hpp>
#include <test/unit/math/rev/fun/util.hpp>
#include <stan/math/torsten/dsolve/pmx_cvodes_integrator.hpp>
#include <stan/math/prim/fun/eval.hpp>
#include <stan/math/rev/functor/jacobian.hpp>
#include <stan/math/torsten/mpi.hpp>
#include <stan/math/torsten/dsolve/pmx_integrate_ode_adams.hpp>
#include <stan/math/torsten/dsolve/pmx_integrate_ode_bdf.hpp>
#include <stan/math/torsten/dsolve/pmx_integrate_ode_group_adams.hpp>
#include <stan/math/torsten/dsolve/pmx_integrate_ode_group_bdf.hpp>
#include <stan/math/torsten/test/unit/pmx_ode_test_fixture.hpp>
#include <test/unit/math/prim/functor/harmonic_oscillator.hpp>
#include <stan/math/rev/functor/integrate_ode_bdf.hpp>
#include <nvector/nvector_serial.h>
#include <boost/mpi.hpp>
#include <test/unit/util.hpp>
#include <gtest/gtest.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <memory>
#include <iostream>
#include <fstream>
#include <vector>
#include <limits>
#include <vector>
#include <chrono>
#include <ctime>

TORSTEN_MPI_SESSION_INIT;

TEST_F(TorstenOdeTest_chem, fwd_sensitivity_theta_bdf_mpi) {

  using torsten::pmx_integrate_ode_group_bdf;
  using stan::math::var;
  using std::vector;

  stan::math::mpi::Envionment::init();

  // size of population
  const int np = 100;
  std::vector<double> ts0 {ts};

  vector<var> theta_var = stan::math::to_var(theta);

  vector<int> len(np, ts0.size());
  vector<double> ts_m;
  ts_m.reserve(np * ts0.size());
  for (int i = 0; i < np; ++i) ts_m.insert(ts_m.end(), ts0.begin(), ts0.end());

  vector<vector<double> > y0_m (np, y0);
  vector<vector<var> > theta_var_m (np, theta_var);
  vector<vector<double> > x_r_m (np, x_r);
  vector<vector<int> > x_i_m (np, x_i);

  Eigen::Matrix<var, -1, -1> y_m = pmx_integrate_ode_group_bdf(f, y0_m, t0, len, ts_m, theta_var_m , x_r_m, x_i_m);
}

TEST_F(TorstenOdeTest_lorenz, fwd_sensitivity_theta_bdf_mpi) {

  using torsten::pmx_integrate_ode_group_bdf;
  using stan::math::var;
  using std::vector;

  stan::math::mpi::Envionment::init();

  // size of population
  const int np = 100;
  std::vector<double> ts0 {ts};

  vector<var> theta_var = stan::math::to_var(theta);

  vector<int> len(np, ts0.size());
  vector<double> ts_m;
  ts_m.reserve(np * ts0.size());
  for (int i = 0; i < np; ++i) ts_m.insert(ts_m.end(), ts0.begin(), ts0.end());

  vector<vector<double> > y0_m (np, y0);
  vector<vector<var> > theta_var_m (np, theta_var);
  vector<vector<double> > x_r_m (np, x_r);
  vector<vector<int> > x_i_m (np, x_i);

  Eigen::Matrix<var, -1, -1> y_m = pmx_integrate_ode_group_bdf(f, y0_m, t0, len, ts_m, theta_var_m , x_r_m, x_i_m);
}

TEST_F(TorstenOdeTest_neutropenia, fwd_sensitivity_theta_bdf_mpi) {

  using torsten::pmx_integrate_ode_group_bdf;
  using stan::math::var;
  using std::vector;

  stan::math::mpi::Envionment::init();

  // size of population
  const int np = 100;
  std::vector<double> ts0 {ts};

  vector<var> theta_var = stan::math::to_var(theta);

  vector<int> len(np, ts0.size());
  vector<double> ts_m;
  ts_m.reserve(np * ts0.size());
  for (int i = 0; i < np; ++i) ts_m.insert(ts_m.end(), ts0.begin(), ts0.end());

  vector<vector<double> > y0_m (np, y0);
  vector<vector<var> > theta_var_m (np, theta_var);
  vector<vector<double> > x_r_m (np, x_r);
  vector<vector<int> > x_i_m (np, x_i);

  Eigen::Matrix<var, -1, -1> y_m = pmx_integrate_ode_group_bdf(f, y0_m, t0, len, ts_m, theta_var_m , x_r_m, x_i_m);
}

TEST_F(TorstenOdeTest_chem, fwd_sensitivity_theta_adams_mpi) {

  using torsten::pmx_integrate_ode_group_adams;
  using stan::math::var;
  using std::vector;

  stan::math::mpi::Envionment::init();

  // size of population
  const int np = 100;
  std::vector<double> ts0 {ts};

  vector<var> theta_var = stan::math::to_var(theta);

  vector<int> len(np, ts0.size());
  vector<double> ts_m;
  ts_m.reserve(np * ts0.size());
  for (int i = 0; i < np; ++i) ts_m.insert(ts_m.end(), ts0.begin(), ts0.end());

  vector<vector<double> > y0_m (np, y0);
  vector<vector<var> > theta_var_m (np, theta_var);
  vector<vector<double> > x_r_m (np, x_r);
  vector<vector<int> > x_i_m (np, x_i);

  Eigen::Matrix<var, -1, -1> y_m = pmx_integrate_ode_group_adams(f, y0_m, t0, len, ts_m, theta_var_m , x_r_m, x_i_m);
}

TEST_F(TorstenOdeTest_lorenz, fwd_sensitivity_theta_adams_mpi) {

  using torsten::pmx_integrate_ode_group_adams;
  using stan::math::var;
  using std::vector;

  stan::math::mpi::Envionment::init();

  // size of population
  const int np = 100;
  std::vector<double> ts0 {ts};

  vector<var> theta_var = stan::math::to_var(theta);

  vector<int> len(np, ts0.size());
  vector<double> ts_m;
  ts_m.reserve(np * ts0.size());
  for (int i = 0; i < np; ++i) ts_m.insert(ts_m.end(), ts0.begin(), ts0.end());

  vector<vector<double> > y0_m (np, y0);
  vector<vector<var> > theta_var_m (np, theta_var);
  vector<vector<double> > x_r_m (np, x_r);
  vector<vector<int> > x_i_m (np, x_i);

  Eigen::Matrix<var, -1, -1> y_m = pmx_integrate_ode_group_adams(f, y0_m, t0, len, ts_m, theta_var_m , x_r_m, x_i_m);
}

TEST_F(TorstenOdeTest_neutropenia, fwd_sensitivity_theta_adams_mpi) {

  using torsten::pmx_integrate_ode_group_adams;
  using stan::math::var;
  using std::vector;

  stan::math::mpi::Envionment::init();

  // size of population
  const int np = 100;
  std::vector<double> ts0 {ts};

  vector<var> theta_var = stan::math::to_var(theta);

  vector<int> len(np, ts0.size());
  vector<double> ts_m;
  ts_m.reserve(np * ts0.size());
  for (int i = 0; i < np; ++i) ts_m.insert(ts_m.end(), ts0.begin(), ts0.end());

  vector<vector<double> > y0_m (np, y0);
  vector<vector<var> > theta_var_m (np, theta_var);
  vector<vector<double> > x_r_m (np, x_r);
  vector<vector<int> > x_i_m (np, x_i);

  Eigen::Matrix<var, -1, -1> y_m = pmx_integrate_ode_group_adams(f, y0_m, t0, len, ts_m, theta_var_m , x_r_m, x_i_m);
}

#endif


#ifndef KA_TYPE_INTEGERSEQUENCE_HPP
#define KA_TYPE_INTEGERSEQUENCE_HPP
#pragma once
#include <boost/config.hpp>

/// @file Implements std::integer_sequence types from C++14.
///
/// This implementation is from the stdlib shipped with g++7.
///
/// TODO: Remove this file when C++14 is available.

namespace ka
{
  namespace detail
  {
    // Stores a tuple of indices. Used by tuple and pair, and by bind() to
    // extract the elements in a tuple.
    template<size_t... Indexes>
    struct Index_tuple
    {
    };

    // Concatenates two Index_tuples.
    template<typename Itup1, typename Itup2>
    struct Itup_cat;

    template<size_t... Ind1, size_t... Ind2>
    struct Itup_cat<Index_tuple<Ind1...>, Index_tuple<Ind2...>>
    {
      using type = Index_tuple<Ind1..., (Ind2 + sizeof...(Ind1))...>;
    };

    // Builds an Index_tuple<0, 1, 2, ..., Num-1>.
    template<size_t Num>
    struct Build_index_tuple
      : Itup_cat<typename Build_index_tuple<Num / 2>::type,
                 typename Build_index_tuple<Num - Num / 2>::type>
    {
    };

    template<>
    struct Build_index_tuple<1>
    {
      typedef Index_tuple<0> type;
    };

    template<>
    struct Build_index_tuple<0>
    {
      typedef Index_tuple<> type;
    };

  } // namespace detail

  /// Class template integer_sequence
  template<typename Tp, Tp... Idx>
  struct integer_sequence
  {
    typedef Tp value_type;
    static BOOST_CONSTEXPR size_t size()
    {
      return sizeof...(Idx);
    }
  };

  namespace detail
  {
    template<typename Tp, Tp Num, typename ISeq = typename Build_index_tuple<Num>::type>
    struct Make_integer_sequence;

    template<typename Tp, Tp Num,  size_t... Idx>
    struct Make_integer_sequence<Tp, Num, Index_tuple<Idx...>>
    {
      static_assert(Num >= 0,
         "Cannot make integer sequence of negative length");

      typedef integer_sequence<Tp, Idx...> type;
    };

    template<typename Tp, Tp Num>
    struct make_integer_sequence : detail::Make_integer_sequence<Tp, Num> {};
  } // namespace detail

  /// Alias template make_integer_sequence
  template<typename Tp, Tp Num>
  using make_integer_sequence = typename detail::make_integer_sequence<Tp, Num>::type;

  /// Alias template index_sequence
  template<size_t... Idx>
  using index_sequence = integer_sequence<size_t, Idx...>;

  /// Alias template make_index_sequence
  template<size_t Num>
  using make_index_sequence = typename detail::make_integer_sequence<size_t, Num>::type;

  /// Alias template index_sequence_for
  template<typename... Types>
  using index_sequence_for = make_index_sequence<sizeof...(Types)>;
} // namespace ka

#endif // KA_TYPE_INTEGERSEQUENCE_HPP


// #include <boost/python.hpp>
// #include <boost/python/list.hpp>
// #include <boost/python/extract.hpp>

#include "opencv2/core.hpp"
#include <iostream>

using namespace cv;
using namespace std;

#include <sstream>
#include <string>
#include <vector>

#include <pybind11/pybind11.h>
namespace py = pybind11;

struct World
{

    void init_cv_mat(int row, int col)
    {
        cv_mat = Mat ( row, col, CV_64F );
        randu ( cv_mat, Scalar::all ( 0 ), Scalar::all ( 255 ) );
        cout << cv_mat << endl;
    }

    vector<Point2f> pylist2vec( py::list pylist )
    {
        long l = py::len ( pylist );
        vector<Point2f> vec_p;
        vec_p.reserve ( l / 2 );

        for ( long i = 0; i<l/2; ++i ) {
            double x = py::cast<double> ( pylist[i * 2] );
            double y = py::cast<double> ( pylist[i * 2+1] );
            vec_p.push_back ( Point2f ( x, y ) );
        }

        //cout << vec_p << endl;
        return vec_p;
    }

    py::list transform(py::list list1, py::list list2)
    {
        auto vec_p1 = pylist2vec ( list1 );
        auto vec_p2 = pylist2vec ( list2 );
        cout << vec_p1 << endl;
        cout << vec_p2 << endl;

        auto vec_l1 = cv_mat_vec ( Mat ( vec_p1 ) );
        auto vec_l2 = cv_mat_vec ( Mat ( vec_p2 ) );

        py::list two_pylist;
        two_pylist.append ( vec_l1 );
        two_pylist.append ( vec_l2 );

        return two_pylist;
    }

    vector<double> getVector ( const Mat &_t1f )
    {
        Mat t1f;
        _t1f.convertTo ( t1f, CV_64F );
        return (vector<double>)(t1f.reshape ( 1, 1 ));
    }

    py::list cv_mat_vec (const Mat& mat)
    {
        py::list vec_l;

        for(auto e : getVector ( mat ) )
        {
            vec_l.append ( e );
        }

        return vec_l;
    }

    py::list cv_mat_vec_self ()
    {
        return cv_mat_vec(cv_mat);
    }

    Mat cv_mat;
};

PYBIND11_MODULE ( classes_cv, m )
{
    py::class_<World> ( m, "World" )
        .def ( py::init<> () ) // THIS IS MUST
        .def ( "init_cv_mat", &World::init_cv_mat, py::arg ( "row" ) = 1, py::arg ( "col" ) = 1 )
        .def ( "cv_mat_vec_self", &World::cv_mat_vec_self )
        .def ( "transform", &World::transform, py::arg ( "list1" ) = 1, py::arg ( "list2" ) = 1 )
    ;
}


//    Copyright (C) Dimitrios Michail 2019 - 2021.
// 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)

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"

#include <iostream>
#include <set>
#include <boost/graph/adjacency_list.hpp>
#include <boost/property_map/property_map.hpp>

#include <parmcb/forestindex.hpp>

TEST_CASE("forest index")
{

    typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, boost::no_property,
            boost::property<boost::edge_weight_t, double> > Graph;

    Graph graph;

    auto v0 = boost::add_vertex(graph);
    auto v3 = boost::add_vertex(graph);
    auto v5 = boost::add_vertex(graph);
    auto v7 = boost::add_vertex(graph);
    auto v8 = boost::add_vertex(graph);
    auto v9 = boost::add_vertex(graph);
    auto v10 = boost::add_vertex(graph);
    auto v11 = boost::add_vertex(graph);
    auto v12 = boost::add_vertex(graph);
    auto v13 = boost::add_vertex(graph);
    auto v14 = boost::add_vertex(graph);
    auto v15 = boost::add_vertex(graph);
    boost::add_vertex(graph);
    boost::add_vertex(graph);
    auto v18 = boost::add_vertex(graph);

    boost::add_edge(v0, v7, graph).first;
    boost::add_edge(v0, v5, graph).first;
    boost::add_edge(v3, v5, graph).first;
    boost::add_edge(v3, v8, graph).first;
    boost::add_edge(v7, v9, graph).first;
    boost::add_edge(v8, v9, graph).first;
    boost::add_edge(v8, v10, graph).first;
    boost::add_edge(v9, v18, graph).first;
    boost::add_edge(v10, v18, graph).first;
    boost::add_edge(v11, v12, graph).first;
    boost::add_edge(v12, v13, graph).first;
    boost::add_edge(v12, v15, graph).first;
    boost::add_edge(v13, v14, graph).first;
    boost::add_edge(v14, v15, graph).first;

    CHECK(boost::num_vertices(graph) == 15);
    CHECK(boost::num_edges(graph) == 14);

    //
    //   0 -- 7 -- 9 -- 18
    //   |         |     |
    //   5 -- 3 -- 8 -- 10
    //
    //       11 -- 12 -- 13
    //              |      |
    //  16   17     15 --  14
    //

    parmcb::ForestIndex<Graph> fi(graph);

    CHECK(fi.weak_connected_components() == 4);
    CHECK(fi.cycle_space_dimension() == 3);

    int not_on_forest_count = 0;
    int on_forest_count = 0;
    std::set<std::size_t> used;
    for (const auto &e : boost::make_iterator_range(boost::edges(graph))) {
        auto index = fi(e);
        CHECK (used.count(index) == 0);
        used.insert(index);
        CHECK(fi(index) == e);
        CHECK(fi(e) == index);
        if (fi.is_on_forest(e)) {
            on_forest_count++;
        } else {
            not_on_forest_count++;
        }
    }
    CHECK(used.size() == 14);

    CHECK(not_on_forest_count == 3);
    CHECK(on_forest_count == 11);
}