#ifndef _BLOCK_CYCLIC_MATRIX_
#define _BLOCK_CYCLIC_MATRIX_

#include <vector>
#include <string>
#include <cmath>
#include <algorithm>
#include <boost/lexical_cast.hpp>
#include "../core/exceptions.hpp"
#include "scalapack_redist.hpp"
#include "colmajor_matrix.hpp"

#define ITOS boost::lexical_cast<std::string>
#define ARRAY_DESCRIPTOR_SIZE 11
#define VR_BSIZE 256

extern "C" {
  void sl_init_(int* ICONTEXT, int* NPROW, int* NPCOL);

  void blacs_gridinfo_(int* ICONTEXT,
                       int* NPROW, int* NPCOL,
                       int* MYROW, int* MYCOL);

  int numroc_(int* M_, int* M_B, int* M, int* M_SRC, int* M_PROC);

  void descinit_(int* DESC_A, int* M_A, int* N_A,
                 int* MB_A, int* NB_A,
                 int* RSRC_A, int* CSRC_A,
                 int* ICONTEXT, int* LLD_A, int* INFO);

  void blacs_gridexit_(int* ICONTEXT);
  void blacs_exit_(int* STAT);
}

namespace frovedis {

// enum for array descriptor field
enum {
  DTYPE_A = 0,
  CTXT_A,
  M_A,
  N_A,
  MB_A,
  NB_A,
  RSRC_A,
  CSRC_A,
  LLD_A
};

template <class T>
struct blockcyclic_matrix_local {
  blockcyclic_matrix_local() {
    set_local_num(0,0,2);
  }
  blockcyclic_matrix_local(size_t nrow, size_t ncol, size_t t=2) {
    val.resize(nrow*ncol,0);
    set_local_num(nrow,ncol,t);
  }
  blockcyclic_matrix_local(blockcyclic_matrix_local<T>&& m) {
    val.swap(m.val);
    descA.swap(m.descA);
    local_num_row = m.local_num_row;
    local_num_col = m.local_num_col;
    type = m.type;
  }
  blockcyclic_matrix_local<T>&
  operator=(blockcyclic_matrix_local<T>&& m) {
    val.swap(m.val);
    descA.swap(m.descA);
    local_num_row = m.local_num_row;
    local_num_col = m.local_num_col;
    type = m.type;
    return *this;
  }
  blockcyclic_matrix_local(const blockcyclic_matrix_local<T>& m) {
    val = m.val;
    descA = m.descA;
    local_num_row = m.local_num_row;
    local_num_col = m.local_num_col;
    type = m.type;
  }
  blockcyclic_matrix_local<T>&
  operator=(const blockcyclic_matrix_local<T>& m) {
    val = m.val;
    descA = m.descA;
    local_num_row = m.local_num_row;
    local_num_col = m.local_num_col;
    type = m.type;
    return *this;
  }
  void set_local_num(size_t nrow, size_t ncol, size_t t=2) {
     local_num_row = nrow;
     local_num_col = ncol;
     type = t;
  }
  void set_descriptor(const std::vector<size_t>& desc) {
    for(size_t i = 0; i < descA.size(); i++) descA[i] = desc[i];
  }
  bool is_valid() const {
    return ((val.size() == local_num_row * local_num_col) &&
            (descA.size() == ARRAY_DESCRIPTOR_SIZE) &&
            (type >= 1 && type <= 2));
  }
  void debug_print(size_t n = 0) const {
    std::cout << "node = " << get_selfid()
              << ", local_num_row = " << local_num_row
              << ", local_num_col = " << local_num_col
              << ", type = " << type
              << ", descriptor = ";
    for(auto i: descA){ std::cout << i << " "; }
    std::cout << "\nval = "; debug_print_vector(val, n);
  }
  std::vector<T> val;
  std::vector<int> descA;
  size_t local_num_row, local_num_col, type;
  SERIALIZE(val, descA, local_num_row, local_num_col, type)
};

/*
   distributed version
*/

//----------------------------------------------------------------------------
// Name: scalapack_distributor_engine<T>
//
// Description: A distributor engine program which has the global number of rows
// and global number cols information to be distributed over a process grid.
//
// How to start: This engine will automatically get started whenever any of the
// below mentioned operation will be requested by the user for the first time.
//
// How to stop: To stop this engine, user has to invoke
// its "stop" function, scalapack_distributor_engine<T>::stop()
//
// Input - Output of this engine program is described below in short:
// (1) Input: No input (actually a dummy boolean)
//  => Output: An empty distributed blockcyclic matrix with global dimension(nrow,ncol)
// (2) Input: colmajor matrix with global dimension(nrow, ncol)
//  => Output: corresponding blockcyclic matrix with global dimension(nrow,ncol)
// (3) Input: blockcyclic matrix with global dimension(nrow, ncol)
//  => Output: corresponding colmajor matrix with global dimension(nrow,ncol)
//----------------------------------------------------------------------------
template <class T>
struct scalapack_distributor_engine {
  private:
   static bool cmm_grid_created, bcm_grid_created;
   static int cmm_context, bcm_context;
   int nrow, ncol, MB_, NB_;

  public:
   scalapack_distributor_engine() : nrow(0), ncol(0), MB_(0), NB_(0) {}
   scalapack_distributor_engine(int nr, int nc, int mb = 0, int nb = 0) :
                                nrow(nr), ncol(nc), MB_(mb), NB_(nb) {}

   // Operation (1): Returns an empty blockcycic_matrix_local<T>
   blockcyclic_matrix_local<T>
   operator() (bool dummy, int type=2) {
      // quick return
      if(nrow == 0 && ncol == 0)
        return blockcyclic_matrix_local<T>(0,0,type);

      checkAssumption( nrow > 0 && ncol > 0 && type >= 1 && type <= 2);

      std::vector<int> desc;
      if(type == 1) {
#ifdef _DEBUG_STAT_
        std::cout << "invoking engine operation (1.1)\n";
#endif
        if(!cmm_grid_created) create_grid(1);
        desc = create_array_descriptor(cmm_context,1);
      }
      else {
#ifdef _DEBUG_STAT_
        std::cout << "invoking engine operation (1.2)\n";
#endif
        if(!bcm_grid_created) create_grid(2);
        desc = create_array_descriptor(bcm_context,2);
      }
      int local_num_row = desc[ARRAY_DESCRIPTOR_SIZE-2];
      int local_num_col = desc[ARRAY_DESCRIPTOR_SIZE-1];
      blockcyclic_matrix_local<T> outMat(local_num_row,local_num_col,type);
      outMat.descA.swap(desc);
      return outMat;
   }

   // Operation (2): Returns re-distributed blockcycic_matrix_local<T>
   blockcyclic_matrix_local<T>
   operator() (colmajor_matrix_local<T>& inMat,
               int type=2, bool isMovable=false) {
      // quick return
      if(nrow == 0 && ncol == 0 && inMat.val.size()==0)
        return blockcyclic_matrix_local<T>(0,0,type);

     checkAssumption(nrow > 0  && ncol > 0 && type >= 1 && type <= 2);

     if(!cmm_grid_created) create_grid(1);
     std::vector<int> desc = create_array_descriptor(cmm_context,1);

     // tempMat is actually the input colmajor_matrix_local<T> with array descriptor
     blockcyclic_matrix_local<T> tempMat;
     if(isMovable) tempMat.val.swap(inMat.val);
     else tempMat.val = inMat.val;
     tempMat.descA.swap(desc);
     tempMat.set_local_num(inMat.local_num_row,inMat.local_num_col,1); // grid=1

     if(type == 1) {
#ifdef _DEBUG_STAT_
       std::cout << "invoking engine operation (2.1)\n";
#endif
       return tempMat;
     }
     else {
#ifdef _DEBUG_STAT_
       std::cout << "invoking engine operation (2.2)\n";
#endif
       blockcyclic_matrix_local<T> outMat = cmm_to_bcm(tempMat);
       return outMat;
     }
   }

   // Operation (3): Returns re-distributed colmajor_matrix_local<T>
   colmajor_matrix_local<T>
   operator() (blockcyclic_matrix_local<T>& inMat,bool isMovable=false) {
     // quick return
     if(nrow == 0 && ncol == 0 && inMat.val.size()==0)
       return colmajor_matrix_local<T>(0,0);

     checkAssumption(nrow > 0 && ncol > 0);

     colmajor_matrix_local<T> outMat;
     if (inMat.type == 1){
#ifdef _DEBUG_STAT_
     std::cout << "invoking engine operation (3.1)\n";
#endif
       if(isMovable) outMat.val.swap(inMat.val);
       else outMat.val = inMat.val;
       outMat.local_num_row = inMat.local_num_row;
       outMat.local_num_col = inMat.local_num_col;
     }
     else {
#ifdef _DEBUG_STAT_
     std::cout << "invoking engine operation (3.2)\n";
#endif
       blockcyclic_matrix_local<T> tempMat = bcm_to_cmm(inMat);
       outMat.val.swap(tempMat.val);
       outMat.local_num_row = tempMat.local_num_row;
       outMat.local_num_col = tempMat.local_num_col;
     }
     return outMat;
   }

  // the function to stop the engine (if it is started)
  static void stop() {
    int nproc = get_nodesize();
    int nprow = std::sqrt(nproc);
    int npcol = nproc / nprow;
    // only process in bcm_grid, will invoke this exit call
    if(bcm_grid_created && (get_selfid() < nprow*npcol))
      blacs_gridexit_(&bcm_context);

    // considering (nrow > nproc), all processes are part of cmm_grid
    if(cmm_grid_created)
      blacs_gridexit_(&cmm_context);

    //resetting static members
    cmm_grid_created = bcm_grid_created = false;
    cmm_context = bcm_context = -1;

    int stat = 0;
    blacs_exit_(&stat);
  }

// --- PRIVATE SECTION ---
  private:
//------------------------

   // input : blockcyclic matrix with NPROC x 1 grid (colmajor matrix)
   // output: blockcyclic matrix with NPROW x NPCOL grid
   blockcyclic_matrix_local<T>
   cmm_to_bcm(blockcyclic_matrix_local<T>& inMat) {
      if(!check_input_validity(inMat))
        REPORT_ERROR(USER_ERROR,
	      "Input matrix size is incompatible with input global size!!\n");

      if (!cmm_grid_created)
        REPORT_ERROR(USER_ERROR,"Invalid call: NPROC x 1 grid not created!!\n");

      if (!bcm_grid_created) create_grid(2);
      std::vector<int> desc = create_array_descriptor(bcm_context,2);

      int local_num_row = desc[ARRAY_DESCRIPTOR_SIZE-2];
      int local_num_col = desc[ARRAY_DESCRIPTOR_SIZE-1];
      blockcyclic_matrix_local<T> outMat(local_num_row,local_num_col,2); //grid=2

      // === Re-distribution colmajor matrix => blockcyclic matrix ===
      // https://software.intel.com/en-us/node/521674
      // The "context" encompassing at least the union of all processes in
      // cmm_context and bcm_context should be passed in this routine call.
      // Here cmm_context covers all the processes. Thus, it is passed.
      // All processes in the passed "context" must call this routine,
      // even if they do not own a piece of either matrix.
      int IJ = 1;
      T* aptr = &inMat.val[0];
      T* bptr = &outMat.val[0];
      int* DESCA = &inMat.descA[0];
      int* DESCB = &desc[0];
      scalapack_redist<T>(&nrow, &ncol,
                          aptr, &IJ, &IJ, DESCA,
                          bptr, &IJ, &IJ, DESCB, &cmm_context);

      outMat.descA.swap(desc);
      return outMat;
   }

   // input : blockcyclic matrix with NPROW x NPCOL grid
   // output: blockcyclic matrix with NPROC x 1 grid (colmajor matrix)
   blockcyclic_matrix_local<T>
   bcm_to_cmm(blockcyclic_matrix_local<T>& inMat) {
      if(!check_input_validity(inMat))
        REPORT_ERROR(USER_ERROR,
	      "Input matrix size is incompatible with input global size!!\n");

      if (!bcm_grid_created)
        REPORT_ERROR(USER_ERROR,"Invalid call: NPROW x NPCOL grid not created!!\n");

      if (!cmm_grid_created) create_grid(1);
      std::vector<int> desc = create_array_descriptor(cmm_context,1);

      int local_num_row = desc[ARRAY_DESCRIPTOR_SIZE-2];
      int local_num_col = desc[ARRAY_DESCRIPTOR_SIZE-1];
      // outMat is actually the output colmajor_matrix_local<T> with array descriptor
      blockcyclic_matrix_local<T> outMat(local_num_row,local_num_col,1); //grid=1

      // === Re-distribution blockcyclic matrix => colmajor matrix ===
      // https://software.intel.com/en-us/node/521674
      // The "context" encompassing at least the union of all processes in
      // cmm_context and bcm_context should be passed in this routine call.
      // Here cmm_context covers all the processes. Thus, it is passed.
      // All processes in the passed "context" must call this routine,
      // even if they do not own a piece of either matrix.
      int IJ = 1;
      T* aptr = &inMat.val[0];
      T* bptr = &outMat.val[0];
      int* DESCA = &inMat.descA[0];
      int* DESCB = &desc[0];
      scalapack_redist<T>(&nrow, &ncol,
                          aptr, &IJ, &IJ, DESCA,
                          bptr, &IJ, &IJ, DESCB, &cmm_context);

      outMat.descA.swap(desc);
      return outMat;
   }

   // if all the inputs of the functor call are valid for re-distribution,
   // then this function returns true, else it returns false
   bool check_input_validity(blockcyclic_matrix_local<T>& inMat) {
     if(!inMat.is_valid())
       REPORT_ERROR(USER_ERROR,"Invalid input matrix!!\n");

     int icontext = inMat.descA[CTXT_A];
     int global_row_size = inMat.descA[M_A];
     int global_col_size = inMat.descA[N_A];
     int MB = inMat.descA[MB_A];
     int NB = inMat.descA[NB_A];
     int RSRC = inMat.descA[RSRC_A];
     int CSRC = inMat.descA[CSRC_A];

     bool is_valid = false;
     int nproc = get_nodesize();
     int l_num_row = 0, l_num_col = 0;
     int myrow = -1, mycol = -1;
     int nprow = -1, npcol = -1;
     blacs_gridinfo_(&icontext, &nprow, &npcol, &myrow, &mycol);

     if(myrow >= 0 && myrow < nprow &&
        mycol >= 0 && mycol < npcol) { // which are in process grid
       l_num_row = numroc_(&nrow, &MB, &myrow, &RSRC, &nprow);
       l_num_col = numroc_(&ncol, &NB, &mycol, &CSRC, &npcol);
       // adjustment for blockcyclic matrix (as per array descriptor creation)
       if(nprow != nproc) {
         l_num_row = l_num_row > 0 ? l_num_row : 1;
         l_num_col = l_num_col > 0 ? l_num_col : 1;
       }
       // for blockcyclic matrix and colmajor matrix loaded using loadbinary()
       bool t1 = (inMat.local_num_row == l_num_row  &&
                 inMat.local_num_col == l_num_col);
       // for colmajor matrix loaded using load()
       bool t2 = (inMat.local_num_row == 0 && inMat.local_num_col == 0);
       if(nrow == global_row_size && ncol == global_col_size && (t1 || t2))
         is_valid = true;
     }
     else { // which are not in process grid
       if(inMat.val.size() == 0)
         is_valid = true;
     }

#ifdef _BCM_DEBUG_
     std::cout << "[check_input_validity] node: " << get_selfid()
               << ", nrow: " << nrow
               << ", ncol: " << ncol
               << ", global_row_size: " << global_row_size
               << ", global_col_size: " << global_col_size
               << ", context: " << icontext
               << ", local_num_row: " << inMat.local_num_row
               << ", local_num_col: " << inMat.local_num_col
               << ", l_num_row: " << l_num_row
               << ", l_num_col: " << l_num_col << std::endl;
#endif
     return is_valid;
   }

   // to create the requested process grid (only two types are supported)
   // Type 1 Grid: NPROC x 1
   // Type 2 Grid: NPROW x NPCOL
   void create_grid(int type) {
     int node_size = get_nodesize();
     checkAssumption (node_size > 0);
     int nprow = -1, npcol = -1;
     switch(type) {
       case 1: if(!cmm_grid_created) {
                 nprow = node_size;
	         npcol = 1;
	         sl_init_(&cmm_context, &nprow, &npcol);
	         cmm_grid_created = true;
                 if((nrow < node_size) && (get_selfid() == 0)) {
                   std::string msg;
                   msg = "\n #nrow: "  + ITOS(nrow) +
                         " #nproc: " + ITOS(node_size) + " - ";
                   msg += "Too small problem size (usage of serial version is recommended)!!\n";
                   REPORT_WARNING(WARNING_MESSAGE, msg);
                 }
	       }
	       break;
       case 2: if(!bcm_grid_created) {
                 nprow = std::sqrt(node_size);
	         npcol = node_size / nprow;
	         sl_init_(&bcm_context, &nprow, &npcol);
	         bcm_grid_created = true;

                 int np_proc = nprow * npcol;
                 if( (np_proc != node_size) && (get_selfid() == 0)) {
	           std::string msg;
                   msg = "\n Total number of processes in NPROW x NPCOL grid: " +
                          ITOS(np_proc) + "\n";
	           REPORT_WARNING(WARNING_MESSAGE, msg);
	         }
	       }
	       break;
       default: REPORT_ERROR(USER_ERROR,"Unsupported Grid Type!!\n");
     }
   }

   // to create the array descriptor for the requested grid type
   std::vector<int>
   create_array_descriptor(int icontext, int type) {

     int node_size = get_nodesize();
     checkAssumption(node_size > 0);

     std::vector<int> desc(ARRAY_DESCRIPTOR_SIZE,0);
     // https://software.intel.com/en-us/node/521674
     // if the calling process is not a part of the input context,
     // it must set the descriptor-context-field [1] as -1.
     desc[CTXT_A] = -1;

     // blacs query on on the input context to know
     // the grid dimension and current process co-ordinates
     int nprow = -1, npcol = -1, myrow = -1, mycol = -1;
     blacs_gridinfo_(&icontext, &nprow, &npcol, &myrow, &mycol);

     int local_num_row = 0, local_num_col = 0;
     int i_zero = 0;
     int LLD = 1, MB = 0, NB = 0, INFO = -1;

     // === The array descriptor initialization ===
     if (type == 1) {
       // All the participating processes must correctly define
       // all the parameters describing the respective array (colmajor matrix).
       MB = ceil_div(nrow, node_size);
       NB = ncol;
       local_num_row = numroc_(&nrow, &MB, &myrow, &i_zero, &nprow);
       local_num_col = ncol;
       LLD = local_num_row > 0 ? local_num_row : 1;

       descinit_(&desc[0], &nrow, &ncol,
                 &MB, &NB,
                 &i_zero, &i_zero,
                 &icontext, &LLD, &INFO);
       if(INFO)
         REPORT_ERROR(INTERNAL_ERROR,
                      "Descriptor routine for NPROC x 1 grid aborted!!\n");
     }
     else if(type == 2) {
       if(myrow >= 0 && myrow < nprow &&
          mycol >= 0 && mycol < npcol) { // processes which are in bcm_grid
         // Only processes in this input context must correctly define all parameters
         // describing the blockcyclic matrix.

         checkAssumption(MB_ >= 0 && NB_ >= 0);

         if (MB_ == 0 || NB_ == 0) {
            MB = blockset(nrow, nprow, npcol);
            NB = blockset(ncol, nprow, npcol);
         }
         // Using user blocksizes, when both MB and NB is supplied as non-zero
         else {
            MB = MB_;
            NB = NB_;
         }
         local_num_row = numroc_(&nrow, &MB, &myrow, &i_zero, &nprow);
         local_num_row = local_num_row > 0 ? local_num_row : 1;
         local_num_col = numroc_(&ncol, &NB, &mycol, &i_zero, &npcol);
         local_num_col = local_num_col > 0 ? local_num_col : 1;
         LLD = local_num_row;

         descinit_(&desc[0], &nrow, &ncol,
                   &MB, &NB,
                   &i_zero, &i_zero,
                   &icontext, &LLD, &INFO);
         if(INFO)
           REPORT_ERROR(INTERNAL_ERROR,
                        "Descriptor routine for NPROW x NPCOL grid aborted!!\n");
       }
       else { // to make it valid for sliced_matrix
         desc[M_A] = nrow;
         desc[N_A] = ncol;
       }
     }
     else
       REPORT_ERROR(USER_ERROR,"Unsupported Grid Type!!\n");

     // NOTE: Processes, not in bcm-grid, will have 0 for the below fields
     desc[ARRAY_DESCRIPTOR_SIZE-2] = local_num_row;
     desc[ARRAY_DESCRIPTOR_SIZE-1] = local_num_col;

#ifdef _BCM_DEBUG_
     std::cout << "Descriptor created: type: " << type
               << " cntxt: " << icontext
               << " val: ";
     for(int i=0; i<ARRAY_DESCRIPTOR_SIZE; i++) std::cout << desc[i] << " ";
     std::cout << std::endl;
#endif

     return desc;
   }

   // the blockset routine to compute the optimum block-size (MB/NB)
   int blockset(int n, int nprow, int npcol, int user_bsize = VR_BSIZE) {
     int bsize = std::min(ceil_div(n,nprow),ceil_div(n,npcol));
     if(user_bsize > 0) bsize = std::min(bsize, user_bsize);
     bsize = (bsize < 1) ? 1 : bsize;
     return bsize;
   }

   SERIALIZE(nrow, ncol, MB_, NB_,
             cmm_context, cmm_grid_created,
             bcm_context, bcm_grid_created)
};

// Initialization of the static data members...
template <class T>
int scalapack_distributor_engine<T>::cmm_context = -1;
template <class T>
bool scalapack_distributor_engine<T>::cmm_grid_created = false;

template <class T>
int scalapack_distributor_engine<T>::bcm_context = -1;
template <class T>
bool scalapack_distributor_engine<T>::bcm_grid_created = false;

template <class T>
blockcyclic_matrix_local<T>
copy_local_data(const blockcyclic_matrix_local<T>& m) {
  return blockcyclic_matrix_local<T>(m);
}

template <class T>
void transpose_impl(blockcyclic_matrix_local<T>& inMat,
                    blockcyclic_matrix_local<T>& outMat) {
   T* aptr = &inMat.val[0];
   int* DESCA = &inMat.descA[0];
   T* bptr = &outMat.val[0];
   int* DESCB = &outMat.descA[0];
   int IJA = 1, IJB = 1;
   T ALPHA = 1.0;
   T BETA = 0.0;
   char TRANS = 'T';
   int M = outMat.descA[M_A];
   int N = outMat.descA[N_A];
   geadd_wrapper<T>(&TRANS,
                    &M, &N,
                    &ALPHA,
                    aptr, &IJA, &IJA, DESCA,
                    &BETA,
                    bptr, &IJB, &IJB, DESCB);
}

template <class T>
struct blockcyclic_matrix {
  blockcyclic_matrix() : num_row(0), num_col(0), type(2) {
     auto dummy = frovedis::make_node_local_allocate<bool>();
     data = dummy.template map<blockcyclic_matrix_local<T>>
              (scalapack_distributor_engine<T>(0,0),broadcast(type));
  }
  blockcyclic_matrix(size_t nrow, size_t ncol, size_t t=2) :
     num_row(nrow), num_col(ncol), type(t) {
     auto dummy = frovedis::make_node_local_allocate<bool>();
     data = dummy.template map<blockcyclic_matrix_local<T>>
              (scalapack_distributor_engine<T>(nrow, ncol),broadcast(type));
  }
  blockcyclic_matrix(frovedis::node_local<blockcyclic_matrix_local<T>>&& d) :
    data(std::move(d)) {}

  blockcyclic_matrix(const blockcyclic_matrix<T>& m) {
#ifdef _DEBUG_STAT_
    std::cout << "invoking copy ctor of blockcyclic-matrix\n";
#endif
    auto& m2 = const_cast<blockcyclic_matrix<T>&>(m);
    data = m2.data.map(copy_local_data<T>);
    set_num(m2.num_row,m2.num_col,m2.type);
  }

  blockcyclic_matrix<T>& operator=(const blockcyclic_matrix<T>& m) {
#ifdef _DEBUG_STAT_
    std::cout << "invoking copy op= of blockcyclic-matrix\n";
#endif
    auto& m2 = const_cast<blockcyclic_matrix<T>&>(m);
    data = m2.data.map(copy_local_data<T>);
    set_num(m2.num_row,m2.num_col,m2.type);
    return *this;
  }

  blockcyclic_matrix(blockcyclic_matrix<T>&& m): data(std::move(m.data)) {
#ifdef _DEBUG_STAT_
    std::cout << "invoking move ctor of blockcyclic-matrix\n";
#endif
    set_num(m.num_row,m.num_col,m.type);
  }
  blockcyclic_matrix<T>& operator=(blockcyclic_matrix<T>&& m) {
#ifdef _DEBUG_STAT_
    std::cout << "invoking move op= of blockcyclic-matrix\n";
#endif
    data = std::move(m.data);
    set_num(m.num_row,m.num_col,m.type);
    return *this;
  }

  // implicit conversion:
  // (lvalue) colmajor_matrix<T> => blockcyclic_matrix<T>
  blockcyclic_matrix(const colmajor_matrix<T>& c_mat,size_t type=2,
                     size_t MB = 0, size_t NB = 0) {
#ifdef _DEBUG_STAT_
    std::cout << "(lvalue) colmajor_matrix -> blockcyclic_matrix (type): "
              << type << "\n";
#endif
    checkAssumption(type>=1 && type<=2);
    if(type == 1) { MB = NB = 0; } // ignoring user input
    auto& c_mat2 = const_cast<colmajor_matrix<T>&>(c_mat);
    size_t nrow = c_mat2.num_row;
    size_t ncol = c_mat2.num_col;
    data = std::move(c_mat2.data.template map<blockcyclic_matrix_local<T>>
                    (scalapack_distributor_engine<T>(nrow,ncol,MB,NB),
                     broadcast(type),broadcast(false)));
    set_num(nrow,ncol,type);
  }
  // implicit conversion:
  // (rvalue) colmajor_matrix<T> => blockcyclic_matrix<T>
  blockcyclic_matrix(colmajor_matrix<T>&& c_mat,size_t type=2,
                     size_t MB = 0, size_t NB = 0) {
#ifdef _DEBUG_STAT_
    std::cout << "(rvalue) colmajor_matrix -> blockcyclic_matrix (type): "
              << type << "\n";
#endif
    checkAssumption(type>=1 && type<=2);
    if(type == 1) { MB = NB = 0; } // ignoring user input
    size_t nrow = c_mat.num_row;
    size_t ncol = c_mat.num_col;
    data = std::move(c_mat.data.template map<blockcyclic_matrix_local<T>>
                    (scalapack_distributor_engine<T>(nrow,ncol,MB,NB),
                     broadcast(type),broadcast(true)));
    set_num(nrow,ncol,type);
  }
  // implicit conversion:
  // (lvalue) vector<T> => blockcyclic_matrix<T>
  blockcyclic_matrix(const std::vector<T>& vec,size_t type=1,
                     size_t MB = 0, size_t NB = 0) {
#ifdef _DEBUG_STAT_
    std::cout << "(lvalue) vector -> blockcyclic_matrix (type): "
              << type << "\n";
#endif
    if(vec.size() == 0)
      REPORT_ERROR(USER_ERROR,"Empty input vector!!\n");
    checkAssumption(type>=1 && type<=2);
    if(type == 1) { MB = NB = 0; } // ignoring user input
    size_t nrow = vec.size();
    size_t ncol = 1;
    rowmajor_matrix_local<T> r_mat_loc(vec);
    colmajor_matrix<T> c_mat(make_rowmajor_matrix_scatter<T>(r_mat_loc));
    data = std::move(c_mat.data.template map<blockcyclic_matrix_local<T>>
                    (scalapack_distributor_engine<T>(nrow,ncol,MB,NB),
                     broadcast(type),broadcast(true)));
    set_num(nrow,ncol,type);
  }
  // implicit conversion:
  // (rvalue) vector<T> => blockcyclic_matrix<T>
  blockcyclic_matrix(std::vector<T>&& vec,size_t type=1,
                     size_t MB = 0, size_t NB = 0) {
#ifdef _DEBUG_STAT_
    std::cout << "(rvalue) vector -> blockcyclic_matrix (type): "
              << type << "\n";
#endif
    checkAssumption(type>=1 && type<=2);
    if(type == 1) { MB = NB = 0; } // ignoring user input
    if(vec.size() == 0)
      REPORT_ERROR(USER_ERROR,"Empty input vector!!\n");
    size_t nrow = vec.size();
    size_t ncol = 1;
    rowmajor_matrix_local<T> r_mat_loc(std::move(vec));
    colmajor_matrix<T> c_mat(make_rowmajor_matrix_scatter<T>(r_mat_loc));
    data = std::move(c_mat.data.template map<blockcyclic_matrix_local<T>>
                    (scalapack_distributor_engine<T>(nrow,ncol,MB,NB),
                     broadcast(type),broadcast(true)));
    set_num(nrow,ncol,type);
  }
  std::vector<T> to_vector() {
    if(num_col > 1)
      REPORT_ERROR(USER_ERROR,
        "input is a matrix, can't be converted to a vector!!\n");
    auto rmat = to_colmajor().moveto_rowmajor().gather();
    return rmat.val;
  }
  std::vector<T> moveto_vector() {
    if(num_col > 1)
      REPORT_ERROR(USER_ERROR,
        "input is a matrix, can't be converted to a vector!!\n");
    if(type != 1) REPORT_ERROR(USER_ERROR,"Invalid move operation requested!\n");
    auto rmat = moveto_colmajor().moveto_rowmajor().gather();
    return rmat.val;
  }
  colmajor_matrix<T> to_colmajor() {
    colmajor_matrix<T> outMat(data.template map<colmajor_matrix_local<T>>
                          (scalapack_distributor_engine<T>(num_row,num_col),
                           broadcast(false)));
    outMat.set_num(num_row, num_col);
    return outMat;
  }
  colmajor_matrix<T> moveto_colmajor() {
    if(type != 1) REPORT_ERROR(USER_ERROR,"Invalid move operation requested!\n");
    colmajor_matrix<T> outMat(data.template map<colmajor_matrix_local<T>>
                          (scalapack_distributor_engine<T>(num_row,num_col),
                           broadcast(true)));
    outMat.set_num(num_row, num_col);
    return outMat;
  }
  rowmajor_matrix<T> to_rowmajor() {
    if (num_col == 1)  return to_colmajor().moveto_rowmajor();
    else               return to_colmajor().to_rowmajor();
  }
  blockcyclic_matrix<T> transpose() {
    blockcyclic_matrix<T> ret(num_col, num_row);
    data.mapv(transpose_impl<T>,ret.data); // use pblas p_geadd()
    return ret;
  }
  void set_num(size_t nrow, size_t ncol, size_t t=2) {
    num_row = nrow; num_col = ncol; type = t;
  }
  void save(const std::string& file){
    to_rowmajor().save(file);
  }
  void savebinary(const std::string& dir) {
    to_rowmajor().savebinary(dir);
  }
  void debug_print(size_t n = 0) {
    std::cout << "num_row = " << num_row
              << ", num_col = " << num_col << std::endl;
    auto g = data.gather();
    for(size_t i = 0; i < g.size(); i++) {
      std::cout << "node " << i << std::endl;
      g[i].debug_print(n);
    }
  }
  size_t get_nrows() { return num_row; }
  size_t get_ncols() { return num_col; }

  frovedis::node_local<blockcyclic_matrix_local<T>> data;
  size_t num_row, num_col, type;
};

// make a blockcyclic matrix by loading binary data, stored in input directory
template <class T>
blockcyclic_matrix<T>
make_blockcyclic_matrix_loadbinary(const std::string& dir, size_t type = 2,
                                   size_t MB = 0, size_t NB = 0) {
  colmajor_matrix<T> c_mat (make_rowmajor_matrix_loadbinary<T>(dir));
  return blockcyclic_matrix<T>(std::move(c_mat),type,MB,NB);
}

// make a blockcyclic matrix by loading textual data, stored in input file
template <class T>
blockcyclic_matrix<T>
make_blockcyclic_matrix_load(const std::string& file, size_t type = 2,
                             size_t MB = 0, size_t NB = 0) {
  colmajor_matrix<T> c_mat (make_rowmajor_matrix_load<T>(file));
  return blockcyclic_matrix<T>(std::move(c_mat),type,MB,NB);
}

// make a blockcyclic matrix by scattering a rowmajor_matrix_local<T>
template <class T>
blockcyclic_matrix<T>
make_blockcyclic_matrix_scatter(rowmajor_matrix_local<T>& r_mat, size_t type=2,
                                size_t MB = 0, size_t NB = 0) {
  colmajor_matrix<T> c_mat (make_rowmajor_matrix_scatter<T>(r_mat));
  return blockcyclic_matrix<T>(std::move(c_mat),type,MB,NB);
}

// make a blockcyclic matrix by scattering a (lvalue) std::vector<T>
// deprecated method: kept for backward compatibility
template <class T>
blockcyclic_matrix<T>
make_blockcyclic_matrix_scatter(const std::vector<T>& vec, size_t type=2,
                                size_t MB = 0, size_t NB = 0) {
  if(vec.size() == 0)
    REPORT_ERROR(USER_ERROR,"Empty input vector!!\n");
  rowmajor_matrix_local<T> tmp(vec);
  return make_blockcyclic_matrix_scatter <T> (tmp,type,MB,NB);
}

// make a blockcyclic matrix by scattering a (rvalue) std::vector<T>
// deprecated method: kept for backward compatibility
template <class T>
blockcyclic_matrix<T>
make_blockcyclic_matrix_scatter(std::vector<T>&& vec, size_t type=2,
                                size_t MB = 0, size_t NB = 0) {
  if(vec.size() == 0)
    REPORT_ERROR(USER_ERROR,"Empty input vector!!\n");
  rowmajor_matrix_local<T> tmp(std::move(vec));
  return make_blockcyclic_matrix_scatter<T>(tmp,type,MB,NB);
}

// make a blockcyclic matrix from a std::vector<T> (lValue)
template <class T>
blockcyclic_matrix<T>
inline vec_to_bcm(const std::vector<T>& vec, size_t type=1,
                  size_t MB = 0, size_t NB = 0) {
  if(vec.size() == 0)
    REPORT_ERROR(USER_ERROR,"Empty input vector!!\n");
  return blockcyclic_matrix<T>(vec,type,MB,NB);
}

// make a blockcyclic matrix from a std::vector<T> (rValue)
template <class T>
blockcyclic_matrix<T>
inline vec_to_bcm(std::vector<T>&& vec, size_t type=1,
                  size_t MB = 0, size_t NB = 0) {
  if(vec.size() == 0)
    REPORT_ERROR(USER_ERROR,"Empty input vector!!\n");
  return blockcyclic_matrix<T>(std::move(vec),type,MB,NB);
}

// T can be sliced_blockcyclic_matrix_local<T> or
// sliced_blockcyclic_vector_local<T> or blockcyclic_matrix_local<T>
// to determine whether the current process
// is to be participated in PBLAS/ScaLAPACK routine computations
template <class T>
inline bool is_active(const T& d) {
  if(!(&d.descA[0]))
    REPORT_ERROR(USER_ERROR,"Input slice has NULL parameter\n");
  return (d.descA[CTXT_A] != -1);
}

// to check whether the input arguments of PBLAS/ScaLAPACK routines
// are of the same distribution (with same context)
// since inter-context computations can't be performed using PBLAS/ScaLAPACK
template <class T1, class T2>
inline bool is_same_distribution(const T1& d1, const T2& d2) {
  bool not_null = (&d1.descA[0]) && (&d2.descA[0]);
  if(!not_null) REPORT_ERROR(USER_ERROR,"Input slice has NULL parameter\n");
  return (d1.descA[CTXT_A] == d2.descA[CTXT_A]);
}

template <class T1, class T2, class T3>
inline bool is_same_distribution(const T1& d1, const T2& d2,
                                 const T3& d3) {
  bool not_null = (&d1.descA[0]) && (&d2.descA[0]) && (&d3.descA[0]);
  if(!not_null) REPORT_ERROR(USER_ERROR,"Input slice has NULL parameter\n");
  return (d1.descA[CTXT_A] == d2.descA[CTXT_A] &&
          d1.descA[CTXT_A] == d3.descA[CTXT_A]);
}

template <class T1, class T2, class T3, class T4>
inline bool is_same_distribution(const T1& d1, const T2& d2,
                                 const T3& d3, const T4& d4) {
  bool not_null = (&d1.descA[0]) && (&d2.descA[0]) &&
                  (&d3.descA[0]) && (&d4.descA[0]);
  if(!not_null) REPORT_ERROR(USER_ERROR,"Input slice has NULL parameter\n");
  return (d1.descA[CTXT_A] == d2.descA[CTXT_A] &&
          d1.descA[CTXT_A] == d3.descA[CTXT_A] &&
          d1.descA[CTXT_A] == d4.descA[CTXT_A]);
}

}

#endif


//==================================================================================================
/*!

  Copyright 2015 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#include <boost/simd/function/scalar/if_zero_else_one.hpp>
#include <simd_test.hpp>
#include <boost/dispatch/meta/as_integer.hpp>
#include <boost/simd/constant/inf.hpp>
#include <boost/simd/constant/minf.hpp>
#include <boost/simd/constant/mone.hpp>
#include <boost/simd/constant/nan.hpp>
#include <boost/simd/constant/one.hpp>
#include <boost/simd/constant/zero.hpp>
STF_CASE_TPL (" if_zero_else_one real",  STF_IEEE_TYPES)
{
  namespace bs = boost::simd;
  namespace bd = boost::dispatch;

  using bs::if_zero_else_one;
  using bs::logical;

  // return type conformity test
  STF_EXPR_IS( if_zero_else_one(T()), T);

  // specific values tests
  STF_EQUAL(if_zero_else_one(T(0)), 1);
  STF_EQUAL(if_zero_else_one(T(1)), 0);
  STF_EQUAL(if_zero_else_one(bs::Inf<T>()) , 0);
  STF_EQUAL(if_zero_else_one(bs::Minf<T>()), 0);
  STF_EQUAL(if_zero_else_one(bs::Nan<T>()) , 0);
  STF_EQUAL(if_zero_else_one(bs::Zero<T>()), 1);

} // end of test for floating_

STF_CASE_TPL (" if_zero_else_onesigned int",  STF_SIGNED_INTEGRAL_TYPES)
{
  namespace bs = boost::simd;
  namespace bd = boost::dispatch;
  using bs::if_zero_else_one;
  using bs::logical;

  // return type conformity test
  STF_EXPR_IS( if_zero_else_one(T()), T);

  // specific values tests
  STF_EQUAL(if_zero_else_one(T(0)), 1);
  STF_EQUAL(if_zero_else_one(T(1)), 0);
} // end of test for signed_int_


/**
 * This code is an implementation of the algorithm presented by Dong et al., 2012,
 *"Efficient K-Nearest Neighbor Graph Construction for Generic Similarity Measures"
 * @author:   Mahdi Maghrebi <mahdi.maghrebi@nih.gov>
 * August 2019
 */

#include <vector>
#include <iostream>
#include <list>
#include <string>
#include <math.h>
#include <fstream>
#include <float.h>
#include <boost/filesystem.hpp>
#include <omp.h>
#include <boost/iostreams/device/mapped_file.hpp>
#include <boost/iostreams/stream.hpp>

using boost::iostreams::mapped_file_source;
using boost::iostreams::stream;

using namespace std;

/**
 * Read the output of linux command execution
 * @param  cmd  is the linux command to be executed
 * @return the output from the execution of the linux command
 */
std::string exec(const char* cmd) {
	std::array<char, 128> buffer;
	std::string result;
	std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
	if (!pipe) {
		throw std::runtime_error("popen() failed!");
	}
	while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
		result += buffer.data();
	}
	return result;
}

int main(int argc, char * const argv[]) {
	/**
	 * The errors and informational messages are outputted to the log file
	 */
	ofstream logFile;
	string logFileName="Setting.txt";
	logFile.open(logFileName);

	/**
	 * The input parameters are read from command line which are as follow.
	 * inputPath: The full path to the input file containig the dataset.
	 * outputPath: The full path to the output csv files.
	 * K: K in K-NN that means the desired number of Nearest Neighbours to be computed.
	 * sampleRate: The rate at which we do sampling. This parameter plays a key role in the performance.
	 * This parameter is a trades-off between the performance and the accuracy of the results.
	 * Values closer to 1 provides more accurate results but the execution takes longer.
	 * convThreshold: Convergance Threshold. A fixed integer is used here instead of delta*N*K.
	 * colIndex1 and colIndex2 (optional): The indices of columns from the input csv file where raw data exists continuously in between.
	 * If these two arguments were left blank, the code assumes that the entire input csv file is raw data
	 * and automatically computes the number of columns in the input csv file.
	 */
	string filePath, outputPath, outputPath2, inputPath,LogoutputPath;
	int K,convThreshold, colIndex1=-1, colIndex2=-1;
	float sampleRate;

	for (int i=1; i<argc;++i){
		if (string(argv[i])=="--inputPath") {
			inputPath=argv[i+1];

			if(!boost::filesystem::exists(inputPath) || !boost::filesystem::is_directory(inputPath))
			{
				logFile << "Incorrect input path";
				cout << "Incorrect input path";
				return 1;
			}

			const std::string ext = ".csv";
			boost::filesystem::recursive_directory_iterator it(inputPath);
			boost::filesystem::recursive_directory_iterator endit;

			bool fileFound = false;
			while(it != endit) {
				if(boost::filesystem::is_regular_file(*it) && it->path().extension() == ext){
					fileFound = true;
					filePath = it->path().string();
					break;
				}
				++it;
			}
			if (!fileFound){
				logFile << "CSV file is not found in the input path";
				cout << "CSV file is not found in the input path";
				return 1;
			}
		}
		else if (string(argv[i])=="--K") K=atoi(argv[i+1]);
		else if (string(argv[i])=="--sampleRate") sampleRate=stof(argv[i+1]);
		else if (string(argv[i])=="--convThreshold") convThreshold=stof(argv[i+1]);
		else if (string(argv[i])=="--outputPath"){
			boost::filesystem::path p(argv[i+1]);

			if(!boost::filesystem::exists(p) || !boost::filesystem::is_directory(p))
			{
				logFile << "Incorrect output path";
				cout << "Incorrect output path";
				return 1;
			}

			LogoutputPath=argv[i+1];
			boost::filesystem::path joinedPath = p / boost::filesystem::path("KNN_Indices.csv");
			outputPath = joinedPath.string();
			boost::filesystem::path joinedPath2 = p / boost::filesystem::path("KNN_Distances.csv");
			outputPath2 = joinedPath2.string();
		}
		else if (string(argv[i])=="--colIndex1") {
			colIndex1=stof(argv[i+1]);
			if (colIndex1<1) {
				logFile << "colIndex1 should be greater than 1";
				cout << "colIndex1 should be greater than 1";
				return 1;
			}
		}
		else if (string(argv[i])=="--colIndex2") {
			colIndex2=stof(argv[i+1]);
			if (colIndex2<1) {
				logFile << "colIndex2 should be greater than 1";
				cout << "colIndex2 should be greater than 1";
				return 1;
			}
		}
	}

	logFile<<"------------The following Input Arguments were read------------"<<endl;
	logFile<<"The full path to the input file: "<< filePath<<endl;
	logFile<<"The desired number of NN to be computed: "<< K <<endl;
	logFile<<"The sampleRate(The rate at which we do sampling): "<< sampleRate <<endl;
	logFile<<"The convergance threshold: "<< convThreshold <<endl;
	logFile<<"The full path to the output file1: "<< outputPath<<endl;
	logFile<<"The full path to the output file2: "<< outputPath2<<endl;
	if (colIndex1 != -1) {logFile<<"The optioanl column index starts from: "<< colIndex1<<endl;}
	if (colIndex2 != -1) {logFile<<"The optioanl column index ends at: "<< colIndex2<<endl;}

	cout<<"------------The following Input Arguments were read------------"<<endl;
	cout<<"The full path to the input file: "<< filePath<<endl;
	cout<<"The desired number of NN to be computed: "<< K <<endl;
	cout<<"The sampleRate(The rate at which we do sampling): "<< sampleRate <<endl;
	cout<<"The convergance threshold: "<< convThreshold <<endl;
	cout<<"The full path to the output file1: "<< outputPath<<endl;
	cout<<"The full path to the output file2: "<< outputPath2<<endl;
	if (colIndex1 != -1) {cout<<"The optioanl column index starts from: "<< colIndex1<<endl;}
	if (colIndex2 != -1) {cout<<"The optioanl column index ends at: "<< colIndex2<<endl;}

	/**
	 * Size of Dataset without the header (i.e.(#Rows in dataset)-1).
	 */
	string cmd="wc -l "+filePath;
	string outputCmd = exec(cmd.c_str());
	const int N=stoi(outputCmd.substr(0, outputCmd.find(" ")))-1;
	/**
	 * Dimension of Dataset (#Columns)
	 * is computed automatically if not passed as argument in command line
	 * otherwise, the range (beginning and end) for the column index of input csv file is needed
	 */
	int Dim;
	string cmd2="head -n 1 "+ filePath + " |tr '\\,' '\\n' |wc -l ";
	Dim = stoi(exec(cmd2.c_str()));

	logFile<<"The input csv file contains "<<N<<" rows of raw data with "<< Dim<< " columns(features)"<<endl;
	cout<<"The input csv file contains "<<N<<" rows of raw data with "<< Dim<< " columns(features)"<<endl;

	/**
	 * Query about the number of available processors and set it for OpenMP
	 */
	int nProcessors = omp_get_num_procs();
    omp_set_num_threads(nProcessors-1);
	cout <<"Total Number of Processes in the Parallel Region = "<< nProcessors-1 <<endl;

	/**
	 * A 2D Array containing the entire input dataset (read from filePath).
	 */
	double** dataPoints = new double*[N];
	for (int i = 0; i < N; ++i) { dataPoints[i] = new double[Dim]; }
	/**
	 * indices of K-NN for all the points in dataset
	 */
	int** B_Index = new int*[N];
	for (int i = 0; i < N; ++i) { B_Index[i] = new int[K]; }
	/**
	 * corresponding distance for K-NN indices stored in B_Index
	 */
	double** B_Dist = new double*[N];
	for (int i = 0; i < N; ++i) { B_Dist[i] = new double[K]; }
	/**
	 * corresponding flag for K-NN indices stored in B_Index
	 */
	short** B_IsNew = new short*[N];
	for (int i = 0; i < N; ++i) { B_IsNew[i] = new short[K]; }
	/**
	 * Data structure for new[v]
	 */
	vector<int> *New_Index = new std::vector<int>[N];
	/**
	 * Data structure for REVERSE(new[v]) or new'
	 */
	vector<int> *Reverse_New_Index = new vector<int>[N];
	/**
	 * Data Structure for SAMPLE(new'[v],pk)
	 */
	vector<int> *Sampled_Reverse_New_Index = new vector<int>[N];
	/**
	 * Data Structure for new[v] U SAMPLE(new'[v],pk)
	 */
	vector<int> *New_Final_List = new vector<int>[N];
	/**
	 * An approximation of zero in computing distances. Two points with the distance
	 * smaller than epsilon are considered as one point.
	 */
	double epsilon = 1e-10; //
	short* allEntriesFilled = new short[N];
	/**
	 * At first, let's Read Dataset from Input File Using Memory Mapping
	 */
	mapped_file_source mmap(filePath);
    stream<mapped_file_source> is(mmap, std::ios::binary);
    if (is.fail())
	{
		logFile << "error in Opening Input File" << endl;
		cout << "error in Opening Input File" << endl;
		return 1;
	}
	/**
	 * Remove the header info
	 */
	string dummyLine;
	getline(is, dummyLine);
	/**
	 * Reading the Entire Dataset
	 */
	if (argc==11){
		for (int i = 0; i < N; ++i) {
			string temp, temp2;
			getline(is, temp);
			for (int j = 0; j < Dim; ++j) {
				temp2 = temp.substr(0, temp.find(","));
				dataPoints[i][j] = atof(temp2.c_str());
				temp.erase(0, temp.find(",") + 1);
			}
		}
	} else {
		for (int i = 0; i < N; ++i) {
			string temp, temp2;
			getline(is, temp);
			for (int j = 0; j < Dim; ++j) {
				temp2 = temp.substr(0, temp.find(","));
				if (j >= colIndex1-1 && j < colIndex2) dataPoints[i][j] = atof(temp2.c_str());
				temp.erase(0, temp.find(",") + 1);
			}
		}
	}
	mmap.close();

	if (colIndex1 != -1) Dim=colIndex2-colIndex1+1;
	if (Dim <1) {
		logFile << "Error in Computing the Dimension of input csv file" << endl;
		cout << "Error in Computing the Dimension of input csv file" << endl;
		return 1;
	}
	/**
	 * define a seed for random generator. Using a constant value produces
	 * the same set of random numbers and is good for debugging. Alternatively,
	 * we can select the seed number randomly as srand(time(NULL))
	 */
	srand(17);
	/**
	 * Initialization of Arrays B_IsNew and B_Dist
	 */
	for (int i = 0; i < N; ++i) {
		allEntriesFilled[i]=0;
		for (int j = 0; j < K; ++j) {
			B_IsNew[i][j] = 1;
			B_Dist[i][j] = -1.0;
		}
	}
	/**
	 * Random Initialization of B_Index
	 */
	int randomIndex, iter;
	for (int i = 0; i < N; ++i) {
		for (int j = 0; j < K; ++j) {
			iter = 1;
			while (iter) {
				randomIndex = rand() % N;
				if (randomIndex != i) {
					B_Index[i][j] = randomIndex;
					iter = 0;
				}
			}
		}
	}
	/**
	 * Replace the farthest point in B_Index (for u1) with u2 if u2 is closer
	 * <p>
	 * This method corresponds to UPDATENN(B[u1],<u2,l,true>) in the paper
	 * </p>
	 * @param  Dist  represents B_Dist
	 * @param  Index represents B_Index
	 * @param  IsNew represents B_IsNew
	 * @param  u1    the indice of point that we want to potentially update its K-NN with the point u2
	 * @param  u2    the indice of potential K-NN fpr point u1
	 * @param  distance the spatial distance between u1 and u2
	 * @param  flag updates B_IsNew
	 * @return 1 if B_Index[u1][.] is updated, 0 otherwise
	 */
	auto UpdateNN = [&](int u1, int u2, double distance, int flag = 1) {

		if(allEntriesFilled[u1]==0){
			for (int j = 0; j < K; j++) {
				if (B_Dist[u1][j] < 0) {

					for (int jj = 0; jj < j; jj++) {if (B_Index[u1][jj] == u2) return 0;}

					B_Dist[u1][j] = distance;
					B_Index[u1][j] = u2;
					B_IsNew[u1][j] = flag;
					if (j==K-1) allEntriesFilled[u1]=1;
					return 1;}
			}
		}

		else{
			for (int j = 0; j < K; j++) {
				if (B_Index[u1][j] == u2) return 0;
			}

			double max = DBL_MIN;
			int index = -1;
			for (int j = 0; j < K; j++) {
				if (B_Dist[u1][j] > max) {
					max = B_Dist[u1][j];
					index = j;
				}
			}
			if (index == -1) { cout << "Error"; }
			if (distance < max) {
				B_Dist[u1][index] = distance;
				B_Index[u1][index] = u2;
				B_IsNew[u1][index] = flag;
				return 1;
			}
			else { return 0; }
		}
	};
	/**
	 * Main Loop of the Algorithm
	 */
	bool iterate = true;
	while (iterate) {
		/**
		 * Create "New" for each Datapoint
		 */
		for (int i = 0; i < N; ++i) {
			for (int j = 0; j < K; ++j) {
				if (float(rand() % 100) < sampleRate*100) {
					if (B_IsNew[i][j] == 1) {
						New_Index[i].push_back(B_Index[i][j]);
						B_IsNew[i][j] = 0;
					}
				}
			}
		}
		/**
		 * Create "New'"(or REVERSE("New")) for each Datapoint
		 */
		for (int i = 0; i < N; ++i) {
			for (int j = 0; j < New_Index[i].size(); ++j) {
				Reverse_New_Index[New_Index[i][j]].push_back(i);
			}
		}
		/**
		 * Random Sampling from "New'"
		 */
		for (int i = 0; i < N; ++i) {
			for (int j = 0; j < Reverse_New_Index[i].size(); ++j) {
				if (float(rand() % 100) < sampleRate*100) {
					Sampled_Reverse_New_Index[i].push_back(Reverse_New_Index[i][j]);
				}
			}
		}
		/**
		 * "New"= "New" U SAMPLE("New'", pK)
		 */
		for (int i = 0; i < N; ++i) {
			for (int j = 0; j < New_Index[i].size(); ++j) {
				New_Final_List[i].push_back(New_Index[i][j]);
			}
			for (int j = 0; j < Sampled_Reverse_New_Index[i].size(); ++j) {
				New_Final_List[i].push_back(Sampled_Reverse_New_Index[i][j]);
			}
		}
		/**
		 * c=c+UPDATENN(B[u1],<u2,l,true>)
		 */
		int c_criteria = 0;
		int abort=0;

		for (int i = 0; i < N; ++i) {
			if (abort != 0) break;

            #pragma omp parallel for schedule(dynamic)
			for (int it = 0; it < New_Final_List[i].size(); ++it) {
				int par1= New_Final_List[i][it];

				for (int it2 = it+1; it2 < New_Final_List[i].size(); ++it2) {
					int par2= New_Final_List[i][it2];
					if (par1 != par2 && abort ==0) {

						double dist = 0;
						for (int j = 0; j < Dim; ++j) {
							dist += pow((dataPoints[par1][j] - dataPoints[par2][j]), 2);
						}
						double dista = sqrt(dist);
						if (dista < epsilon) {
							logFile << "Found Duplicate Data for Points "<< par1 << " and " << par2 <<endl;;
							cout << "Found Duplicate Data for Points "<< par1 << " and " << par2 <<endl;
							abort=1;
							iterate = false;
						}
                        #pragma omp critical
						{
							c_criteria += UpdateNN(par1, par2, dista, 1);
							c_criteria += UpdateNN(par2, par1, dista, 1);
						}
					}
				}
			}
		}
		logFile << "c_criteria = " << c_criteria << " With Threshold Convergence of " << convThreshold << endl;
		cout << "c_criteria = " << c_criteria << " With Threshold Convergence of " << convThreshold << endl;
		if (c_criteria < convThreshold) { iterate = false; }
		/**
		 * Clear the contents of the used data structures
		 */
		for (int i = 0; i < N; ++i) {
			New_Index[i].clear();
			Reverse_New_Index[i].clear();
			Sampled_Reverse_New_Index[i].clear();
			New_Final_List[i].clear();
		}
	}
	/**
	 * Sort and output the results
	 */
	ofstream outputFileIndex,outputFileDistance;
	outputFileIndex.open(outputPath);
	outputFileDistance.open(outputPath2);

	for (int i=0; i<N; ++i){
		vector<pair<double,int>> aggregateResults;
		for (int j=0; j<K; ++j){
			aggregateResults.push_back(make_pair(B_Dist[i][j], B_Index[i][j]));
		}
		sort(aggregateResults.begin(), aggregateResults.end());

		for (int j=0; j<K; ++j){
			if (j != K-1) {outputFileIndex<<aggregateResults[j].second<<",";}
			else {outputFileIndex<<aggregateResults[j].second<<endl; }
		}

		for (int j=0; j<K; ++j){
			if (j != K-1) {outputFileDistance<<aggregateResults[j].first<<",";}
			else {outputFileDistance<<aggregateResults[j].first<<endl; }
		}
	}

	outputFileIndex.close();
	outputFileDistance.close();
	logFile.close();
	/**
	 * copy Logfile to the file system which could be accessed outside the docker container
	 */
	string cmd3="cp "+ logFileName+"  "+LogoutputPath;
	string outputCmd3 = exec(cmd3.c_str());

	return 0;
}


//=======================================================================
// Copyright 2001 Jeremy G. Siek, Andrew Lumsdaine, Lie-Quan Lee,
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================
#include <boost/config.hpp>
#include <fstream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graph_utility.hpp>

using namespace boost;

template < typename T >
std::istream& operator>>(std::istream& in, std::pair< T, T >& p)
{
    in >> p.first >> p.second;
    return in;
}

int main(int argc, const char** argv)
{
    typedef adjacency_list< listS, // Store out-edges of each vertex in a
                                   // std::list
        vecS, // Store vertex set in a std::vector
        directedS // The graph is directed
        >
        graph_type;

    std::ifstream file_in(argc >= 2 ? argv[1] : "makefile-dependencies.dat");
    typedef graph_traits< graph_type >::vertices_size_type size_type;
    size_type n_vertices;
    file_in >> n_vertices; // read in number of vertices

    graph_type g(n_vertices); // create graph with n vertices

    // Read in edges
    graph_traits< graph_type >::vertices_size_type u, v;
    while (file_in >> u)
        if (file_in >> v)
            add_edge(u, v, g);
        else
            break;

    assert(num_vertices(g) == 15);
    assert(num_edges(g) == 19);
    return 0;
}


#include "OccupancyGrid/TwoAssumptionAlgorithm.h"
#include "OccupancyGrid/OccupancyGrid.h"
#include "OccupancyGrid/LaserFactor.h"
#include "OccupancyGrid/loadData.h"
#include "OccupancyGrid/loadOccupancyGrid.h"

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include "OccupancyGrid/visualiser.h"

#include "gtsam/discrete/Assignment.h"
#include "gtsam/base/types.h"
#include <boost/program_options.hpp>

using namespace std;
using namespace gtsam;
using namespace occgrid;
namespace po = boost::program_options;

typedef boost::shared_ptr< LaserFactor > LaserFactorPtr;
Visualiser global_vis_;

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

  cv::namedWindow("c", cv::WINDOW_NORMAL);
  // parse arguments ///////////////////////////////////////////
  // Declare the supported options.
  po::options_description desc("Run dual decomposition");
  desc.add_options()
    ("help", "produce help message")
    ("width", po::value<double>(), "Width")
    ("height", po::value<double>(), "Height")
    ("resolution", po::value<double>()->required(), "Size of square cell in the map")
    ("dir", po::value<std::string>()->default_value("Data/player_sim_with_reflectance"), "Data directory")
    ("clock", po::value<double>()->default_value(400), "Max clock")
;

  po::positional_options_description pos;
  pos.add("resolution", 1);

  po::variables_map vm;
  po::store(po::command_line_parser(argc, argv).options(desc).positional(pos).run(), vm);
  po::notify(vm);

  std::string directory = vm["dir"].as<std::string>();
  double max_clock = CLOCKS_PER_SEC * vm["clock"].as<double>();
  // end of parse arguments ////////////////////////////////////
  OccupancyGrid occupancyGrid = loadOccupancyGrid(vm);
  global_vis_.init(occupancyGrid.height(), occupancyGrid.width());

  occupancyGrid.saveLaser("Data/lasers.lsr");

  gtsam::Assignment<gtsam::Index> best_assign;
  std::vector<double> energy(occupancyGrid.cellCount());
  clock_t st = clock();
  two_assumption_algorithm(occupancyGrid, best_assign, energy);

  double Ex = occupancyGrid(best_assign);
  clock_t et = clock();
  std::cout << "<Energy>\t" << ((float)(et - st)) / CLOCKS_PER_SEC << "\t" << Ex << std::endl;
  std::vector<double> probab(energy.size());
  for (size_t i = 0; i < energy.size(); ++i) {
    double e = energy[i];
    double p = std::exp(-e);
    probab[i] = 1 / (1 + p);
  }
  global_vis_.setMarginals(probab);
  std::stringstream ss;
  ss << directory << "/TwoAssumptionAlgo.png";
  global_vis_.save(ss.str());
  global_vis_.show(2000);

  return 0;
}


/*
 * dom_sort_fast.hpp
 *
 *  Created on: Apr 30, 2015
 *      Author: Joost Huizinga
 */

#ifndef MODULES_NSGAEXT_DOM_SORT_FAST_HPP_
#define MODULES_NSGAEXT_DOM_SORT_FAST_HPP_

//Boost includes
#include <boost/foreach.hpp>

//Sferes includes
#include <sferes/stc.hpp>
#include <sferes/dbg/dbg.hpp>
#include <sferes/eval/parallel.hpp>

#ifdef TBB
#include <tbb/atomic.h>
#endif

namespace sferes {
namespace ea {
namespace _dom_sort {

static const int a_dom_b = 1;
static const int b_dom_a = -1;
static const int non_dom = 0;

////// Probabilistic domination sort
//SFERES_CLASS(prob_dom_f){
//public:
//    template<typename Indiv>
//    inline bool operator() (const Indiv &ind, const std::vector<Indiv>&pop) const
//    {
//        BOOST_FOREACH(Indiv i, pop){
//            if (dominate_flag(i, ind) == 1)
//                return false;
//        }
//        return true;
//    }
//
//    template<typename I1, typename I2>
//    inline int dominate_flag(const I1& i1, const I2& i2) const
//    {
//        dbg::assertion(DBG_ASSERTION(i1->fit().objs().size()));
//        dbg::assertion(DBG_ASSERTION(i2->fit().objs().size()));
//        dbg::assertion(DBG_ASSERTION(i1->fit().objs().size() == i2->fit().objs().size()));
//
//        size_t nb_objs = i1->fit().objs().size();
//
//        bool flag1 = false, flag2 = false;
//        for (size_t i = 0; i < nb_objs; ++i)
//        {
//            dbg::assertion(DBG_ASSERTION(i < Params::obj_pressure_size()));
//            float pressure = Params::obj_pressure(i);;
//
//            if (misc::rand<float>() > pressure)
//                continue;
//            float fi1 = i1->fit().obj(i);
//            float fi2 = i2->fit().obj(i);
//            if (fi1 > fi2) {
//                flag1 = true;
//            } else if (fi2 > fi1){
//                flag2 = true;
//            }
//        }
//
//        if (flag1 && !flag2){
//            return a_dom_b;
//        } else if (!flag1 && flag2){
//            return b_dom_a;
//        } else {
//            return non_dom;
//        }
//    }
//};

template<typename Phen, typename DominateComparator>
struct count_dom_custom_comparator
{

#ifdef TBB
    typedef std::vector<tbb::atomic<size_t> > dom_by_t;

#else
    typedef std::vector<size_t> dom_by_t;
#endif
    const std::vector<Phen>& pop;
    dom_by_t& dominatedBy;
    std::vector<std::vector<size_t> >& dominating;
    const DominateComparator& dominateComparator;

    count_dom_custom_comparator(const std::vector<Phen>& pop_,
            dom_by_t& n_,
            std::vector<std::vector<size_t> >& s_,
            const DominateComparator& dominateComparator_) :
                pop(pop_), dominatedBy(n_), dominating(s_), dominateComparator(dominateComparator_)
    {}

    void operator() (const parallel::range_t& range) const{
        dbg::assertion(DBG_ASSERTION(dominatedBy.size() == pop.size()));
        dbg::assertion(DBG_ASSERTION(dominating.size() == pop.size()));
        for (size_t indiv_1_index = range.begin(); indiv_1_index != range.end(); ++indiv_1_index){
//            dbg::out(dbg::info, "dom_sort_op") << "Checking domination for individual: " << indiv_1_index << std::endl;
            dbg::assertion(DBG_ASSERTION(indiv_1_index < dominating.size()));
            dbg::assertion(DBG_ASSERTION(indiv_1_index < dominatedBy.size()));
            dbg::assertion(DBG_ASSERTION(indiv_1_index < pop.size()));
            dbg::assertion(DBG_ASSERTION(dominating[indiv_1_index].empty()));
//            dominatedBy[indiv_1_index] = 0;
            for (size_t indiv_2_index = 0; indiv_2_index < pop.size(); ++indiv_2_index)
            {
                dbg::assertion(DBG_ASSERTION(indiv_2_index < dominatedBy.size()));
                dbg::assertion(DBG_ASSERTION(indiv_2_index < dominating.size()));
                dbg::assertion(DBG_ASSERTION(indiv_2_index < pop.size()));

                int result = dominateComparator.dominate_flag(pop[indiv_1_index], pop[indiv_2_index]);
                if (result == a_dom_b){
#ifdef TBB
                    dominatedBy[indiv_2_index].fetch_and_add(1);
#else
                    ++dominatedBy[indiv_2_index];
#endif
                    dominating[indiv_1_index].push_back(indiv_2_index);
//                    dbg::out(dbg::info, "dom_sort_op") << "Individual: " << indiv_1_index << " dominates individual: " << indiv_2_index << std::endl;
                } else if (result == b_dom_a){
//                    ++dominatedBy[indiv_1_index];
//                    dbg::out(dbg::info, "dom_sort") << "Individual: " << indiv_1_index << " is now dominated by: " << dominatedBy[indiv_1_index] << " individuals." << std::endl;
//                    dominating[indiv_2_index].push_back(indiv_1_index);
                }
            }
        }
    }
};

// cf deb's paper on NSGA-2 :
/// @article{deb2002nsga,
// title={{NSGA-II}},
//   author={Deb, K. and Pratap, A. and Agarwal, S. and Meyarivan, T. and Fast, A. and Algorithm, E.M.G.},
//  journal={IEEE transactions on evolutionary computation},
//   volume={6},
//  number={2},
//  year={2002}
// }
// this algorithm is in O(n^2)
template<typename Indiv, typename DominateComparator>
inline void sort_deb(const std::vector<Indiv>& pop,
        std::vector<std::vector<Indiv> >& fronts,
        const DominateComparator& dominateComparator,
        std::vector<size_t>& ranks)
{
    dbg::assertion(DBG_ASSERTION(!pop.empty()));
    std::vector<std::vector<size_t> > s(pop.size());
    std::vector<std::vector<size_t> > f(1);
    std::vector<size_t> n(pop.size());
    ranks.resize(pop.size());

    std::fill(ranks.begin(), ranks.end(), pop.size());

#ifndef NDEBUG
    BOOST_FOREACH(const Indiv& ind, pop)
    for (size_t i = 0; i < ind->fit().objs().size(); ++i)
    { dbg::assertion(DBG_ASSERTION(!std::isnan(ind->fit().objs()[i]))); }
#endif

    parallel::p_for(parallel::range_t(0, pop.size()),
            _dom_sort::count_dom_custom_comparator<Indiv, DominateComparator>(pop, n, s, ranks, dominateComparator));

    for (size_t i = 0; i < pop.size(); ++i)
        if (ranks[i] == 0)
            f[0].push_back(i);

#ifndef NDEBUG
    BOOST_FOREACH(size_t k, n)
    { dbg::assertion(DBG_ASSERTION(k < pop.size())); }
#endif
    dbg::assertion(DBG_ASSERTION(!f[0].empty()));

    // second step : make layers
    size_t i = 0;
    while (!f[i].empty()){
        f.push_back(std::vector<size_t>());
        for (size_t pp = 0; pp < f[i].size(); ++pp){
            size_t p = f[i][pp];
            for (size_t k = 0; k < s[p].size(); ++k){
                size_t q = s[p][k];
                dbg::assertion(DBG_ASSERTION(q != p));
                dbg::assertion(DBG_ASSERTION(n[q] != 0));
                --n[q];
                if (n[q] == 0){
                    ranks[q] = i + 1;
                    f.back().push_back(q);
                }
            }
        }
        ++i;
        dbg::assertion(DBG_ASSERTION(i < f.size()));
    }

#ifndef NDEBUG
    size_t size = 0;
    BOOST_FOREACH(std::vector<size_t>& v, f)
    size += v.size();
    assert(size == pop.size());
#endif

    // copy indivs to the res
    fronts.clear();
    fronts.resize(f.size());
    for (unsigned i = 0; i < f.size(); ++i)
        for (unsigned j = 0; j < f[i].size(); ++j)
            fronts[i].push_back(pop[f[i][j]]);

    assert(fronts.back().size() == 0);
    fronts.pop_back();
    dbg::assertion(DBG_ASSERTION(!fronts.empty()));
    dbg::assertion(DBG_ASSERTION(!fronts[0].empty()));
}

template<typename Indiv, typename ND>
inline void dom_sort(const std::vector<Indiv>& pop,
        std::vector<std::vector<Indiv> >& fronts,
        const ND& nd,
        std::vector<size_t>& ranks)
{
        _dom_sort::sort_deb(pop, fronts, ranks);
}

} //_dom_sort

struct dom_sort_fast_f{
    template<typename Indiv, typename ND>
    inline void operator() (const std::vector<Indiv>& pop,
            std::vector<std::vector<Indiv> >& fronts,
            const ND& nd,
            std::vector<size_t>& ranks) const
    {
        _dom_sort::dom_sort(pop, fronts, nd, ranks);
    }

    template<typename Indiv>
    inline void operator() (const std::vector<Indiv>& pop,
            std::vector<std::vector<Indiv> >& fronts,
            std::vector<size_t>& ranks) const
    {
        _dom_sort::dom_sort(pop, fronts, _dom_sort_basic::non_dominated_f(), ranks);
    }
};

} //ea
} //sferes

#endif /* MODULES_NSGAEXT_DOM_SORT_FAST_HPP_ */


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

#include <boost/static_assert.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/type_traits/is_convertible.hpp>
#include <cmath>

namespace test
{
    template <class T1>
    struct check_return_type
    {
        template <class T2>
        static void equals(T2)
        {
            BOOST_STATIC_ASSERT((boost::is_same<T1, T2>::value));
        }

        template <class T2>
        static void equals_ref(T2&)
        {
            BOOST_STATIC_ASSERT((boost::is_same<T1, T2>::value));
        }

        template <class T2>
        static void convertible(T2)
        {
            BOOST_STATIC_ASSERT((boost::is_convertible<T2, T1>::value));
        }
    };
}

int main() {
    float f = 0;
    double d = 0;
    long double l = 0;

    test::check_return_type<float>::equals(std::ldexp(f, 0));
    test::check_return_type<double>::equals(std::ldexp(d, 0));
    test::check_return_type<long double>::equals(std::ldexp(l, 0));

    int dummy = 0;

    test::check_return_type<float>::equals(std::frexp(f, &dummy));
    test::check_return_type<double>::equals(std::frexp(d, &dummy));
    test::check_return_type<long double>::equals(std::frexp(l, &dummy));

#if BOOST_HASH_USE_FPCLASSIFY

    int (*fpc1)(float) = std::fpclassify;
    int (*fpc2)(double) = std::fpclassify;
    int (*fpc3)(long double) = std::fpclassify;

#endif
}


#include "refill/distributions/distribution_base.h"

#include <gtest/gtest.h>

#include <Eigen/Dense>

namespace refill {

class DistributionBaseClass : public DistributionBase<DistributionBaseClass> {
  Eigen::VectorXd mean() const override { return Eigen::VectorXd::Zero(1); }

  Eigen::MatrixXd cov() const override { return Eigen::MatrixXd::Ones(1, 1); }

  Eigen::VectorXd drawSample() override { return Eigen::VectorXd::Zero(1); }

  double evaluatePdf(const Eigen::VectorXd& x) const override { return 0.0; }
};

TEST(DistributionBaseTest, VectorizedPdfEvaluationTest) {
  DistributionBaseClass distribution;

  EXPECT_EQ(Eigen::Vector2d::Zero(2),
            distribution.evaluatePdfVectorized(Eigen::MatrixXd::Ones(2, 2)));
}

TEST(DistributionBaseTest, RngAccessorTest) {
  DistributionBaseClass distribution;

  std::mt19937 rng;

  distribution.setRng(rng);

  std::mt19937 dist_rng = distribution.getRng();

  EXPECT_EQ(rng, dist_rng);
}

}  // namespace refill


// this must define the same symbol as the main module file (numpy requirement)
#define PY_ARRAY_UNIQUE_SYMBOL andres_graph_PyArray_API
#define NO_IMPORT_ARRAY

#include <Python.h>
#include <boost/python.hpp>
#include <vigra/numpy_array.hxx>
#include <vigra/numpy_array_converters.hxx>

#include <vector>

#include "andres/graph/grid-graph.hxx"
#include "andres/graph/graph.hxx"
#include "andres/graph/multicut-lifted/lifted_mc_model.hxx"
#include "andres/graph/multicut-lifted/kernighan-lin.hxx"

#include "lifted_mc_solver_exporter.hxx"

namespace bp = boost::python;
namespace agraph = andres::graph;

template<class MODEL, class SETTINGS>
SETTINGS * klSettingsFactory(
    const MODEL & model
){
    return new SETTINGS();
}

template<class MODEL, class SETTINGS, class SOLVER>
SOLVER * klSolverFactory(
    const MODEL & model,
    const SETTINGS & settings
){
    return new SOLVER(model, settings);
}

template<class GRAPH>
void exportLiftedKLT(const std::string graphName){

    typedef agraph::multicut_lifted::LiftedMcModel<GRAPH, float> LiftedModel;
    typedef agraph::multicut_lifted::KernighanLin<LiftedModel> Solver;
    typedef typename Solver::Settings Settings;

    // settings
    const auto settingsClsName = std::string("LiftedKernighanLinSettings") + graphName;
    bp::class_<Settings>(settingsClsName.c_str(),bp::init<>())
        .def_readwrite("numberOfInnerIterations", &Settings::numberOfInnerIterations)
        .def_readwrite("numberOfOuterIterations", &Settings::numberOfOuterIterations)
        .def_readwrite("epsilon", &Settings::epsilon)
        .def_readwrite("timeLimit", &Settings::timeLimit)
        .def_readwrite("verbose", &Settings::verbose)
    ;
    // factory
    bp::def("settingsLiftedKernighanLin", &klSettingsFactory<LiftedModel,Settings>,
            bp::return_value_policy<bp::manage_new_object>());

    // solver
    const auto solverClsName = std::string("LiftedKernighanLin") + graphName;
    bp::class_<Solver>(solverClsName.c_str(),bp::no_init)
        .def(LiftedMcSolverExporter<Solver>("LiftedKernighanLin"))
    ;
        // factory
    bp::def("liftedKernighanLin", &klSolverFactory<LiftedModel,Settings, Solver>,
        bp::with_custodian_and_ward_postcall< 0,1 ,
            bp::return_value_policy<   bp::manage_new_object>  >()
    );
}

void exportLiftedKL(){
    typedef agraph::GridGraph<2> GridGraph2D;
    typedef agraph::GridGraph<3> GridGraph3D;
    typedef agraph::Graph<> Graph;

    exportLiftedKLT<GridGraph2D>("GridGraph2D");
    exportLiftedKLT<GridGraph3D>("GridGraph3D");
    exportLiftedKLT<Graph>("Graph");

}


/*
@copyright Louis Dionne 2014
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#include <boost/hana/set.hpp>

#include <boost/hana/detail/assert.hpp>
#include <boost/hana/detail/minimal/list.hpp>
#include <boost/hana/integral.hpp>
using namespace boost::hana;

template <int i>
constexpr auto x = int_<i>;

int main() {
    constexpr auto list = detail::minimal::list<>;

    BOOST_HANA_CONSTANT_ASSERT(to<Set>(list()) == set());
    BOOST_HANA_CONSTANT_ASSERT(to<Set>(list(x<1>)) == set(x<1>));
    BOOST_HANA_CONSTANT_ASSERT(to<Set>(list(x<1>, x<2>)) == set(x<1>, x<2>));
    BOOST_HANA_CONSTANT_ASSERT(to<Set>(list(x<1>, x<2>, x<3>)) == set(x<1>, x<2>, x<3>));
}


// Copyright 2016 by Glukhov V. O. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <armadillo>
#include <vector>
#include <cstdint>
#include "assert.h"

namespace cnn
{
	struct tensor4d
	{
		tensor4d();
		tensor4d(arma::uword height, arma::uword width, arma::uword depth, std::size_t count);
		tensor4d(const tensor4d &item);
		tensor4d(tensor4d &&item);
		tensor4d& operator=(const tensor4d &item);
		tensor4d& operator=(tensor4d &&item);

		std::vector<arma::Cube<double>> data;
		std::size_t n_size;
		arma::uword n_rows;
		arma::uword n_cols;
		arma::uword n_slices;
	};

	struct kernel_size_t
	{
		explicit kernel_size_t(std::size_t h, std::size_t w)
			: height(h), width(w)
		{
		}

		explicit kernel_size_t(std::size_t size)
			: kernel_size_t(size, size)
		{
		}

		std::size_t height;
		std::size_t width;
	};

	typedef kernel_size_t pad_size_t;

	inline tensor4d::tensor4d()
		: n_rows(0), n_cols(0), n_size(0), n_slices(0)
	{
	}

	inline tensor4d::tensor4d(arma::uword height, arma::uword width, arma::uword depth, std::size_t count)
		: n_rows(height), n_cols(width), n_slices(depth), n_size(count)
	{
		data.reserve(n_size);
		for (std::size_t i = 0; i < n_size; ++i)
			data.push_back(arma::Cube<double>(n_rows, n_cols, n_slices));
	}

	inline tensor4d::tensor4d(const tensor4d& item)
		: data(item.data),
		n_rows(item.n_rows), n_cols(item.n_cols), n_slices(item.n_cols), n_size(item.n_cols)
	{
	}

	inline tensor4d::tensor4d(tensor4d&& item)
		: data(std::move(item.data)),
		n_rows(item.n_rows), n_cols(item.n_cols), n_slices(item.n_cols), n_size(item.n_cols)
	{}

	inline tensor4d& tensor4d::operator=(const tensor4d& item)
	{
		data = item.data;
		n_rows = item.n_rows;
		n_cols = item.n_cols;
		n_slices = item.n_cols;
		n_size = item.n_cols;
		return *this;
	}

	inline tensor4d& tensor4d::operator=(tensor4d&& item)
	{
		data= std::move(item.data);
		n_rows = item.n_rows;
		n_cols = item.n_cols;
		n_slices = item.n_cols;
		n_size = item.n_cols;

		item.n_rows = 0;
		item.n_cols = 0;
		item.n_cols = 0;
		item.n_cols = 0;
		return *this;
	}

	arma::Cube<double> vectorise(const arma::Cube<double>& src);

	arma::Cube<double> unvectorise(const arma::Cube<double>& src, arma::uword height,
	                              arma::uword width, arma::uword depth);

	//convert cv mat with 3 channels to arma cube
	arma::Cube<double> cvMat2armaCube(const cv::Mat& src);
	cv::Mat armaMat2cvMat(const arma::Mat<double> &src);
}


#define DEBUG 1
/**
 * File    : E.cpp
 * Author  : Kazune Takahashi
 * Created : 1/9/2020, 5:23:08 PM
 * Powered by Visual Studio Code
 */
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <vector>
#include <string>
#include <complex>
#include <tuple>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <bitset>
#include <functional>
#include <random>
#include <chrono>
#include <cctype>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstdlib>
// ----- boost -----
#include <boost/rational.hpp>
// ----- using directives and manipulations -----
using boost::rational;
using namespace std;
using ll = long long;
// ----- constexpr for Mint and Combination -----
constexpr ll MOD{1000000007LL};
// constexpr ll MOD{998244353LL}; // be careful
constexpr ll MAX_SIZE{3000010LL};
// constexpr ll MAX_SIZE{30000010LL}; // if 10^7 is needed
// ----- ch_max and ch_min -----
template <typename T>
void ch_max(T &left, T right)
{
  if (left < right)
  {
    left = right;
  }
}
template <typename T>
void ch_min(T &left, T right)
{
  if (left > right)
  {
    left = right;
  }
}
// ----- Mint -----
template <ll MOD = MOD>
class Mint
{
public:
  ll x;
  Mint() : x{0LL} {}
  Mint(ll x) : x{x % MOD} {}
  Mint operator-() const { return x ? MOD - x : 0; }
  Mint &operator+=(const Mint &a)
  {
    if ((x += a.x) >= MOD)
    {
      x -= MOD;
    }
    return *this;
  }
  Mint &operator-=(const Mint &a) { return *this += -a; }
  Mint &operator*=(const Mint &a)
  {
    (x *= a.x) %= MOD;
    return *this;
  }
  Mint &operator/=(const Mint &a)
  {
    Mint b{a};
    return *this *= b.power(MOD - 2);
  }
  Mint operator+(const Mint &a) const { return Mint(*this) += a; }
  Mint operator-(const Mint &a) const { return Mint(*this) -= a; }
  Mint operator*(const Mint &a) const { return Mint(*this) *= a; }
  Mint operator/(const Mint &a) const { return Mint(*this) /= a; }
  bool operator<(const Mint &a) const { return x < a.x; }
  bool operator<=(const Mint &a) const { return x <= a.x; }
  bool operator>(const Mint &a) const { return x > a.x; }
  bool operator>=(const Mint &a) const { return x >= a.x; }
  bool operator==(const Mint &a) const { return x == a.x; }
  bool operator!=(const Mint &a) const { return !(*this == a); }
  const Mint power(ll N)
  {
    if (N == 0)
    {
      return 1;
    }
    else if (N % 2 == 1)
    {
      return *this * power(N - 1);
    }
    else
    {
      Mint half = power(N / 2);
      return half * half;
    }
  }
};
template <ll MOD>
Mint<MOD> operator+(ll lhs, const Mint<MOD> &rhs)
{
  return rhs + lhs;
}
template <ll MOD>
Mint<MOD> operator-(ll lhs, const Mint<MOD> &rhs)
{
  return -rhs + lhs;
}
template <ll MOD>
Mint<MOD> operator*(ll lhs, const Mint<MOD> &rhs)
{
  return rhs * lhs;
}
template <ll MOD>
Mint<MOD> operator/(ll lhs, const Mint<MOD> &rhs)
{
  return Mint<MOD>{lhs} / rhs;
}
template <ll MOD>
istream &operator>>(istream &stream, Mint<MOD> &a)
{
  return stream >> a.x;
}
template <ll MOD>
ostream &operator<<(ostream &stream, const Mint<MOD> &a)
{
  return stream << a.x;
}
// ----- Combination -----
template <ll MOD = MOD, ll MAX_SIZE = MAX_SIZE>
class Combination
{
public:
  vector<Mint<MOD>> inv, fact, factinv;
  Combination() : inv(MAX_SIZE), fact(MAX_SIZE), factinv(MAX_SIZE)
  {
    inv[1] = 1;
    for (auto i = 2LL; i < MAX_SIZE; i++)
    {
      inv[i] = (-inv[MOD % i]) * (MOD / i);
    }
    fact[0] = factinv[0] = 1;
    for (auto i = 1LL; i < MAX_SIZE; i++)
    {
      fact[i] = Mint<MOD>(i) * fact[i - 1];
      factinv[i] = inv[i] * factinv[i - 1];
    }
  }
  Mint<MOD> operator()(int n, int k)
  {
    if (n >= 0 && k >= 0 && n - k >= 0)
    {
      return fact[n] * factinv[k] * factinv[n - k];
    }
    return 0;
  }
  Mint<MOD> catalan(int x, int y)
  {
    return (*this)(x + y, y) - (*this)(x + y, y - 1);
  }
};
// ----- for C++14 -----
using mint = Mint<MOD>;
using combination = Combination<MOD, MAX_SIZE>;
ll gcd(ll x, ll y) { return y ? gcd(y, x % y) : x; }
// ----- frequently used constexpr -----
// constexpr double epsilon{1e-10};
constexpr ll infty{1000000000000000LL};
// constexpr int dx[4] = {1, 0, -1, 0};
// constexpr int dy[4] = {0, 1, 0, -1};
// ----- Yes() and No() -----
void Yes()
{
  cout << "Yes" << endl;
  exit(0);
}
void No()
{
  cout << "No" << endl;
  exit(0);
}
// ----- main() -----

struct Vertex
{
  ll h, c, l, r;
  Vertex() {}
  Vertex(ll h, ll c = 0, ll l = 0, ll r = 0) : h{h}, c{c}, l{l}, r{r} {}

  void upper()
  {
    l += c;
    r += c;
  }
};

ostream &operator<<(ostream &os, Vertex const &v)
{
  return os << "{h:" << v.h << ", c:" << v.c << ", l:" << v.l << ", r:" << v.r << "}";
}

class Solve
{
  // tree construction
  int N;
  string S;
  vector<vector<int>> V;

  // data structure
  int root;
  vector<Vertex> A;

public:
  Solve(int N, string S, vector<vector<int>> const &V) : N{N}, S{S}, V(V), A(N)
  {
    assert(static_cast<int>(S.size()) == N && static_cast<int>(V.size()) == N);
  }

  ll calc()
  {
    ll ans{infty};
    for (auto i = 0; i < N; i++)
    {
      ch_min(ans, solve(i));
    }
    return ans;
  }

private:
  ll solve(int root)
  {
#if DEBUG == 1
    cerr << "root = " << root << endl;
#endif
    init(root);
    dfs(root);
    if (A[root].l == 0)
    {
      return A[root].r / 2;
    }
    else
    {
      return infty;
    }
  }

  void init(int root)
  {
    Solve::root = root;
    for (auto i = 0; i < N; i++)
    {
      A[i] = Vertex(S[i] - '0');
    }
  }

  void dfs(int v, int parent = -1)
  {
    for (auto e : V[v])
    {
      if (e != parent)
      {
        dfs(e, v);
      }
    }
    ll sum_L{0};
    ll sum_R{0};
    ll maxi_L{0};
    A[v].c += A[v].h;
    for (auto e : V[v])
    {
      if (e != parent)
      {
        A[e].upper();
        A[v].c += A[e].c;
        sum_L += A[e].l;
        sum_R += A[e].r;
        ch_max(maxi_L, A[e].l);
      }
    }
    A[v].r = sum_R;
    if (maxi_L > sum_L - maxi_L)
    {
      A[v].l = sum_R;
      for (auto e : V[v])
      {
        if (e != parent && A[e].l == maxi_L)
        {
          if (A[e].l > sum_R - A[e].r)
          {
            ch_min(A[v].l, A[e].l - (sum_R - A[e].r));
          }
          else
          {
            ch_min(A[v].l, sum_L % 2);
          }
        }
      }
    }
    else
    {
      A[v].l = sum_L % 2;
    }
#if DEBUG == 1
    cerr << "A[" << v << "] = " << A[v] << endl;
#endif
  }
};

int main()
{
  int N;
  cin >> N;
  string S;
  cin >> S;
  vector<vector<int>> V(N);
  for (auto i = 0; i < N - 1; i++)
  {
    int a, b;
    cin >> a >> b;
    --a;
    --b;
    V[a].push_back(b);
    V[b].push_back(a);
  }
  Solve solve(N, S, V);
  ll ans{solve.calc()};
  if (ans == infty)
  {
    cout << -1 << endl;
  }
  else
  {
    cout << ans << endl;
  }
}


// Copyright (c) 2013-2016. This code was produced by the
// Australian Centre for Field Robotics, The University of Sydney under
// the Future Flight Planning project, University Reference 13996, contract
// NSW-CPS-2011-015264, Work Orders 5, 7 and 8. The intellectual property
// ownership is as set out in these contracts, as registered with
// Commercial Development and Industry Partnerships.

#include <gtest/gtest.h>
#include <comma/math/compare.h>
#include <iomanip>
#include <cstdlib>
#include <boost/date_time/posix_time/posix_time.hpp>

#include "../../../math/spherical_geometry/coordinates.h"
#include "../../../math/spherical_geometry/great_circle.h"

using namespace snark;
using namespace snark::spherical;

namespace {
    std::pair< double, double > call_is_near( const coordinates & anchor, const std::vector< coordinates > & points, size_t repeat, double epsilon_near, double epsilon_far, bool verbose = false )
    {
        boost::posix_time::ptime start = boost::posix_time::microsec_clock::universal_time();
        {
            bool any_near = false;
            for ( size_t r = 0; r < repeat; ++r ) {
                for ( size_t i = 0; i < points.size(); ++i )
                {
                    any_near &= anchor.is_near( points[i], epsilon_near );
                }
            }
            EXPECT_FALSE( any_near );
        }
        double time_near = double( ( boost::posix_time::microsec_clock::universal_time() - start ).total_microseconds() ) / 1000000;

        // if epsilon is very large, nearly all points are close
        start = boost::posix_time::microsec_clock::universal_time();
        {
            bool any_far = false;
            for ( size_t r = 0; r < repeat; ++r ) {
                for ( size_t i = 0; i < points.size(); ++i )
                {
                    any_far &= ( !anchor.is_near( points[i], epsilon_far ) );
                }
            }
            EXPECT_FALSE( any_far );
        }
        double time_far = double( ( boost::posix_time::microsec_clock::universal_time() - start ).total_microseconds() ) / 1000000;

        return std::make_pair( time_near, time_far );
    }

    double deg2rad( double degrees ) { return degrees / 180.0 * M_PI; }
    double rad2deg( double radians ) { return radians * 180.0 / M_PI; }
}

TEST( coordinates, is_near_performance )
{
    bool verbose = false;
    std::vector< coordinates > points;
    size_t large = 1000;
    size_t repeat = 10;
    points.reserve( large * large );
    boost::posix_time::ptime start = boost::posix_time::microsec_clock::universal_time();
    for ( size_t i = 0; i < large; ++i )
    {
        double latitude = i * M_PI / 2 / large;
        for ( size_t j = 0; j < large; ++j )
        {
            double longitude = -M_PI / 2.0 + j * M_PI / large;
            points.push_back( coordinates( latitude, longitude ) );
        }
    }
    double generate_time = double( ( boost::posix_time::microsec_clock::universal_time() - start ).total_microseconds() ) / 1000000;
    if ( verbose ) { std::cerr << "time/generate: " << generate_time << std::endl; }

    {
        // in the Southern hemisphere, thus, never close; shall use shortcuts all the time unless the threshold is large
        coordinates anchor = coordinates::from_degrees( -10.0, 0.0 );
        std::pair< double, double > timings = call_is_near( anchor, points, repeat, 0.0001, M_PI, verbose );
        EXPECT_TRUE( timings.second >= 10 * timings.first );
    }

    {
        // make latitude the same for all points and the anchor; longitude is different so points are not really close
        coordinates anchor = coordinates::from_degrees( 10.0, 100.0 );
        for ( size_t i = 0; i < points.size(); ++i ) { points[i].latitude = anchor.latitude; }
        std::pair< double, double > timings = call_is_near( anchor, points, repeat, 0.0001, M_PI, verbose );
        EXPECT_TRUE( timings.second >= 10 * timings.first );
    }
}

TEST( coordinates, is_near_basic )
{
    EXPECT_TRUE(  coordinates::is_near( coordinates::from_degrees( 1.0, 1.0 ), coordinates::from_degrees( 1.0, 1.01 ), deg2rad(0.1)   ) );
    EXPECT_FALSE( coordinates::is_near( coordinates::from_degrees( 1.0, 1.0 ), coordinates::from_degrees( 1.0, 1.01 ), deg2rad(0.001) ) );
}

TEST( coordinates, is_near_across_equator )
{
    EXPECT_TRUE(  coordinates::is_near( coordinates::from_degrees( 0.1, 1.0 ), coordinates::from_degrees( -0.1, 1.0 ), deg2rad(0.25) ) );
    EXPECT_FALSE( coordinates::is_near( coordinates::from_degrees( 0.1, 1.0 ), coordinates::from_degrees( -0.1, 1.0 ), deg2rad(0.1)  ) );
}

TEST( coordinates, is_near_across_zero_meridian )
{
    EXPECT_TRUE(  coordinates::is_near( coordinates::from_degrees( 1.0,   0.1 ), coordinates::from_degrees( 1.0, -0.1 ), deg2rad(0.25) ) );
    EXPECT_FALSE( coordinates::is_near( coordinates::from_degrees( 1.0,   0.1 ), coordinates::from_degrees( 1.0, -0.1 ), deg2rad(0.1)  ) );
    EXPECT_TRUE(  coordinates::is_near( coordinates::from_degrees( 1.0, 359.9 ), coordinates::from_degrees( 1.0,  0.1 ), deg2rad(0.25) ) );
    EXPECT_FALSE( coordinates::is_near( coordinates::from_degrees( 1.0, 359.9 ), coordinates::from_degrees( 1.0,  0.1 ), deg2rad(0.1)  ) );
    EXPECT_TRUE(  coordinates::is_near( coordinates::from_degrees( 1.0, 360.0 ), coordinates::from_degrees( 1.0,  0.1 ), deg2rad(0.25) ) );
    EXPECT_FALSE( coordinates::is_near( coordinates::from_degrees( 1.0, 360.0 ), coordinates::from_degrees( 1.0,  0.1 ), deg2rad(0.09) ) );
}

TEST( coordinates, is_near_epsilon_above_globe )
{
    EXPECT_TRUE( coordinates::is_near( coordinates::from_degrees( 10.0,  10.0 ), coordinates::from_degrees(  50.0, -20.0 ), deg2rad(180.1) ) );
    EXPECT_TRUE( coordinates::is_near( coordinates::from_degrees( 60.0, 110.0 ), coordinates::from_degrees( -60.0, -70.0 ), deg2rad(180.1) ) );
    EXPECT_TRUE( coordinates::is_near( coordinates::from_degrees( 90.0,   0.0 ), coordinates::from_degrees( -90.0, 180.0 ), deg2rad(180.1) ) );
}

TEST( coordinates, is_near_epsilon_half_globe )
{
    EXPECT_TRUE(  coordinates::is_near( coordinates::from_degrees( 10.0,  10.0 ), coordinates::from_degrees(  50.0, -20.0 ), deg2rad(179.999) ) );
    EXPECT_FALSE( coordinates::is_near( coordinates::from_degrees( 60.0, 110.0 ), coordinates::from_degrees( -60.0, -70.0 ), deg2rad(179.999) ) );
    EXPECT_FALSE( coordinates::is_near( coordinates::from_degrees( 90.0,   0.0 ), coordinates::from_degrees( -90.0, 180.0 ), deg2rad(179.999) ) );
}

TEST( coordinates, is_near_high_latitude_north )
{
    EXPECT_TRUE( coordinates::is_near( coordinates::from_degrees( 85.0,  10.0 ), coordinates::from_degrees( 85.0, -20.0 ), deg2rad(5.0)  ) );
    EXPECT_TRUE( coordinates::is_near( coordinates::from_degrees( 90.0, 110.0 ), coordinates::from_degrees( 85.0, -70.0 ), deg2rad(5.01) ) );
    EXPECT_TRUE( coordinates::is_near( coordinates::from_degrees( 89.0,   0.0 ), coordinates::from_degrees( 89.0, 180.0 ), deg2rad(5.0)  ) );
}

TEST( coordinates, is_near_high_latitude_south )
{
    EXPECT_TRUE( coordinates::is_near( coordinates::from_degrees( -85.0,  10.0 ), coordinates::from_degrees( -85.0, -20.0 ), deg2rad(5.0)  ) );
    EXPECT_TRUE( coordinates::is_near( coordinates::from_degrees( -90.0, 110.0 ), coordinates::from_degrees( -85.0, -70.0 ), deg2rad(5.01) ) );
    EXPECT_TRUE( coordinates::is_near( coordinates::from_degrees( -89.0,   0.0 ), coordinates::from_degrees( -89.0, 180.0 ), deg2rad(5.0)  ) );
}

TEST( coordinates, is_near_80_latitude )
{
    EXPECT_TRUE( coordinates::is_near( coordinates::from_degrees(  80.1,  10.0 ), coordinates::from_degrees(  79.9,  10.0 ), deg2rad(0.5) ) );
    EXPECT_TRUE( coordinates::is_near( coordinates::from_degrees( -80.1, 110.0 ), coordinates::from_degrees( -79.9, 110.1 ), deg2rad(0.5) ) );
    EXPECT_TRUE( coordinates::is_near( coordinates::from_degrees( -80.1,  -0.1 ), coordinates::from_degrees( -79.9, 360.1 ), deg2rad(0.5) ) );
}

// NOTE: this test relies on the broken, numerically unstable implementation of angle calculations (third, precise branch of is_far)
// the test would start to fail once the problem is fixed; fix the test then, do not try to rid the implementation
TEST( coordinates, is_near_broken_precision )
{
    // enforce the 3rd branch of is_far that relies on unstable computations
    {
        double tiny_distance = rad2deg( 0.5 / 6371000.0 );   // 0.5 m at the Earth radius; still not zero
        double tiny_epsilon = deg2rad(0.5 * tiny_distance);
        EXPECT_TRUE( tiny_epsilon < coordinates::epsilon );
        // epsilon is smaller then distance, cannot be near; but angle is zero and is reported as near
        EXPECT_TRUE( coordinates::is_near( coordinates::from_degrees( tiny_distance, 0.0 ), coordinates::from_degrees( 0.0, 0.0 ), tiny_epsilon ) );
        EXPECT_TRUE( coordinates::is_near( coordinates::from_degrees( 0.0, tiny_distance ), coordinates::from_degrees( 0.0, 0.0 ), tiny_epsilon ) );
    }
    // for reference, enforce NOT taking the 3rd branch of is_far that relies on unstable computations
    {
        double tiny_distance = rad2deg( 16.0 / 6371000.0 );   // 16 m at the Earth radius; still not zero
        double tiny_epsilon = deg2rad(0.5 * tiny_distance);
        EXPECT_FALSE( tiny_epsilon < coordinates::epsilon );
        // epsilon is smaller then distance, cannot be near; but angle is zero and is reported as near
        EXPECT_FALSE( coordinates::is_near( coordinates::from_degrees( tiny_distance, 0.0 ), coordinates::from_degrees( 0.0, 0.0 ), tiny_epsilon ) );
        EXPECT_FALSE( coordinates::is_near( coordinates::from_degrees( 0.0, tiny_distance ), coordinates::from_degrees( 0.0, 0.0 ), tiny_epsilon ) );
    }
}


//
//  Copyright (c) 2021 Kannav Mehta
//
//  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)
//

#ifndef LA_MATRIX_TRAIT_H
#define LA_MATRIX_TRAIT_H

#include <boost/numeric/ublas/matrix/engine_traits.hpp>

namespace boost::numeric::ublas::experimental {
template<typename T>
class matrix;
} // namespace boost::numeric::ublas::experimental

namespace boost::numeric::ublas::experimental::detail {

/// @brief checks if type is a matrix
template<typename T>
struct is_matrix : std::false_type {
};

template<typename T>
struct is_matrix<matrix<T>> : std::true_type {
};

template<typename T>
using is_matrix_t = typename is_matrix<std::decay_t<T>>::type;

template<typename T>
inline constexpr bool is_matrix_v = is_matrix_t<T>::value;

template<typename T>
struct is_matrix<T &> : is_matrix<T> {
};

/// @brief used for type space reduction in expression templates
template<typename T>
using enable_if_matrix = std::enable_if_t<is_matrix_v<T>>;

} // namespace boost::numeric::ublas::experimental::detail

namespace boost::numeric::ublas::experimental {

template <typename E>
struct matrix_traits;

template<typename E>
struct matrix_traits<matrix<E>>;

template<typename E, detail::enable_if_dynamic<E>>
struct matrix_traits<matrix<E>> {

};
}

#endif //LA_MATRIX_TRAIT_H