#define PY_SSIZE_T_CLEAN
#include <Python.h>

#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
#include <math.h>
#include <unistd.h>
#include <sys/types.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <time.h> 

#include <pthread.h>

typedef unsigned long long int COHOM_VV_ID;
typedef unsigned int EDGE_ID;
typedef unsigned int VERT_ID;
typedef double  PAR;

typedef struct{

  VERT_ID neighbor;
  EDGE_ID order;

}Neighbors;

typedef struct{
    
    EDGE_ID key1;
    EDGE_ID key2;

}simplex;

typedef struct{

    EDGE_ID col_idx;
    EDGE_ID o_ab;
    
}H0_pivots;


typedef struct{

  EDGE_ID key2;
  EDGE_ID col_idx;
  EDGE_ID bndry;

}H1_cohom_pivots;

typedef struct{

  EDGE_ID key2;
  EDGE_ID col_idx;
  simplex bndry;

}H2_cohom_pivots;

typedef struct{
    
  int a_ptr;
  int b_ptr;
  EDGE_ID o_ab;
  simplex low;

}coboundary_H1;

typedef struct{

  // The simplex
  simplex triangle;
  // Note: triangle.key1 is o_ab
  // Note: triangle.key2 is c

  VERT_ID a_ptr;
  VERT_ID b_ptr;
  VERT_ID c_ptr;


  // The low of the simplex
  simplex low;
  //key1 is 0: ab, 1: ad, 2: bd, 3: cd
  int vertex;

  //vertex is -1 should mean empty. But have not been consistent.
  //low.key1 = n_valid_edges also means empty.

}coboundary_H2;

typedef struct{

      //int original;
      simplex pivot;

      EDGE_ID len;
      EDGE_ID max_len;
      
      int flag_red_w_complex;
      EDGE_ID complex_find_pivot;

      int flag_red_w_trivial;
      simplex reduce_with_trivial;

      int flag_append_to_complex;
      int flag_non_empty;

      simplex reduce_w_bndry;
      EDGE_ID V_col_idx;

}coboundary_H2_ws;


typedef struct{
      
      int original;

      EDGE_ID len;
      EDGE_ID max_len;
      
      int flag_red_w_complex;
      int flag_append_to_complex;
      int flag_non_empty;

      EDGE_ID pivot;

      
}boundary_H1_ws;


typedef struct{
      
      int original;

      EDGE_ID cob;

      EDGE_ID len;
      EDGE_ID max_len;
      
      int flag_red_w_complex;
      int flag_append_to_complex;
      int flag_non_empty;

      EDGE_ID pivot;

      
}boundary_H0_ws;





int compare_neighbors_vertex(Neighbors s1, Neighbors s2){
    
      if (s1.neighbor < s2.neighbor) return -1;
      else if (s1.neighbor > s2.neighbor) return 1;
      else return 0;

 
}

int compare_neighbors_order(Neighbors s1, Neighbors s2){
    
      if (s1.order < s2.order) return -1;
      else if (s1.order > s2.order) return 1;
      else return 0;

 
}

// This is for tim sort
int compare_simplex(simplex s1, simplex s2){
    
    if (s1.key1 > s2.key1) return 1;
    if (s1.key1 < s2.key1) return -1;
    else{

        if (s1.key2 > s2.key2) return 1;
        else if (s1.key2 < s2.key2) return -1;
        else return 0;

    }

      
}



// This is for tim sort
int compare_cob_H1(coboundary_H1 s1, coboundary_H1 s2){
    
    if (s1.o_ab > s2.o_ab) return 1;
    else if (s1.o_ab < s2.o_ab) return -1;
    else return 0;

      
}

int compare_coboundary_H2(coboundary_H2 s1, coboundary_H2 s2){
    

      if (s1.triangle.key1 < s2.triangle.key1) return -1;
      else if (s1.triangle.key1 > s2.triangle.key1) return 1;
      else{
            if (s1.triangle.key2 < s2.triangle.key2) return -1;
            else if (s1.triangle.key2 > s2.triangle.key2) return 1;
            else return 0;
      }


 
}

#define SORT_NAME sorter
//#define SORT_TYPE int64_t
#define SORT_TYPE Neighbors
#define SORT_CMP(x, y) compare_neighbors_vertex((x), (y))
/* You can redefine the comparison operator.
   The default is
#define SORT_CMP(x, y)  ((x) < (y) ? -1 : ((x) == (y) ? 0 : 1))
   but the one below is often faster for integer types.
*/
//#define SORT_CMP(x, y) ((x->key1) < (y->key1) ? -1 : ((x->key1) == (y->key1) ? ((x->key2 < y->key2 ? -1 : ((x->key2) == (y->key2) ? 0: 1))) : 1))

//#define SORT_CMP(x, y,) compare_partial_cob_dec((x), (y))
#include "sort.h"


#undef SORT_NAME
#undef SORT_TYPE
#undef SORT_CMP

#define SORT_NAME sorter2
//#define SORT_TYPE int64_t
#define SORT_TYPE Neighbors
#define SORT_CMP(x, y) compare_neighbors_order((x), (y))
/* You can redefine the comparison operator.
   The default is
#define SORT_CMP(x, y)  ((x) < (y) ? -1 : ((x) == (y) ? 0 : 1))
   but the one below is often faster for integer types.
*/
//#define SORT_CMP(x, y) ((x->key1) < (y->key1) ? -1 : ((x->key1) == (y->key1) ? ((x->key2 < y->key2 ? -1 : ((x->key2) == (y->key2) ? 0: 1))) : 1))

//#define SORT_CMP(x, y,) compare_partial_cob_dec((x), (y))
#include "sort2.h"


#undef SORT_NAME
#undef SORT_TYPE
#undef SORT_CMP

#define SORT_NAME sorter3
//#define SORT_TYPE int64_t
#define SORT_TYPE EDGE_ID
//#define SORT_CMP(x, y) compare_EDGE_ID(x, y)
/* You can redefine the comparison operator.
   The default is
#define SORT_CMP(x, y)  ((x) < (y) ? -1 : ((x) == (y) ? 0 : 1))
   but the one below is often faster for integer types.
*/
//#define SORT_CMP(x, y) ((x->key1) < (y->key1) ? -1 : ((x->key1) == (y->key1) ? ((x->key2 < y->key2 ? -1 : ((x->key2) == (y->key2) ? 0: 1))) : 1))

//#define SORT_CMP(x, y,) compare_partial_cob_dec((x), (y))
#include "sort3.h"


#undef SORT_NAME
#undef SORT_TYPE
#undef SORT_CMP

#define SORT_NAME sorter4
//#define SORT_TYPE int64_t
#define SORT_TYPE simplex
#define SORT_CMP(x, y) compare_simplex((x), (y))
//#define SORT_CMP(x, y) compare_EDGE_ID(x, y)
/* You can redefine the comparison operator.
   The default is
#define SORT_CMP(x, y)  ((x) < (y) ? -1 : ((x) == (y) ? 0 : 1))
   but the one below is often faster for integer types.
*/
//#define SORT_CMP(x, y) ((x->key1) < (y->key1) ? -1 : ((x->key1) == (y->key1) ? ((x->key2 < y->key2 ? -1 : ((x->key2) == (y->key2) ? 0: 1))) : 1))

//#define SORT_CMP(x, y,) compare_partial_cob_dec((x), (y))
#include "sort4.h"

#undef SORT_NAME
#undef SORT_TYPE
#undef SORT_CMP

#define SORT_NAME sorter5
//#define SORT_TYPE int64_t
#define SORT_TYPE coboundary_H1
#define SORT_CMP(x, y) compare_cob_H1((x), (y))
//#define SORT_CMP(x, y) compare_EDGE_ID(x, y)
/* You can redefine the comparison operator.
   The default is
#define SORT_CMP(x, y)  ((x) < (y) ? -1 : ((x) == (y) ? 0 : 1))
   but the one below is often faster for integer types.
*/
//#define SORT_CMP(x, y) ((x->key1) < (y->key1) ? -1 : ((x->key1) == (y->key1) ? ((x->key2 < y->key2 ? -1 : ((x->key2) == (y->key2) ? 0: 1))) : 1))

//#define SORT_CMP(x, y,) compare_partial_cob_dec((x), (y))
#include "sort5.h"


#undef SORT_NAME
#undef SORT_TYPE
#undef SORT_CMP

#define SORT_NAME sorter6
//#define SORT_TYPE int64_t
#define SORT_TYPE coboundary_H2
#define SORT_CMP(x, y) compare_coboundary_H2((x), (y))
//#define SORT_CMP(x, y) compare_EDGE_ID(x, y)
/* You can redefine the comparison operator.
   The default is
#define SORT_CMP(x, y)  ((x) < (y) ? -1 : ((x) == (y) ? 0 : 1))
   but the one below is often faster for integer types.
*/
//#define SORT_CMP(x, y) ((x->key1) < (y->key1) ? -1 : ((x->key1) == (y->key1) ? ((x->key2 < y->key2 ? -1 : ((x->key2) == (y->key2) ? 0: 1))) : 1))

//#define SORT_CMP(x, y,) compare_partial_cob_dec((x), (y))
#include "sort6.h"






typedef struct{

    char* filename;

    char* g_source;
    char* g_target;

    char* g_H0_pers_file;
    char* g_H1_pers_file;
    char* g_H2_pers_file;

    //char* g_file_prefix;
    //const char* g_H1_boundaries;
    //const char* g_H1_indices;
    //const char* g_H2_boundaries;

    int g_cpu_count;

    int g_dim_lim;

    int g_filetype;

    PAR g_thresh;

    // Vertex data structures
    VERT_ID g_n_vert;

    // Edge data structures
    EDGE_ID g_n_valid_edges;

    EDGE_ID** g_edges_list;

    PAR* g_edge_parameter;


    // Neighbor data structures
    Neighbors** g_Neighbors;
    Neighbors** g_Neighbors_e;
    VERT_ID* g_Neigh_len;
    EDGE_ID g_max_neighbors;



    // WORKSPACE PARAMETERS
    int g_workspace_size;
    int g_ws_pre_alloc;
    int g_ws_counter;


    ////////////////////////////////////
    // Parallel job allocation
    ////////////////////////////////////
    int* g_jobs;

    int g_sleeping_threads;
    int g_processed_threads;

    int g_thread_id;

    int g_delete_threads;

    pthread_t *g_threads;

    pthread_mutex_t g_thread_lock;
    pthread_cond_t g_start_boss;
    pthread_cond_t g_start_workers;


    ////////////////////////////////////
    // H0 Structures
    ////////////////////////////////////

    // Pivots for H0
    // i is pivot of A[i]
    EDGE_ID* g_pivots_H0;

    // STORE R for H0
    EDGE_ID* g_R_sparse_H0;
    EDGE_ID g_R_sparse_ptr_H0;
    EDGE_ID g_R_sparse_max_H0;

    // Mapping of R columns to sparse R linear
    EDGE_ID* g_R_col_indices_H0;
    EDGE_ID g_R_col_indices_max_H0;
    EDGE_ID g_R_col_indices_ptr_H0;


    // Store pivot for H0
    EDGE_ID* g_edges_with_pivots_H0;

    // H0 WORKSPACE STRUCTURES
    EDGE_ID** g_R_ws_H0; 

    boundary_H0_ws* g_R_ws_H0_info;

    ////////////////////////////////////
    // cohomology H1 structures
    ////////////////////////////////////
   

    coboundary_H1* g_coH1_all_lows;
    
    // V SPARSE
    
    EDGE_ID* g_V_sparse_H1;
    EDGE_ID g_V_sparse_max;
    EDGE_ID g_V_sparse_ptr;
    EDGE_ID g_V_sparse_beg_ptr;
    EDGE_ID g_V_sparse_end_ptr;

    EDGE_ID* g_V_col_indices;
    EDGE_ID g_V_col_indices_max;
    EDGE_ID g_V_col_indices_ptr;

    // V temp workspace
    int g_V_temp_len;
    int g_V_temp_max_len;
    coboundary_H1* g_V_temp;

    // Parallel 'next' coH1
    simplex*  g_this_pivot_thread;
    simplex g_this_pivot;
    int*  g_coeff_thread;
    int*  g_empty_thread;


    // PIVOTS OF H1 COHOMOLOGY
    H1_cohom_pivots** g_H1_cohom_pivots;
    EDGE_ID* g_H1_cohom_pivots_len;
    EDGE_ID* g_H1_cohom_pivots_max_len;

    // Pers pairs
    EDGE_ID g_H1_pers_pairs_max_len;
    EDGE_ID g_H1_pers_pairs_len;
    PAR* g_H1_pers_pairs;
 
    
    ////////////////////////////////////
    // cohomology H2 structures
    ////////////////////////////////////
    
    simplex* g_V_sparse_H2;

    // WORKSPACE STRUCTURES
    int g_cohom_ws_size;
    coboundary_H2** g_V_ws_H2; 
    coboundary_H2_ws* g_V_ws_H2_info;

    
    // NEW PIVOTS OF H2 COHOMOLOGY
    H2_cohom_pivots** g_H2_cohom_pivots;
    EDGE_ID* g_H2_cohom_pivots_len;
    EDGE_ID* g_H2_cohom_pivots_max_len;


    // Pers pairs
    EDGE_ID g_H2_pers_pairs_max_len;
    EDGE_ID g_H2_pers_pairs_len;
    PAR* g_H2_pers_pairs;

    ////////////////////////////////////
    // Timers
    ////////////////////////////////////
    double g_timer_H2_low;
    double g_timer_H2_next;
    double g_timer_H2_greater;

    struct timespec g_start_wall_clock;
    struct timespec g_finish_wall_clock;

    double g_timer_process;
    double g_timer_sort_edges;
    double g_timer_neigh;
    double g_timer_H0;
    double g_timer_coH1;
    double g_timer_coH2;

    double g_timer_coH2_serial;
    double g_timer_coH2_parallel;

    // Temporary
    int g_p_flag;
    EDGE_ID g_counter;


    ////////////////////////////////////
    // homology H1 structures
    ////////////////////////////////////

    EDGE_ID** g_workspace_H1;
    boundary_H1_ws* g_workspace_H1_info;

    ////////////////////////////////////
    // For cycles
    ////////////////////////////////////
    //int g_extract_cycles;
    //float g_cycle_birth_limit;
    //float g_cycle_usage_thresh;
    //float g_cycle_depth_thresh;


    int g_new_debug;
    
} filtration;



int simplex1_check(VERT_ID, VERT_ID, PAR, PAR);

int simplex2_check(VERT_ID, VERT_ID, VERT_ID);

int simplex3_check(VERT_ID, VERT_ID, VERT_ID, VERT_ID);


// MERGE SORT ALGORITHM
void mergeSort(PAR* , EDGE_ID** , EDGE_ID , EDGE_ID ) ;
void merge(PAR* , EDGE_ID** , EDGE_ID , EDGE_ID , EDGE_ID ) ;

//////////////////////////////////////////////////////////////
//       NEIGHBOR CREATION AND SEARCH ALGORITHMS
//////////////////////////////////////////////////////////////

void update_neighbors_new(filtration* , VERT_ID , VERT_ID , EDGE_ID);

VERT_ID search_Neighbors(filtration* , VERT_ID , VERT_ID , VERT_ID , VERT_ID);
VERT_ID search_Neighbors_e(filtration* , VERT_ID , EDGE_ID , VERT_ID , VERT_ID, EDGE_ID);

VERT_ID bin_search_min_geq_Ne(Neighbors* , VERT_ID, VERT_ID, VERT_ID, EDGE_ID);
VERT_ID bin_search_min_geq_N(Neighbors* , VERT_ID, VERT_ID, VERT_ID, EDGE_ID);

//////////////////////////////////////////////////////////////



// H0 HOMOLOGY FUNCTIONS

// Parallel homology reduction H0
//main reduction
void reduce_ws_H0(filtration* );
//reduction with complex
void* reduce_with_complex_H0(void* );
//reduction with self
void reduce_with_self_H0(filtration* );
//Update R
void update_R_H0(filtration* , int );


void allocate_jobs(filtration*, int);




// H1 cohomology functions

void add_H1_pivot(filtration* , simplex* , int , EDGE_ID );

void update_V_coH1 (filtration* );

void* parallel_coH1_next(void* );

void find_H1_cohom_next (filtration* , coboundary_H1* );
void find_H1_cohom_low(filtration* , coboundary_H1* );
void find_H1_cohom_greater(filtration* , coboundary_H1* , simplex* );

void* parallel_coH2_next(void* );

void find_H2_cohom_next (filtration* , coboundary_H2* );
void find_H2_cohom_low(filtration* , coboundary_H2* );
void find_H2_cohom_greater(filtration* , coboundary_H2* , simplex* );

int H2_case1 (filtration*, coboundary_H2*);
void H2_case2 (filtration*, coboundary_H2*);


EDGE_ID search_H1_cohom_pivots(H1_cohom_pivots* , EDGE_ID , EDGE_ID , EDGE_ID , EDGE_ID ); 
EDGE_ID search_H2_cohom_pivots(H2_cohom_pivots* , EDGE_ID , EDGE_ID , EDGE_ID , EDGE_ID ); 


void H2_reduce (filtration*, coboundary_H2*, EDGE_ID, int);
void add_H2_pivot(filtration* , simplex* , int , simplex );
void update_V_coH2(filtration* , int );

void reduce_ws_coH2(filtration* );
void* reduce_with_complex_coH2(void* );
void reduce_with_self_coH2(filtration* );



// H1 HOMOLOGY FUNCTIONS
//main reduction
void reduce_ws_H1(filtration* );
//reduction with complex
void* reduce_with_complex_H1(void* );
//reduction with self
void reduce_with_self_H1(filtration* );
//Update R
void update_R_H1(filtration* , int );

// DEALLOCATE
void deallocator(filtration*);



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

static PyObject *compute_PH(PyObject *self2, PyObject *args){

    printf("\nStarting...");
  
     struct timespec start_wall_clock, finish_wall_clock;
     clock_gettime(CLOCK_MONOTONIC, &start_wall_clock);


    // Filetype = 0 : Distance matrix
    // Filetype = 1 : Locations
    // Filetype = 2 : Edge list with edge length

     //printf("%ld", (long)getpid());

     filtration* self;
     self = (filtration*)malloc(sizeof(filtration));

     self->g_new_debug = 0;

     //////////////////////////////////////////////////////
     // Set testing timers and test counters
     //////////////////////////////////////////////////////
     self->g_counter = 0;

     self->g_timer_H2_low = 0;
     self->g_timer_H2_next = 0;
     self->g_timer_H2_greater = 0;

     self->g_timer_coH2_serial = 0;
     self->g_timer_coH2_parallel = 0;
     //////////////////////////////////////////////////////
     //////////////////////////////////////////////////////


      if (!PyArg_ParseTuple(args, "sdiisi"\
                                     , &(self->g_source), &(self->g_thresh)\
                                     , &(self->g_filetype), &(self->g_cpu_count)\
                                     , &(self->g_target), &(self->g_dim_lim)\
                                     )){
          printf("\nERROR in parse args");
          return NULL;

      }



     int file_len = strlen(self->g_target) + 100;
     char* duplicate = (char*)malloc(file_len*sizeof(char));

     //strcpy(duplicate, self->g_source);
     //strcat(duplicate, ".csv");

     self->filename = strdup(self->g_source);



     strcpy(duplicate, self->g_target);
     strcat(duplicate, "H0_pers_data.txt");

     self->g_H0_pers_file = strdup(duplicate);



     if (self->g_dim_lim > 0){

          strcpy(duplicate, self->g_target);
          strcat(duplicate, "H1_pers_data.txt");

          self->g_H1_pers_file = strdup(duplicate);

          if (self->g_dim_lim > 1){

               strcpy(duplicate, self->g_target);
               strcat(duplicate, "H2_pers_data.txt");

               self->g_H2_pers_file = strdup(duplicate);

          }

     }




     free(duplicate);

     //self->g_extract_cycles = atoi(argv[6]);

     //self->g_cycle_birth_limit = atof(argv[7]);

     //self->g_cycle_usage_thresh = 2;
     //self->g_cycle_depth_thresh = 5;
   
     
     omp_set_num_threads(self->g_cpu_count);


     FILE *fp = fopen(self->filename, "r");  

     if (fp == NULL){
          perror("Unable to open file!");
          exit(1);
     }

     char* line = NULL;
     size_t len = 0;
     char* dist;
     PAR dist_d;
     char* end;

     getline(&line, &len, fp);
     dist = strtok(line, " ,");

     fclose(fp);

     fp = fopen(self->filename, "r");  

     int prealloc = 100000;

     VERT_ID row = 0;
     VERT_ID col = 0;

     self->g_edges_list = (EDGE_ID**)malloc(prealloc*sizeof(EDGE_ID*));
     self->g_edge_parameter = (PAR*)malloc(prealloc*sizeof(PAR));

     self->g_n_valid_edges = 0;

     if (self->g_filetype == 0){

       // this is a distance matrix
        while(getline(&line, &len, fp) != -1) {

            col = 0;
            
            dist = strtok(line, " ,");
            while(dist != NULL){
              dist_d = strtod(dist, &end);
              //if (dist_d != 0) dist_d = 1/dist_d;
              dist = strtok(NULL, ",");
              if (col > row){
                   
                   if (simplex1_check(row, col, dist_d, self->g_thresh)){

                         self->g_edges_list[self->g_n_valid_edges] = (EDGE_ID*)malloc(2*sizeof(EDGE_ID));

                         // Note that g_edges_list is sorted, row < col
                         self->g_edges_list[self->g_n_valid_edges][0] = row;
                         self->g_edges_list[self->g_n_valid_edges][1] = col;

		                     // parameter
                         self->g_edge_parameter[self->g_n_valid_edges] = dist_d;

                         self->g_n_valid_edges += 1;
                         if (self->g_n_valid_edges == prealloc){
                               
                               prealloc += 100000;
                               self->g_edges_list = (EDGE_ID**)realloc(self->g_edges_list, prealloc*sizeof(EDGE_ID*));
                               self->g_edge_parameter = (PAR*)realloc(self->g_edge_parameter, prealloc*sizeof(PAR));

                         }
                   }

              }
              col += 1;
            }
            row += 1;
        }

	self->g_n_vert = row;
	

     }
     else if (self->g_filetype == 1){

       self->g_thresh = self->g_thresh * self->g_thresh;

       //Locations information
       
       printf("extracting edges");
       int dim_space = 0;

       while(getline(&line, &len, fp) != -1) {
          dist = strtok(line, " ,");
          while(dist != NULL){
            dist_d = strtod(dist, &end);
            dist = strtok(NULL, ",");
            dim_space++;
          }
          break;
       }

       rewind(fp);

       PAR** locations;
       locations = (PAR**)malloc(sizeof(PAR*));

        while(getline(&line, &len, fp) != -1) {

            col = 0;
            
            locations = (PAR**)realloc(locations, (row+1)*sizeof(PAR*));
            locations[row] = (PAR*)malloc(dim_space*sizeof(PAR));

            dist = strtok(line, " ,");
            while(dist != NULL){

              dist_d = strtod(dist, &end);
              //if (dist_d != 0) dist_d = 1/dist_d;
              dist = strtok(NULL, ",");

              locations[row][col++] = dist_d; 

            }

            row++;

        }

        PAR diff;

        printf("\n");
        for (int i = 0; i < row-1; i++){

              //printf("Done %f percent\r", (float)i/(float)(row-1));
              
              for (int j = i+1; j < row; j++){
                    
                  dist_d = 0;
                  for (int k = 0; k < dim_space; k++){
                          
                        diff = locations[i][k] - locations[j][k];
                        dist_d += diff*diff;                        

                  }

                  //dist_d = sqrt(dist_d);

                  if (simplex1_check(i, j, dist_d, self->g_thresh)){

                        self->g_edges_list[self->g_n_valid_edges] = (EDGE_ID*)malloc(2*sizeof(EDGE_ID));

                        // Note that g_edges_list is sorted, row < col
                        self->g_edges_list[self->g_n_valid_edges][0] = i;
                        self->g_edges_list[self->g_n_valid_edges][1] = j;

		                    // parameter
                        self->g_edge_parameter[self->g_n_valid_edges] = dist_d;

                        self->g_n_valid_edges += 1;
                        if (self->g_n_valid_edges == prealloc){
                              
                              prealloc += 100000;
                              self->g_edges_list = (EDGE_ID**)realloc(self->g_edges_list, prealloc*sizeof(EDGE_ID*));
                              self->g_edge_parameter = (PAR*)realloc(self->g_edge_parameter, prealloc*sizeof(PAR));

                        }
                  }
                    
              }
              
        }


        for (int i = 0; i < row; i++) free(locations[i]);
        free(locations);

	      self->g_n_vert = row;
        printf("\nExtracted edges\n");

     }
     else if (self->g_filetype == 2){
          
        //List of edges with lengths
        //Format is v1, v2, length
        printf("extracting edges");

        int n_edges = 0;

        row = 0;

        //while(getline(&line, &len, fp) != -1)
        //      n_edges++;

        //printf("\nnumber of edges %d", n_edges);
        //rewind(fp);

        int vv1, vv2;
	      int max_v = 0;
        while(getline(&line, &len, fp) != -1) {

            col = 0;
            
            self->g_edges_list[self->g_n_valid_edges] = (EDGE_ID*)malloc(2*sizeof(EDGE_ID));
            dist = strtok(line, " ,");
            while(dist != NULL){

                if (col == 0){

                    vv1 = atoi(dist);
		    if (vv1 > max_v)
			max_v = vv1;

                }
                else if (col == 1){

                    vv2 = atoi(dist);
		    if (vv2 > max_v)
			max_v = vv2;
                    
                }
                else if (col == 2){


                    PAR edge_length = strtod(dist, &end);
                    if (simplex1_check(vv1, vv2, edge_length, self->g_thresh)){
                        self->g_edge_parameter[self->g_n_valid_edges] = edge_length;

                        if (vv1 < vv2){

                            self->g_edges_list[self->g_n_valid_edges][0] = vv1;
                            self->g_edges_list[self->g_n_valid_edges][1] = vv2;

                        }
                        else {

                            self->g_edges_list[self->g_n_valid_edges][0] = vv2;
                            self->g_edges_list[self->g_n_valid_edges][1] = vv1;

                        }

            	          self->g_n_valid_edges++;

            	          if (self->g_n_valid_edges == prealloc){
            	                
            	                prealloc += 100000;
            	                self->g_edges_list = (EDGE_ID**)realloc(self->g_edges_list, prealloc*sizeof(EDGE_ID*));
            	                self->g_edge_parameter = (PAR*)realloc(self->g_edge_parameter, prealloc*sizeof(PAR));

            	          }

                    }

                }

                dist = strtok(NULL, ",");
                col++;

            }

            row++;

        }

     	self->g_n_vert = max_v+1;
        printf("\nExtracted edges\n");

     }



     self->g_edges_list = (EDGE_ID**)realloc(self->g_edges_list, self->g_n_valid_edges*sizeof(EDGE_ID*));
     self->g_edge_parameter = (PAR*)realloc(self->g_edge_parameter, self->g_n_valid_edges*sizeof(PAR));
 
     fclose(fp);
     free(line);    

     clock_gettime(CLOCK_MONOTONIC, &finish_wall_clock);
     self->g_timer_process = (finish_wall_clock.tv_sec - start_wall_clock.tv_sec);
     self->g_timer_process += (finish_wall_clock.tv_nsec - start_wall_clock.tv_nsec) / 1000000000.0;


     clock_gettime(CLOCK_MONOTONIC, &start_wall_clock);

     mergeSort(self->g_edge_parameter, self->g_edges_list, 0, self->g_n_valid_edges-1);

     clock_gettime(CLOCK_MONOTONIC, &finish_wall_clock);
     self->g_timer_sort_edges = (finish_wall_clock.tv_sec - start_wall_clock.tv_sec);
     self->g_timer_sort_edges += (finish_wall_clock.tv_nsec - start_wall_clock.tv_nsec) / 1000000000.0;

     printf("\nSorted %d edges\n", self->g_n_valid_edges);
     //exit(0);



///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
//                           STEP 1
//                  Generate Neighbor matrices
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

     //printf("\nPress key to start...");
     //getchar();
     
     clock_gettime(CLOCK_MONOTONIC, &start_wall_clock);
  
     // Initiate the Neighbor data structures
     self->g_Neighbors_e = (Neighbors**)malloc(self->g_n_vert*sizeof(Neighbors*));
     self->g_Neighbors = (Neighbors**)malloc(self->g_n_vert*sizeof(Neighbors*));
     self->g_Neigh_len = (VERT_ID*)calloc(self->g_n_vert, sizeof(VERT_ID));

     self->g_pivots_H0 = (EDGE_ID*)calloc(self->g_n_vert, sizeof(EDGE_ID));


     EDGE_ID* n_neigh = (EDGE_ID*)calloc(self->g_n_vert, sizeof(EDGE_ID));

     VERT_ID vv;

     for (EDGE_ID i = 0; i < self->g_n_valid_edges; i++){

          n_neigh[self->g_edges_list[i][0]]++;
          n_neigh[self->g_edges_list[i][1]]++;
          
          
     }

     for (VERT_ID i = 0; i < self->g_n_vert; i++){
          
          self->g_Neighbors_e[i] = (Neighbors*)malloc(n_neigh[i]*sizeof(Neighbors));
          self->g_Neighbors[i] = (Neighbors*)malloc(n_neigh[i]*sizeof(Neighbors));

     }

     free(n_neigh);
     


     printf("\nCreating neighbors...");
    
     //double time_create_neigh = omp_get_wtime();
     self->g_max_neighbors = 0;

     for (EDGE_ID i = 0; i < self->g_n_valid_edges; i++){
        
          VERT_ID v1 = self->g_edges_list[i][0];
          VERT_ID v2 = self->g_edges_list[i][1];

          len = self->g_Neigh_len[v1];

          self->g_Neighbors[v1][len].order = i;
          self->g_Neighbors[v1][len].neighbor = v2;

          self->g_Neighbors_e[v1][len].order = i;
          self->g_Neighbors_e[v1][len].neighbor = v2;

          self->g_Neigh_len[v1]++;

          len = self->g_Neigh_len[v2];

          self->g_Neighbors[v2][len].order = i;
          self->g_Neighbors[v2][len].neighbor = v1;

          self->g_Neighbors_e[v2][len].order = i;
          self->g_Neighbors_e[v2][len].neighbor = v1;

          self->g_Neigh_len[v2]++;


     }

     //printf("Time taken %f", omp_get_wtime() - time_create_neigh);

     printf("\nSorting neighbors...");
     //double time_sort_neigh = omp_get_wtime();

     #pragma omp parallel for schedule(static) shared(self)
     for (EDGE_ID i = 0; i < self->g_n_vert; i++){

          //self->g_Neighbors[i] = (Neighbors*)realloc(self->g_Neighbors[i],\
          //                                      self->g_Neigh_len[i]*sizeof(Neighbors));

          //self->g_Neighbors_e[i] = (Neighbors*)realloc(self->g_Neighbors_e[i],\
          //                                      self->g_Neigh_len[i]*sizeof(Neighbors));

          if (self->g_Neigh_len[i] > 1){

              sorter_tim_sort(self->g_Neighbors[i], self->g_Neigh_len[i]);
              sorter2_tim_sort(self->g_Neighbors_e[i], self->g_Neigh_len[i]);

          }
          
     }


     clock_gettime(CLOCK_MONOTONIC, &finish_wall_clock);
     self->g_timer_neigh = (finish_wall_clock.tv_sec - start_wall_clock.tv_sec);
     self->g_timer_neigh += (finish_wall_clock.tv_nsec - start_wall_clock.tv_nsec) / 1000000000.0;

     //printf("Time taken %f", omp_get_wtime() - time_sort_neigh);


///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
//            STEP H0.1: Reduce the edges using column method
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

     printf("\n\n---------------");
     printf("\nComputing H0...");
     printf("\n---------------\n");

     clock_gettime(CLOCK_MONOTONIC, &start_wall_clock);


     // R Sparse
     self->g_R_sparse_max_H0 = 1000;
     self->g_R_sparse_H0 = (EDGE_ID*)malloc(self->g_R_sparse_max_H0*sizeof(EDGE_ID));
     self->g_R_sparse_ptr_H0 = 0;


     // R sparse col mapping
     self->g_R_col_indices_max_H0 = 100;
     self->g_R_col_indices_H0 = (EDGE_ID*)malloc(self->g_R_col_indices_max_H0*sizeof(EDGE_ID));
     self->g_R_col_indices_ptr_H0 = 1;

     
     // Note which edges have pivots in H0
     self->g_edges_with_pivots_H0 = \
                            (EDGE_ID*)calloc(self->g_n_valid_edges, sizeof(EDGE_ID));


     /////////////
     // WORKSPACE
     /////////////
     self->g_ws_pre_alloc = 100;
     self->g_workspace_size = 1000;

     // H0 workspace structures
     self->g_R_ws_H0 = \
                     (EDGE_ID**)malloc(self->g_workspace_size*sizeof(EDGE_ID*));

     // H0 workspace info
     self->g_R_ws_H0_info = (boundary_H0_ws*)malloc(self->g_workspace_size*sizeof(boundary_H0_ws));


     // Initialize ws counter
     self->g_ws_counter = 0;

     for (int ws_counter = 0; ws_counter < self->g_workspace_size; ws_counter++){

         self->g_R_ws_H0_info[ws_counter].max_len = self->g_ws_pre_alloc;

         self->g_R_ws_H0[ws_counter] = (EDGE_ID*)malloc(2*self->g_R_ws_H0_info[ws_counter].max_len*sizeof(EDGE_ID));
         
     }


     ////////////////////////////////
     // Allocate jobs for parallel H0
     ////////////////////////////////
     
     self->g_jobs = (int*)malloc((self->g_cpu_count + 1)*sizeof(int));

     allocate_jobs(self, self->g_workspace_size);

     int rtn;

     self->g_threads = (pthread_t *)malloc(self->g_cpu_count*sizeof(pthread_t));

     if ((rtn = pthread_mutex_init(&(self->g_thread_lock), NULL)) !=0)
        fprintf(stderr, "pthread_mutex_init %s", strerror(rtn)), exit(-1);

     if ((rtn = pthread_cond_init(&(self->g_start_boss), NULL)) !=0)
        fprintf(stderr, "pthread_cond_init %s", strerror(rtn)), exit(-1);

     if ((rtn = pthread_cond_init(&(self->g_start_workers), NULL)) !=0)
        fprintf(stderr, "pthread_cond_init %s", strerror(rtn)), exit(-1);


     // Initialize thread creation
     self->g_thread_id = 0;
     self->g_sleeping_threads = 0;
     self->g_delete_threads = 0;

     for (int i = 0; i < self->g_cpu_count; i++){

        if ((rtn = pthread_create( \
                                &(self->g_threads[i]) \
                                , NULL \
                                , reduce_with_complex_H0 \
                                , (void*)self)!= 0))
          fprintf(stderr, "pthread_create %d", rtn), exit(-1);
      
     }

     // Wait for threads to be initialized
     pthread_mutex_lock(&(self->g_thread_lock));

     while(self->g_sleeping_threads != self->g_cpu_count){
        
          pthread_cond_wait(&(self->g_start_boss) \
                          , &(self->g_thread_lock));

     }

     ////////////////////////////////

     ////////////////////////////////
     // Main H0 Homology loop
     ////////////////////////////////
     
     for (EDGE_ID i = 0; i < self->g_n_valid_edges; i++){

               //printf("Percentage %f\r", (float)i/(float)self->g_n_valid_edges);
               //

               //if (i%10000 == 0){
               //    printf("\rProcessing edge %d", i);
               //}

               ////////////////////
               // Append to workspace_H0
               ////////////////////
                //self->g_ws_simplices_H0[self->g_ws_counter] = i;
                
                boundary_H0_ws* this_ws = self->g_R_ws_H0_info + self->g_ws_counter;

                // coboundary
                this_ws->cob = i;

                // Initially, the original is at 0
                this_ws->original = 0;

                // Length
                this_ws->len = 2;

                // Non empty
                this_ws->flag_non_empty = 1;
                
                // Recall: edge_list has v_max at 1 and v_min at 0
                self->g_R_ws_H0[self->g_ws_counter][0] = self->g_edges_list[i][0];
                self->g_R_ws_H0[self->g_ws_counter][1] = self->g_edges_list[i][1];
                
                // Pivot
                this_ws->pivot = self->g_edges_list[i][1];
                     
                self->g_ws_counter += 1;

                if (self->g_ws_counter == self->g_workspace_size){

                     reduce_ws_H0(self);
                     
                
                }



     }


     // Reduction of final batch
     while (self->g_ws_counter){

          // Allocate the last batch of size g_ws_counter
          allocate_jobs(self, self->g_ws_counter);

          reduce_ws_H0(self);

     }


     self->g_R_sparse_H0 = (EDGE_ID*)realloc( \
                                    self->g_R_sparse_H0\
                                  , (self->g_R_sparse_ptr_H0+1)*sizeof(EDGE_ID));


     self->g_R_col_indices_H0 = (EDGE_ID*)realloc( \
                                   self->g_R_col_indices_H0 \
                                  , (self->g_R_col_indices_ptr_H0+1)*sizeof(EDGE_ID));
                         
     
     /////////////////////////
     // Cancel the threads
     /////////////////////////

     self->g_delete_threads = 1;

     pthread_cond_broadcast(&(self->g_start_workers));

     pthread_mutex_unlock(&(self->g_thread_lock));

     for (int i = 0; i < self->g_cpu_count; i++){

        pthread_join(self->g_threads[i], NULL);
      
     }

     free(self->g_threads);
     free(self->g_jobs);

     //////////////////////////////////////////////////
     // Clear H0 parallel workspace
     //////////////////////////////////////////////////

     for (int ws_counter = 0; ws_counter < self->g_workspace_size; ws_counter++){
          
          free(self->g_R_ws_H0[ws_counter]);

     }

     free(self->g_R_ws_H0);
     free(self->g_R_ws_H0_info);
    
     /////////////////////////
     // Write H0 deaths to file
     /////////////////////////

     //// BINARY FILE
     //FILE* fp2 = fopen("H0_pers_pairs.bin", "wb");
     //fwrite(self->g_H0_pers_pairs, sizeof(PAR),self->g_H0_pers_pairs_len, fp2);
     //fclose(fp2);

     // TEXT FILE
     FILE* fp2 = fopen(self->g_H0_pers_file, "w");

     if (self->g_filetype == 1){

            for (EDGE_ID it = 0; it < self->g_n_valid_edges; it++){
                  
                  if (self->g_edges_with_pivots_H0[it]){
                     fprintf(fp2, "%.12lf,", sqrt(self->g_edge_parameter[it]));
                  }
                  
            }

     }
     else{

              for (EDGE_ID it = 0; it < self->g_n_valid_edges; it++){
                    
                    if (self->g_edges_with_pivots_H0[it]){
                       fprintf(fp2, "%.12lf,", self->g_edge_parameter[it]);
                    }
                    
              }
     }

     fclose(fp2);



     clock_gettime(CLOCK_MONOTONIC, &finish_wall_clock);
     self->g_timer_H0 = (finish_wall_clock.tv_sec - start_wall_clock.tv_sec);
     self->g_timer_H0 += (finish_wall_clock.tv_nsec - start_wall_clock.tv_nsec) / 1000000000.0;
     


///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
//            STEP coH1.1: Find cohomology now for the edges
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

     printf("\n\n-----------------");
     printf("\nComputing coH1...");
     printf("\n-----------------\n");
     //double time_compute_coH1 = omp_get_wtime();
     clock_gettime(CLOCK_MONOTONIC, &start_wall_clock);

     // V sparse 
     EDGE_ID pre_alloc = 1000;

     self->g_V_sparse_max = pre_alloc;
     self->g_V_sparse_H1 = (EDGE_ID*)malloc(self->g_V_sparse_max*sizeof(EDGE_ID));
     self->g_V_sparse_ptr = 1;
     self->g_V_sparse_beg_ptr = 1;
     self->g_V_sparse_end_ptr = 1;

     self->g_V_col_indices_max = pre_alloc;
     self->g_V_col_indices = (EDGE_ID*)malloc(self->g_V_col_indices_max*sizeof(EDGE_ID));
     self->g_V_col_indices_ptr = 1;


     self->g_V_temp_len = 0;
     self->g_V_temp_max_len = pre_alloc;
     self->g_V_temp = (coboundary_H1*)malloc(self->g_V_temp_max_len*sizeof(coboundary_H1));

     self->g_this_pivot_thread = (simplex*)malloc(self->g_cpu_count*sizeof(simplex));
     self->g_coeff_thread = (int*)malloc(self->g_cpu_count*sizeof(int));
     self->g_empty_thread = (int*)malloc(self->g_cpu_count*sizeof(int));


     // H1 pivots 

     self->g_H1_cohom_pivots = (H1_cohom_pivots**)malloc(self->g_n_valid_edges*sizeof(H1_cohom_pivots*));

     self->g_H1_cohom_pivots_len = (EDGE_ID*)calloc(self->g_n_valid_edges, sizeof(EDGE_ID));
     self->g_H1_cohom_pivots_max_len = (EDGE_ID*)malloc(self->g_n_valid_edges*sizeof(EDGE_ID));

     for (EDGE_ID mm = 0; mm < self->g_n_valid_edges; mm++){
          
        self->g_H1_cohom_pivots_max_len[mm] = 2;
        self->g_H1_cohom_pivots[mm] = \
                               (H1_cohom_pivots*)malloc(self->g_H1_cohom_pivots_max_len[mm]*sizeof(H1_cohom_pivots));
          
     }

     // H1 Pers pairs
     self->g_H1_pers_pairs_max_len = 1000;
     self->g_H1_pers_pairs_len = 0;
     self->g_H1_pers_pairs = (PAR*)malloc(self->g_H1_pers_pairs_max_len*sizeof(PAR));
     


     int new_debug = 0;

     self->g_coH1_all_lows = (coboundary_H1*)malloc(self->g_n_valid_edges*sizeof(coboundary_H1));



     ////////////////////////////////
     // parallel coH1 next
     ////////////////////////////////
     
     self->g_jobs = (int*)malloc((self->g_cpu_count + 1)*sizeof(int));
     self->g_threads = (pthread_t *)malloc(self->g_cpu_count*sizeof(pthread_t));

     if ((rtn = pthread_mutex_init(&(self->g_thread_lock), NULL)) !=0)
        fprintf(stderr, "pthread_mutex_init %s", strerror(rtn)), exit(-1);

     if ((rtn = pthread_cond_init(&(self->g_start_boss), NULL)) !=0)
        fprintf(stderr, "pthread_cond_init %s", strerror(rtn)), exit(-1);

     if ((rtn = pthread_cond_init(&(self->g_start_workers), NULL)) !=0)
        fprintf(stderr, "pthread_cond_init %s", strerror(rtn)), exit(-1);


     // Initialize thread creation
     self->g_thread_id = 0;
     self->g_sleeping_threads = 0;
     self->g_delete_threads = 0;

     for (int i = 0; i < self->g_cpu_count; i++){

        if ((rtn = pthread_create( \
                                &(self->g_threads[i]) \
                                , NULL \
                                , parallel_coH1_next \
                                , (void*)self)!= 0))
          fprintf(stderr, "pthread_create %d", rtn), exit(-1);
      
     }

     // Wait for threads to be initialized
     pthread_mutex_lock(&(self->g_thread_lock));

     while(self->g_sleeping_threads != self->g_cpu_count){
        
          pthread_cond_wait(&(self->g_start_boss) \
                          , &(self->g_thread_lock));

     }

     ////////////////////////////////


     #pragma omp parallel for schedule(static) shared(self)
     for (EDGE_ID mm = 0; mm < self->g_n_valid_edges; mm++) {

          self->g_coH1_all_lows[mm].o_ab = mm; 
          find_H1_cohom_low(self, &(self->g_coH1_all_lows[mm]));
          // Need to find a_ptr and b_ptr if first low.key1 > e
          if (self->g_coH1_all_lows[mm].low.key1 > self->g_coH1_all_lows[mm].o_ab){

              VERT_ID a = self->g_edges_list[self->g_coH1_all_lows[mm].o_ab][0];
              VERT_ID b = self->g_edges_list[self->g_coH1_all_lows[mm].o_ab][1];

              self->g_coH1_all_lows[mm].a_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[a], 0, self->g_Neigh_len[a]-1\
                                                      , self->g_coH1_all_lows[mm].low.key1, self->g_Neigh_len[a]);
              
              self->g_coH1_all_lows[mm].b_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[b], 0, self->g_Neigh_len[b]-1\
                                                      , self->g_coH1_all_lows[mm].low.key1, self->g_Neigh_len[b]);
          }

     }



     EDGE_ID i = self->g_n_valid_edges;

     ///////////////////////////////////////////////////
     // MAIN coH1 loop 
     ///////////////////////////////////////////////////


     while(i){
          
          i--;

          //if (i%10000 == 0){
          //  printf("\nProcessing edge %d", i);
          //}

          ///////////////////////////////////////////////////
          // CLEARING ALGORITHM
          // Does this edge have a pivot?
          ///////////////////////////////////////////////////
          

          if (self->g_edges_with_pivots_H0[i]){
            //This edge has a pivot in H0. So, skip it. Continue;
            //skip++;
            
            //printf("\nskipping %d", edge);
            continue;
          }
          ///////////////////////////////////////////////////
          ///////////////////////////////////////////////////
          //


          //V_temp[0].o_ab = i; 


          //VERT_ID a = self->g_edges_list[i][0];
          //VERT_ID b = self->g_edges_list[i][1];

          // Find the first low

          //find_H1_cohom_low(self, &(V_temp[0]));

          self->g_V_temp[0] = self->g_coH1_all_lows[i];
          self->g_V_temp_len = 1;

          //if ((V_temp[0].low.key1 == 2732544)\
          //    &&(V_temp[0].low.key2 == 1329)){

          //      new_debug=1;

          //}
          //else{
          //      new_debug = 0;
          //}

          //if (i == 3341){
          //  self->g_new_debug = 0;
          //}
          //else{
          //  self->g_new_debug = 0;
          //}


          if (self->g_V_temp[0].low.key1 == self->g_n_valid_edges){
            // This edge has no coboundary
            //if (self->g_new_debug){
            //  printf("\nno cob, skipping");
            //}
            continue;
          }


          // This is a trivial pair
          if (self->g_V_temp[0].low.key1 == i){

            //if (self->g_new_debug){
            //  printf("\nTrivial , adding");
            //}

            self->g_edges_with_pivots_H0[i]  = 10;

            //Then add this to pivots but NOT to V_reduced
            // red_col = g_n_valid_edges
            
            ///////////////////////////////
            // NNNEEEEEEEWWWWWWWWWWWWWWWWWWWWWWWWWWW
            // JUST SKIP THIS, COMMENT BELOW LATERRRRRR
            ///////////////////////////////
            //add_H1_pivot(self, &(self->g_V_temp[0].low), 0, i);


            continue;

          }

          EDGE_ID reduce_w_bndry;
          int V_col_idx, idx;
          self->g_this_pivot = self->g_V_temp[0].low;

          //simplex lowlow = self->g_coH1_all_lows[V_temp[0].low.key1].low;

          if ((self->g_coH1_all_lows[self->g_V_temp[0].low.key1].low.key1 == self->g_V_temp[0].low.key1)\
              && (self->g_coH1_all_lows[self->g_V_temp[0].low.key1].low.key2 == self->g_V_temp[0].low.key2)){

                //if (self->g_new_debug){
                //    printf("\nReducing with trivial %d", reduce_w_bndry);
                //}

                reduce_w_bndry = self->g_V_temp[0].low.key1;
                V_col_idx = 0;


          }
          else{




                
                // If this low is not a pivot
                if (!self->g_H1_cohom_pivots_len[self->g_this_pivot.key1]){

                  //if (self->g_new_debug){
                  //  printf("\nnot a prexisting pivot, adding");
                  //}
                  //Then add this to pivots but NOT to V_reduced
                  // red_col = g_n_valid_edges
                  //add_H1_pivot(self, &(self->g_this_pivot), 0, i);


                  update_V_coH1(self);

                  continue;

                }

                idx = search_H1_cohom_pivots(self->g_H1_cohom_pivots[self->g_this_pivot.key1]\
                                          , 0 \
                                          , self->g_H1_cohom_pivots_len[self->g_this_pivot.key1] - 1\
                                          , self->g_this_pivot.key2 \
                                          , self->g_n_valid_edges);

                // If this low is not a pivot
                if (idx == self->g_n_valid_edges){

                      //if (self->g_new_debug){
                      //  printf("\nnot a prexisting pivot, adding");
                      //}

                      //Then add this to pivots but NOT to V_reduced
                      // red_col = g_n_valid_edges, no reductions were done
                      //add_H1_pivot(self, &(self->g_this_pivot), 0, i);

                      update_V_coH1(self);

                      continue;
                        

                }
                
                // Pivot was found, so there will be a reduction now
                //printf("\nFIrst ever to reduce pivot (%d, %d) for %d", self->g_this_pivot.key1, self->g_this_pivot.key2, i);
                //getchar();


                reduce_w_bndry = self->g_H1_cohom_pivots[self->g_this_pivot.key1][idx].bndry;

                //if (self->g_new_debug){
                //    printf("\nReducing with pivot already in complex %d", reduce_w_bndry);
                //}
                V_col_idx = self->g_H1_cohom_pivots[self->g_this_pivot.key1][idx].col_idx;


          }



          
          //if (self->g_new_debug){
          //  printf("\npivot exists, reducing");
          //}


          int empty = 0;



          int main_reduction_counter = 0;
          while (1){

                //if (self->g_new_debug){
                //  printf("\n%d: Reducing with %d, V_col_idx is %d", main_reduction_counter, reduce_w_bndry, V_col_idx);
                //  main_reduction_counter++;
                //  getchar();
                //}

                EDGE_ID check_len = self->g_V_temp_len + 1;

                if (V_col_idx){

                      check_len += self->g_V_col_indices[V_col_idx+1] - self->g_V_col_indices[V_col_idx];

                }

                if (check_len > self->g_V_temp_max_len){
                    self->g_V_temp_max_len = check_len + 1000;
                    self->g_V_temp = (coboundary_H1*)realloc(self->g_V_temp, self->g_V_temp_max_len*sizeof(coboundary_H1));

                }


                //V_temp[V_temp_len].o_ab = self->g_H1_cohom_pivots[self->g_this_pivot.key1][idx].bndry;
                self->g_V_temp[self->g_V_temp_len].o_ab = reduce_w_bndry;

                //printf("\nAdding bndry %d", V_temp[V_temp_len].o_ab);
                //getchar();
                
                find_H1_cohom_greater(self, &(self->g_V_temp[self->g_V_temp_len]), &(self->g_this_pivot));
                //printf("\nFOUND (%d, %d) geq (%d, %d)", V_temp[V_temp_len].low.key1, V_temp[V_temp_len].low.key2\
                //                                      , self->g_this_pivot.key1, self->g_this_pivot.key2);
                //getchar();

                self->g_V_temp_len++;



                // IF the V was recorded, add the bndries
                if (V_col_idx){

                    //if (self->g_new_debug){
                    //  printf("\nAdding col %d from V:", V_col_idx);
                    //  
                    //  for (EDGE_ID mm = self->g_V_col_indices[V_col_idx]; mm < self->g_V_col_indices[V_col_idx+1]; mm++){
                    //      
                    //        printf("\n%d", self->g_V_sparse_H1[mm]);
                    //  }

                    //  getchar();
                    //}

                    // We have to cycle through the col in V and add all the other boundary columns for reduction
                    //

                    EDGE_ID start = self->g_V_col_indices[V_col_idx];
                    //EDGE_ID V_len = V_col_indices[V_col_idx+1] - start;
                    EDGE_ID end = self->g_V_col_indices[V_col_idx+1];

                    //if (end - start < 5000){

                        for (EDGE_ID mm = start; mm < end; mm++){
                              
                            //printf("\nFinding geq for %d", V_temp[V_temp_len].o_ab);
                            self->g_V_temp[self->g_V_temp_len].o_ab = self->g_V_sparse_H1[mm];
                            // Find the first low greater than or equal pivot
                            find_H1_cohom_greater(self, &(self->g_V_temp[self->g_V_temp_len]), &(self->g_this_pivot));
                            //printf("\nFOUND (%d, %d) geq (%d, %d)", V_temp[V_temp_len].low.key1, V_temp[V_temp_len].low.key2\
                            //                            , self->g_this_pivot.key1, self->g_this_pivot.key2);

                            self->g_V_temp_len++;
                            //if (V_temp_len == V_temp_max_len){
                            //    V_temp_max_len += 10;
                            //    V_temp = (coboundary*)realloc(V_temp, V_temp_max_len*sizeof(coboundary));

                            //}
                            
                        }
                    //}
                    //else{

                    //    #pragma omp parallel for schedule(static) shared(self, start, end)
                    //    for (EDGE_ID mm = 0; mm < end-start; mm++){
                    //          
                    //        //printf("\nFinding geq for %d", V_temp[V_temp_len].o_ab);
                    //        self->g_V_temp[self->g_V_temp_len+mm].o_ab = self->g_V_sparse_H1[mm+start];
                    //        // Find the first low greater than or equal pivot
                    //        find_H1_cohom_greater(self, &(self->g_V_temp[self->g_V_temp_len+mm]), &self->g_this_pivot);
                    //        //printf("\nFOUND (%d, %d) geq (%d, %d)", V_temp[V_temp_len].low.key1, V_temp[V_temp_len].low.key2\
                    //        //                            , self->g_this_pivot.key1, self->g_this_pivot.key2);

                    //        //V_temp_len++;
                    //        
                    //        //if (V_temp_len == V_temp_max_len){
                    //        //    V_temp_max_len += 10;
                    //        //    V_temp = (coboundary*)realloc(V_temp, V_temp_max_len*sizeof(coboundary));

                    //        //}
                    //        
                    //    }

                    //    self->g_V_temp_len += end-start;
                    //      

                    //    

                    //}


                }

                //if (self->g_new_debug){

                //    printf("\nV temp is ");
                //    for (EDGE_ID mm = 0; mm < self->g_V_temp_len; mm++){
                //      printf("%d, ", self->g_V_temp[mm].o_ab);
                //    }
                //    getchar();
                //}



                // Now we have to reduce
                int coeff;
                int reduction_counter = 0;

                while (1){

                    //printf("\nInside reduction pivot for %d is (%d, %d)", i, self->g_this_pivot.key1, self->g_this_pivot.key2);

                    EDGE_ID o_min = self->g_n_valid_edges;
                    EDGE_ID v_min = 0;


                    //if (self->g_new_debug){

                    //    printf("\niteration %d, pivot is (%d, %d)\n", reduction_counter,  self->g_this_pivot.key1, self->g_this_pivot.key2);

                    //    for (EDGE_ID mm = 0; mm < self->g_V_temp_len; mm++){

                    //        printf("\n%d has low (%d, %d)", self->g_V_temp[mm].o_ab, self->g_V_temp[mm].low.key1, self->g_V_temp[mm].low.key2);

                    //    }
                    //    reduction_counter++;

                    //}

                    //printf("\nV len %d: , red counter: %d", self->g_V_temp_len, reduction_counter);
                    //printf("\n");

                    empty = 1;



                    if (self->g_V_temp_len > 10000){

                          //////////////////////////////////////
                          // Allocate jobs
                          //////////////////////////////////////
                          //printf("\nAllocating jobs for parallel");
                          //getchar();

                          //printf("\nOld pivot is (%d, %d)", self->g_this_pivot.key1, self->g_this_pivot.key2);
                          //getchar();
                          
                          //printf("\nDoing parallel");

                          allocate_jobs(self, self->g_V_temp_len);

                          self->g_processed_threads = 0;

                          pthread_cond_broadcast(&(self->g_start_workers));


                          while (self->g_processed_threads != self->g_cpu_count){
                                
                                pthread_cond_wait(&(self->g_start_boss) \
                                                ,&(self->g_thread_lock));
                          }

                          //printf("\nOut of parallel ");
                          //getchar();


                          for (int tid = 0; tid < self->g_cpu_count; tid++){

                              //printf("\nthread %d, low is (%d, %d)", tid, self->g_this_pivot_thread[tid].key1\
                              //                                          , self->g_this_pivot_thread[tid].key2);

                              if (self->g_this_pivot_thread[tid].key1 == self->g_n_valid_edges){
                                continue;
                              }

                              if (self->g_empty_thread[tid]){
                                continue;
                              }

                              empty = 0;

                              if ((self->g_this_pivot_thread[tid].key1 < o_min) \
                                || ((self->g_this_pivot_thread[tid].key1 == o_min) && (self->g_this_pivot_thread[tid].key2 < v_min))){
                                    
                                      
                                      o_min = self->g_this_pivot_thread[tid].key1;
                                      v_min = self->g_this_pivot_thread[tid].key2;
                                      coeff = self->g_coeff_thread[tid];

                                }
                              else if ((self->g_this_pivot_thread[tid].key1 == o_min) && (self->g_this_pivot_thread[tid].key2 == v_min)){
                                    
                                    coeff += self->g_coeff_thread[tid];
                                    
                              }
                                

                          }

                          coeff = coeff % 2;

                          //printf("\nNew pivot is (%d, %d), coeff %d", o_min, v_min, coeff);
                          //getchar();

                          
                          // coeff, empty, self->g_this_pivot

                          //////////////////////////////////////


                    }
                    else{


                          for (EDGE_ID mm = 0; mm < self->g_V_temp_len; mm++){
                                

                              if (self->g_V_temp[mm].low.key1 == self->g_n_valid_edges){
                                continue;

                              }

                              if ((self->g_V_temp[mm].low.key1 == self->g_this_pivot.key1)\
                                  && (self->g_V_temp[mm].low.key2 == self->g_this_pivot.key2)){
                                    
                                    //if (self->g_new_debug){
                                    //printf("\nprev low of %d is (%d, %d)", self->g_V_temp[mm].o_ab, self->g_V_temp[mm].low.key1, self->g_V_temp[mm].low.key2);
                                    //}
                                    
                                    //printf("\npointers are %d, %d", self->g_V_temp[mm].a_ptr, self->g_V_temp[mm].b_ptr);
                                    
                                    find_H1_cohom_next(self, &(self->g_V_temp[mm]));

                                    //if (self->g_new_debug){
                                    //printf("\nnext low of %d is (%d, %d)", self->g_V_temp[mm].o_ab, self->g_V_temp[mm].low.key1, self->g_V_temp[mm].low.key2);
                                    //}
                                    //getchar();

                              }

                              empty = 0;

                              if ((self->g_V_temp[mm].low.key1 < o_min) \
                                || ((self->g_V_temp[mm].low.key1 == o_min) && (self->g_V_temp[mm].low.key2 < v_min))){
                                    
                                      
                                      o_min = self->g_V_temp[mm].low.key1;
                                      v_min = self->g_V_temp[mm].low.key2;
                                      coeff = 1;
                                }
                              else if ((self->g_V_temp[mm].low.key1 == o_min) && (self->g_V_temp[mm].low.key2 == v_min)){
                                    
                                    coeff = 1 - coeff;
                                    
                              }

                          }



                    }

                    self->g_this_pivot.key1 = o_min;
                    self->g_this_pivot.key2 = v_min;

                    if (self->g_this_pivot.key1 == self->g_n_valid_edges){
                        empty = 1;
                    }

                    //printf("\n");


                    //if (self->g_new_debug){
                    //    printf("\nCoeff is %d for low (%d, %d)", coeff, self->g_this_pivot.key1, self->g_this_pivot.key2);
                    //}

                    if ((coeff) || (empty)){
                        // We have a new pivot or it is empty
                        break;
                    }
                    
                }

                if (empty){
                      break;
                }

                //if (self->g_new_debug){

                //    printf("\n\nnew pivot is pivot (%d, %d)\n\n", self->g_this_pivot.key1, self->g_this_pivot.key2);
                //    getchar();
                //}

                // NEEEEEEWWWWWWWWWWWWWWWW
                // CHeck whether the low of the key1 of this is this simplex

                //coboundary try;
                //try.o_ab = self->g_this_pivot.key1;
                //find_H1_cohom_low(self, &try);

                //simplex lowlowlow = self->g_coH1_all_lows[self->g_this_pivot.key1].low;

                if ((self->g_coH1_all_lows[self->g_this_pivot.key1].low.key1 == self->g_this_pivot.key1)\
                    && (self->g_coH1_all_lows[self->g_this_pivot.key1].low.key2 == self->g_this_pivot.key2)){


                        reduce_w_bndry = self->g_this_pivot.key1;
                        //if (self->g_new_debug){
                        //    printf("\nreducing with trivial %d", reduce_w_bndry);
                        //}
                        V_col_idx = 0;
                        continue;

                }

                // If this low is not a pivot
                if (!self->g_H1_cohom_pivots_len[self->g_this_pivot.key1]){
                    break;
                }

                idx = search_H1_cohom_pivots(self->g_H1_cohom_pivots[self->g_this_pivot.key1]\
                                    , 0 \
                                    , self->g_H1_cohom_pivots_len[self->g_this_pivot.key1] - 1\
                                    , self->g_this_pivot.key2 \
                                    , self->g_n_valid_edges);




                if (idx == self->g_n_valid_edges){
                    //if (self->g_new_debug){

                    //    printf("\nThis pivot was not found in complex.\n", self->g_this_pivot.key1, self->g_this_pivot.key2);
                    //    getchar();
                    //}
                  break;
                }

                reduce_w_bndry = self->g_H1_cohom_pivots[self->g_this_pivot.key1][idx].bndry;
                V_col_idx = self->g_H1_cohom_pivots[self->g_this_pivot.key1][idx].col_idx;
                //if (self->g_new_debug){
                //    printf("\nreducing with pivot in complex %d with V_col_idx %d", reduce_w_bndry, V_col_idx);
                //}
                  
          }


           // This was not reduced to 0, add self->g_this_pivot and update V_reduced
           if (!empty) {

               PAR birth = self->g_edge_parameter[self->g_V_temp[0].o_ab];
               PAR death = self->g_edge_parameter[self->g_this_pivot.key1];
               if (birth != death){

                      //printf("\nNon trivial pers pair (%f, %f)", birth, death);
                      

                      if (birth > death){
                          printf("\nNOT EMPTY");
                          printf("\nBirth, death (%lf, %lf)", birth, death);
                          printf("\nError %d at pair (%d, %d)", self->g_V_temp[0].o_ab\
                                                               , self->g_this_pivot.key1\
                                                               , self->g_this_pivot.key2);
                          getchar();
                        
                      }
                }

                update_V_coH1(self);

           }
           else{

                PAR birth = self->g_edge_parameter[i];
                PAR death = -1;
                      
                      if (self->g_H1_pers_pairs_len+2 == self->g_H1_pers_pairs_max_len){
                            self->g_H1_pers_pairs_max_len += 1000;
                            self->g_H1_pers_pairs = (PAR*)realloc(self->g_H1_pers_pairs, self->g_H1_pers_pairs_max_len*sizeof(PAR));
                      
                      }
                      self->g_H1_pers_pairs[self->g_H1_pers_pairs_len++] = birth;
                      self->g_H1_pers_pairs[self->g_H1_pers_pairs_len++] = death;

           }

                
                  


    }


     /////////////////////////
     // Cancel the threads used in getting next during reduction
     /////////////////////////

     self->g_delete_threads = 1;

     pthread_cond_broadcast(&(self->g_start_workers));

     pthread_mutex_unlock(&(self->g_thread_lock));

     for (int i = 0; i < self->g_cpu_count; i++){

        pthread_join(self->g_threads[i], NULL);
      
     }

     free(self->g_threads);
     free(self->g_jobs);



    self->g_H1_pers_pairs = (PAR*)realloc(self->g_H1_pers_pairs, self->g_H1_pers_pairs_len*sizeof(PAR));

    //// BINARY FILE
    //FILE* fp2 = fopen("H1_pers_pairs.bin", "wb");
    //fwrite(self->g_H1_pers_pairs, sizeof(PAR),self->g_H1_pers_pairs_len, fp2);
    //fclose(fp2);

    // TEXT FILE
    fp2 = fopen(self->g_H1_pers_file, "w");

    if (self->g_filetype == 1){

        for (EDGE_ID it = 0; it < self->g_H1_pers_pairs_len; it+=2){
              
              fprintf(fp2, "%0.12lf, %0.12lf\n", sqrt(self->g_H1_pers_pairs[it]), sqrt(self->g_H1_pers_pairs[it+1]));
              
        }

    }
    else{

        for (EDGE_ID it = 0; it < self->g_H1_pers_pairs_len; it+=2){
              
              fprintf(fp2, "%0.12lf, %0.12lf\n", self->g_H1_pers_pairs[it], self->g_H1_pers_pairs[it+1]);
              
        }

    }

    fclose(fp2);

    // FREE V_temp
    free(self->g_V_temp);

    // FREE V_sparse
    free(self->g_V_sparse_H1);



    clock_gettime(CLOCK_MONOTONIC, &finish_wall_clock);
    self->g_timer_coH1 = (finish_wall_clock.tv_sec - start_wall_clock.tv_sec);
    self->g_timer_coH1 += (finish_wall_clock.tv_nsec - start_wall_clock.tv_nsec) / 1000000000.0;
    //printf("Time: %lf\n", elapsed_wall_clock);



    if (self->g_dim_lim == 1){
      printf("Time to process input: %lf\n" , self->g_timer_process);
      printf("Time to sort edges: %lf\n" , self->g_timer_sort_edges);
      printf("Time to create neigh: %lf\n" , self->g_timer_neigh);
      printf("Time to compute H0: %lf\n"   , self->g_timer_H0);
      printf("Time to compute coH1: %lf\n" , self->g_timer_coH1);
      printf("Total time taken: %lf\n", self->g_timer_neigh\
                                    + self->g_timer_H0\
                                    + self->g_timer_coH1\
                                    );
      deallocator(self);
      printf("\nQuitting after coH1");
      //return PyLong_FromLong(1);
      Py_RETURN_NONE;
      //exit(0);
    }

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
//            STEP coH2.1: Find cohomology now for the triangles
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

     //printf("\nPRESS KEY TO START coH2");
     //getchar();

     clock_gettime(CLOCK_MONOTONIC, &start_wall_clock);

     printf("\n\n--------------");
     printf("\nComputing coH2");
     printf("\n--------------");


    // sparse V coH2
    self->g_V_sparse_max = 1000;
    self->g_V_sparse_H2 = (simplex*)malloc(self->g_V_sparse_max*sizeof(simplex));
    self->g_V_sparse_ptr = 1;
    self->g_V_sparse_beg_ptr = 1;
    self->g_V_sparse_end_ptr = 1;

    self->g_V_col_indices_ptr = 1;


     // WORKSPACE
     self->g_cohom_ws_size = 100;
     self->g_V_ws_H2 = (coboundary_H2**)malloc(self->g_cohom_ws_size*sizeof(coboundary_H2*)); 
     self->g_V_ws_H2_info = (coboundary_H2_ws*)malloc(self->g_cohom_ws_size*sizeof(coboundary_H2_ws));


     for (int it = 0; it < self->g_cohom_ws_size; it++){
          
              self->g_V_ws_H2_info[it].max_len = 100;
              self->g_V_ws_H2[it] = \
                          (coboundary_H2*)malloc(2*self->g_V_ws_H2_info[it].max_len*sizeof(coboundary_H2)); 
          
     }
    

    // PIVOTS
    self->g_H2_cohom_pivots = (H2_cohom_pivots**)malloc(self->g_n_valid_edges*sizeof(H2_cohom_pivots*));

    self->g_H2_cohom_pivots_len = (EDGE_ID*)calloc(self->g_n_valid_edges, sizeof(EDGE_ID));
    self->g_H2_cohom_pivots_max_len = (EDGE_ID*)malloc(self->g_n_valid_edges*sizeof(EDGE_ID));

    for (EDGE_ID mm = 0; mm < self->g_n_valid_edges; mm++){
         
       self->g_H2_cohom_pivots_max_len[mm] = 2;
       self->g_H2_cohom_pivots[mm] = \
                              (H2_cohom_pivots*)malloc(self->g_H2_cohom_pivots_max_len[mm]*sizeof(H2_cohom_pivots));
         
    }



    // Buffer to find first lows
    
    EDGE_ID buffer_len = 100000;
    int buffer_ptr = 0;
    coboundary_H2* coH2_lows_buffer = (coboundary_H2*)malloc(buffer_len*sizeof(coboundary_H2));
    int* flag_trivial_pair = (int*)malloc(buffer_len*sizeof(int));

    int* flag_reduce_with_triangle = (int*)malloc(buffer_len*sizeof(int));
    simplex* reduce_with_triangle = (simplex*)malloc(buffer_len*sizeof(simplex));
    //coboundary_H2* coH2_lows_buffer;
    


     // H1 Pers pairs
     self->g_H2_pers_pairs_max_len = 1000;
     self->g_H2_pers_pairs_len = 0;
     self->g_H2_pers_pairs = (PAR*)malloc(self->g_H2_pers_pairs_max_len*sizeof(PAR));

     ////////////////////////////////////////////////////////////////
     // Allocate jobs/threads for parallel coH2
     ////////////////////////////////////////////////////////////////
     
     self->g_jobs = (int*)malloc((self->g_cpu_count + 1)*sizeof(int));

     allocate_jobs(self, self->g_cohom_ws_size);

     self->g_threads = (pthread_t *)malloc(self->g_cpu_count*sizeof(pthread_t));

     if ((rtn = pthread_mutex_init(&(self->g_thread_lock), NULL)) !=0)
        fprintf(stderr, "pthread_mutex_init %s", strerror(rtn)), exit(-1);

     if ((rtn = pthread_cond_init(&(self->g_start_boss), NULL)) !=0)
        fprintf(stderr, "pthread_cond_init %s", strerror(rtn)), exit(-1);

     if ((rtn = pthread_cond_init(&(self->g_start_workers), NULL)) !=0)
        fprintf(stderr, "pthread_cond_init %s", strerror(rtn)), exit(-1);


     // Initialize thread creation
     self->g_thread_id = 0;
     self->g_sleeping_threads = 0;
     self->g_delete_threads = 0;

     for (int i = 0; i < self->g_cpu_count; i++){

        if ((rtn = pthread_create( \
                                &(self->g_threads[i]) \
                                , NULL \
                                , reduce_with_complex_coH2 \
                                , (void*)self)!= 0))
          fprintf(stderr, "pthread_create %d", rtn), exit(-1);
      
     }

     // Wait for threads to be initialized
     pthread_mutex_lock(&(self->g_thread_lock));

     while(self->g_sleeping_threads != self->g_cpu_count){
        
          pthread_cond_wait(&(self->g_start_boss) \
                          , &(self->g_thread_lock));

     }

    ////////////////////////////////

     // TEST

     //coboundary_H2 temp2;
     //temp2.triangle.key1 = 5113;
     //temp2.triangle.key2 = 922;

     //find_H2_cohom_low(self, &temp2);

     //printf("\nlow is (%d, %d)", temp2.low.key1, temp2.low.key2);
     //getchar();


    ///////////////////////////////////////////////////
    // MAIN coH2 loop 
    ///////////////////////////////////////////////////
    
    i = self->g_n_valid_edges;
    int reduce_buffer = 0;

    simplex* temp_temp_triangles = (simplex*)malloc(self->g_n_vert*sizeof(simplex));
    VERT_ID temp_temp_len = 0;

    simplex* triangle = (simplex*)malloc(sizeof(simplex));
    

    self->g_p_flag = 0;

    while(i){

          i--;

          //printf("\rProcessing coH2 for edge %d", i);
          //getchar();

          VERT_ID a = self->g_edges_list[i][0];
          VERT_ID b = self->g_edges_list[i][1];

          // Find the faces which are created when this edge is formed
          // That means, the o_max will be i

          EDGE_ID ab = i;

          VERT_ID a_ptr = 0;
          VERT_ID b_ptr = 0;

          EDGE_ID mid;

          while ((a_ptr < self->g_Neigh_len[a])\
                && (b_ptr < self->g_Neigh_len[b])){


                if (self->g_Neighbors[a][a_ptr].neighbor < self->g_Neighbors[b][b_ptr].neighbor)
                {
                      a_ptr++;
                }

                else if (self->g_Neighbors[a][a_ptr].neighbor > self->g_Neighbors[b][b_ptr].neighbor)
                {
                      b_ptr++;
                }

                else{

                      VERT_ID c = self->g_Neighbors[a][a_ptr].neighbor;

                      //if (!simplex2_check(a, b, c)) continue;

                      EDGE_ID ac = self->g_Neighbors[a][a_ptr++].order;
                      EDGE_ID bc = self->g_Neighbors[b][b_ptr++].order;

                      if ((ac > ab) \
                          || (bc > ab))
                        continue;

                      triangle->key1 = ab;
                      triangle->key2 = (EDGE_ID)c;

                      ///////////////////////////////////////////////////
                      // CLEARING ALGORITHM
                      // Does this triangle have a pivot in coH1?
                      ///////////////////////////////////////////////////

                      //EDGE_ID has_pivot = search_simplices(self->g_triangles_with_pivots \
                      //                          , 0 \
                      //                          , self->g_cohom_n_H0 - 1\
                      //                          , triangle \
                      //                          , self->g_cohom_n_H0 \
                      //                          );
                      
                      //printf("\nchecking 0");

                      //
                      
                      // Check whether the triangle is pivot of a trivial pair in coH1
                      if ((self->g_coH1_all_lows[triangle->key1].low.key1 == triangle->key1)\
                          &&(self->g_coH1_all_lows[triangle->key1].low.key2 == triangle->key2)){
                            //printf("\nSkipping");
                            continue;
                      }

                      // Check whether the triangle is a pivot in coH1
                      if (self->g_H1_cohom_pivots_len[triangle->key1]){

                          EDGE_ID has_pivot = search_H1_cohom_pivots(self->g_H1_cohom_pivots[triangle->key1]\
                                        , 0 \
                                        , self->g_H1_cohom_pivots_len[triangle->key1] - 1\
                                        , triangle->key2 \
                                        , self->g_n_valid_edges);
                          
                          if (has_pivot != self->g_n_valid_edges){
                            //This triangle has a pivot in H1. So, skip it. Continue;
                                //printf("\nSkipping");
                                //getchar();
                                continue;
                          }

                      }



                      //printf("\nAdding");

                      //temp_temp_triangles[temp_temp_len].key1 = o_max;
                      //temp_temp_triangles[temp_temp_len++].key2 = third_vertex;
                      temp_temp_triangles[temp_temp_len++] = *triangle;

                }

          }


          //// SERIAL
          //while (temp_temp_len > 0){

          //    temp_temp_len--;

          //    //coH2_lows_buffer[buffer_ptr++].triangle = temp_temp_triangles[temp_temp_len];

          //    self->g_V_temp_H2[0].triangle = temp_temp_triangles[temp_temp_len]; 

          //    find_H2_cohom_low(self, &(self->g_V_temp_H2[0]));

          //    H2_reduce(self, coH2_lows_buffer, 1);



          //}


          // Iterate over the triangles
          while(temp_temp_len > 0){
                
                temp_temp_len--;

                coH2_lows_buffer[buffer_ptr++].triangle = temp_temp_triangles[temp_temp_len];
                //printf("\r buffer ptr %d", buffer_ptr);

                if (buffer_ptr == buffer_len){

                      //printf("\ninside");
                      //getchar();

                      //printf("\n");

                      #pragma omp parallel for schedule(static) \
                                               shared(self, coH2_lows_buffer)
                      for (EDGE_ID mm = 0; mm < buffer_len; mm++) {

                           //printf("\rFinding low of %d", mm);

                           find_H2_cohom_low(self, &(coH2_lows_buffer[mm]));

                           flag_trivial_pair[mm] = 0;
                           flag_reduce_with_triangle[mm] = 0;

                           if (coH2_lows_buffer[mm].vertex == -1){
                                continue;
                           }
                           //EDGE_ID o_ab = coH2_lows_buffer[mm].low.key1;
                           //EDGE_ID o_cd = coH2_lows_buffer[mm].low.key2;
                           //VERT_ID d = self->g_edges_list[o_cd][1];
                           //

                           //coH2_lows_buffer[mm].coeff = 1;


                           if ((coH2_lows_buffer[mm].low.key1 == coH2_lows_buffer[mm].triangle.key1)\
                               &&(self->g_edges_list[coH2_lows_buffer[mm].low.key2][1]== coH2_lows_buffer[mm].triangle.key2)){
                                
                                //printf("\nflg trivial");
                                flag_trivial_pair[mm] = 1;
                                //getchar();
                                continue;
                           }

                           coboundary_H2 temp;
                           
                           temp.triangle.key1 = coH2_lows_buffer[mm].low.key1;
                           temp.triangle.key2 = self->g_edges_list[coH2_lows_buffer[mm].low.key2][1];

                           find_H2_cohom_low(self, &(temp));

                           if ((temp.low.key1 == coH2_lows_buffer[mm].low.key1)\
                               && (temp.low.key2 == coH2_lows_buffer[mm].low.key2)){

                              flag_reduce_with_triangle[mm] = 1;
                              reduce_with_triangle[mm].key1 = coH2_lows_buffer[mm].low.key1;
                              reduce_with_triangle[mm].key2 =self->g_edges_list[coH2_lows_buffer[mm].low.key2][1];

                              continue;

                           }


                      }


                      for (EDGE_ID mm = 0; mm < buffer_len; mm++) {

                            if ((!flag_trivial_pair[mm])\
                                && (coH2_lows_buffer[mm].vertex!= -1)){

                                //printf("\nTriangle (%d, %d) is not trivial pair"\
                                //                          , coH2_lows_buffer[mm].triangle.key1\
                                //                          , coH2_lows_buffer[mm].triangle.key2\
                                //                          );

                                
                                //if ((coH2_lows_buffer[mm].triangle.key1 == 36920)\
                                //    && (coH2_lows_buffer[mm].triangle.key2 == 161)){
                                //      self->g_p_flag = 0;
                                //}

                                self->g_V_ws_H2[self->g_ws_counter][0] = coH2_lows_buffer[mm]; 

                                coboundary_H2_ws* this_ws = self->g_V_ws_H2_info + self->g_ws_counter;

                                //this_ws->original = 0;

                                this_ws->pivot = coH2_lows_buffer[mm].low;

                                this_ws->flag_red_w_complex = 0;

                                this_ws->flag_red_w_trivial = 0;
                                       
                                this_ws->flag_append_to_complex = 0;

                                this_ws->flag_non_empty = 1;

                                this_ws->len = 1;

                                this_ws->flag_red_w_trivial = flag_reduce_with_triangle[mm];

                                if (flag_reduce_with_triangle[mm]){
                                      this_ws->reduce_with_trivial = reduce_with_triangle[mm];
                                }


                                self->g_ws_counter++;

                                if (self->g_ws_counter == self->g_cohom_ws_size){

                                     reduce_ws_coH2(self);

                                }

                            }

                      }

                      buffer_ptr = 0;

                }



          }



    }


    //printf("\nlast batch");
    //getchar();
    
    #pragma omp parallel for schedule(static) \
                             shared(self, coH2_lows_buffer)
    for (EDGE_ID mm = 0; mm < buffer_ptr; mm++) {

         //printf("\rFinding low of %d", mm);

         find_H2_cohom_low(self, &(coH2_lows_buffer[mm]));

         flag_trivial_pair[mm] = 0;
         flag_reduce_with_triangle[mm] = 0;

         //coH2_lows_buffer[mm].coeff = 1;

         if (coH2_lows_buffer[mm].vertex == -1){
              continue;
         }

         if ((coH2_lows_buffer[mm].low.key1 == coH2_lows_buffer[mm].triangle.key1)\
             &&(self->g_edges_list[coH2_lows_buffer[mm].low.key2][1]== coH2_lows_buffer[mm].triangle.key2)){
              
              //printf("\nflg trivial");
              //
              flag_trivial_pair[mm] = 1;
              //getchar();
              continue;
         }


         coboundary_H2 temp;
         
         temp.triangle.key1 = coH2_lows_buffer[mm].low.key1;
         temp.triangle.key2 = self->g_edges_list[coH2_lows_buffer[mm].low.key2][1];

         find_H2_cohom_low(self, &(temp));

         if ((temp.low.key1 == coH2_lows_buffer[mm].low.key1)\
             && (temp.low.key2 == coH2_lows_buffer[mm].low.key2)){

            flag_reduce_with_triangle[mm] = 1;
            reduce_with_triangle[mm].key1 = coH2_lows_buffer[mm].low.key1;
            reduce_with_triangle[mm].key2 =self->g_edges_list[coH2_lows_buffer[mm].low.key2][1];

            continue;

         }

    }


    for (EDGE_ID mm = 0; mm < buffer_ptr; mm++) {

          if ((!flag_trivial_pair[mm])\
              && (coH2_lows_buffer[mm].vertex!= -1)){
          
              self->g_V_ws_H2[self->g_ws_counter][0] = coH2_lows_buffer[mm]; 

              coboundary_H2_ws* this_ws = self->g_V_ws_H2_info + self->g_ws_counter;

              //this_ws->original = 0;

              this_ws->pivot = coH2_lows_buffer[mm].low;

              this_ws->flag_red_w_complex = 0;

              this_ws->flag_red_w_trivial = 0;

              this_ws->flag_append_to_complex = 0;

              this_ws->flag_non_empty = 1;

              this_ws->len = 1;

              this_ws->flag_red_w_trivial = flag_reduce_with_triangle[mm];
              
              if (flag_reduce_with_triangle[mm]){
                  this_ws->reduce_with_trivial = reduce_with_triangle[mm];
              }


              self->g_ws_counter++;

              if (self->g_ws_counter == self->g_cohom_ws_size){

                   reduce_ws_coH2(self);

              }
          

          }

    }


    while (self->g_ws_counter){

          allocate_jobs(self, self->g_ws_counter);
          reduce_ws_coH2(self);

    }



    //////////////////////////////////////////////////
    // Cancel the threads used in getting next during reduction
    //////////////////////////////////////////////////

    self->g_delete_threads = 1;

    pthread_cond_broadcast(&(self->g_start_workers));

    pthread_mutex_unlock(&(self->g_thread_lock));

    for (int i = 0; i < self->g_cpu_count; i++){

       pthread_join(self->g_threads[i], NULL);
     
    }

    free(self->g_threads);
    free(self->g_jobs);
    //printf("\nTime taken to compute coH1 %f", omp_get_wtime() - time_compute_coH1);

    // Deallocate coH2 Workspace
    for (int ws_counter = 0; ws_counter < self->g_cohom_ws_size; ws_counter++){
          
          free(self->g_V_ws_H2[ws_counter]);

    }

    free(self->g_V_ws_H2);
    free(self->g_V_ws_H2_info);



    // Deallocate coH2 structures
    free(coH2_lows_buffer);
    free(flag_trivial_pair);
    free(flag_reduce_with_triangle);


    self->g_H2_pers_pairs = (PAR*)realloc(self->g_H2_pers_pairs, self->g_H2_pers_pairs_len*sizeof(PAR));

    // BINARY FILE
    //fp2 = fopen("H2_pers_pairs.bin", "wb");
    //fwrite(self->g_H2_pers_pairs, sizeof(PAR),self->g_H2_pers_pairs_len, fp2);
    //fclose(fp2);

    // TEXT FILE
    fp2 = fopen(self->g_H2_pers_file, "w");

    if (self->g_filetype == 1){

        for (EDGE_ID it = 0; it < self->g_H2_pers_pairs_len; it+=2){
              
              fprintf(fp2, "%0.12lf, %0.12lf\n", sqrt(self->g_H2_pers_pairs[it]), sqrt(self->g_H2_pers_pairs[it+1]));
              
        }

    }
    else{

        for (EDGE_ID it = 0; it < self->g_H2_pers_pairs_len; it+=2){
              
              fprintf(fp2, "%0.12lf, %0.12lf\n", self->g_H2_pers_pairs[it], self->g_H2_pers_pairs[it+1]);
              
        }

    }

    fclose(fp2);


    clock_gettime(CLOCK_MONOTONIC, &finish_wall_clock);
    self->g_timer_coH2 = (finish_wall_clock.tv_sec - start_wall_clock.tv_sec);
    self->g_timer_coH2 += (finish_wall_clock.tv_nsec - start_wall_clock.tv_nsec) / 1000000000.0;

    printf("Time to process input: %lf\n" , self->g_timer_process);
    printf("Time to sort edges: %lf\n" , self->g_timer_sort_edges);
    printf("Time to create neigh: %lf\n" , self->g_timer_neigh);
    printf("Time to compute H0: %lf\n"   , self->g_timer_H0);
    printf("Time to compute coH1: %lf\n" , self->g_timer_coH1);
    printf("Time to compute coH2: %lf\n" , self->g_timer_coH2);
    //printf("Time to compute coH2 serial: %lf\n" , self->g_timer_coH2_serial);
    //printf("Time to compute coH2 parallel: %lf\n" , self->g_timer_coH2_parallel);
    printf("Total time taken: %lf\n", self->g_timer_neigh\
                                    + self->g_timer_H0\
                                    + self->g_timer_coH1\
                                    + self->g_timer_coH2\
                                    );
    //printf("Time in H2_low: %lf\n", self->g_timer_H2_low);
    //printf("Time in H2_greater: %lf\n", self->g_timer_H2_greater);
    //printf("Time in H2_next: %lf\n", self->g_timer_H2_next);
    deallocator(self);
    printf("\nhave quit");
    //return PyLong_FromLong(1);
    Py_RETURN_NONE;




/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
////
////            STEP H1.1: Find homology now for the triangles
////
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
//
//
//     printf("\n\n---------------");
//     printf("\nComputing H1...");
//     printf("\n---------------\n");
//
//
//
//     self->g_R_sparse_max_H1 = 100;
//     self->g_R_sparse_H1 = (EDGE_ID*)malloc(self->g_R_sparse_max_H1*sizeof(EDGE_ID));
//
//     self->g_R_sparse_ptr_H1 = 0;
//     self->g_R_sparse_beg_ptr_H1 = 0;
//     self->g_R_sparse_end_ptr_H1 = 0;
//
//
//     self->g_R_col_indices_max_H1 = 100;
//     self->g_R_col_indices_H1 = (EDGE_ID*)malloc(self->g_R_col_indices_max_H1*sizeof(EDGE_ID));
//
//     self->g_R_col_indices_ptr_H1 = 1;
//
//
//
//     self->g_R_simplex_H1 = (EDGE_ID*)malloc(self->g_R_col_idx_max_len_H1*sizeof(EDGE_ID));
//
//
//     self->g_workspace_size = 1000;
//
//     self->g_ws_pre_alloc = 1000;
//     // Initialize ws counter
//     self->g_ws_counter = 0;
//
//     // H1 workspace structures
//     self->g_workspace_H1 = \
//                     (EDGE_ID**)malloc(self->g_workspace_size*sizeof(EDGE_ID*));
//
//     // H1 workspace info
//
//     self->g_workspace_H1_info = (boundary_H1_ws*)(self->g_workspace_size*sizeof(boundary_H1_ws));
//
//
//     for (int i = 0; i < self->g_workspace_size; i++){
//
//         self->g_workspace_H1_info[i].max_len = self->g_ws_pre_alloc;
//
//         self->g_workspace_H1[i] = (EDGE_ID*)malloc(2*self->g_workspace_H1_info[i].max_len*sizeof(EDGE_ID));
//
//
//
//     }
//
//
//     // Pivots
//     self->g_pivots_H1 = (EDGE_ID*)calloc(self->g_n_valid_edges, sizeof(EDGE_ID));
//
//
//     ////////////////////////////////////////////////////////////////
//     //
//     //   Allocate jobs for parallel H1
//     // 
//     ////////////////////////////////////////////////////////////////
//
//     self->g_jobs = (int*)malloc((self->g_cpu_count + 1)*sizeof(int));
//
//     allocate_jobs(self, self->g_workspace_size);
//
//     int rtn;
//
//     self->g_threads = (pthread_t *)malloc(self->g_cpu_count*sizeof(pthread_t));
//
//     if ((rtn = pthread_mutex_init(&(self->g_thread_lock), NULL)) !=0)
//        fprintf(stderr, "pthread_mutex_init %s", strerror(rtn)), exit(-1);
//
//     if ((rtn = pthread_cond_init(&(self->g_start_boss), NULL)) !=0)
//        fprintf(stderr, "pthread_cond_init %s", strerror(rtn)), exit(-1);
//
//     if ((rtn = pthread_cond_init(&(self->g_start_workers), NULL)) !=0)
//        fprintf(stderr, "pthread_cond_init %s", strerror(rtn)), exit(-1);
//
//
//     // Initialize thread creation
//     self->g_thread_id = 0;
//     self->g_sleeping_threads = 0;
//     self->g_delete_threads = 0;
//
//     for (int i = 0; i < self->g_cpu_count; i++){
//
//        if ((rtn = pthread_create( \
//                                &(self->g_threads[i]) \
//                                , NULL \
//                                , reduce_with_complex_H1 \
//                                , (void*)self)!= 0))
//          fprintf(stderr, "pthread_create %d", rtn), exit(-1);
//      
//     }
//
//     // Wait for threads to be initialized
//     pthread_mutex_lock(&(self->g_thread_lock));
//
//     while(self->g_sleeping_threads != self->g_cpu_count){
//        
//          pthread_cond_wait(&(self->g_start_boss) \
//                          , &(self->g_thread_lock));
//
//     }
//
//     ////////////////////////////////
//
//
//
//
//
//
//     for (EDGE_ID o_ab = 0; o_ab < self->g_n_valid_edges; o_ab++){
//
//         // This edge is never max edge of a triangle with a pivot in coH1
//         if (!self->g_H1_cohom_pivots_len[o_ab]){
//           continue;
//         }
//          
//         VERT_ID a = self->g_edges_list[o_ab][0];
//         VERT_ID b = self->g_edges_list[o_ab][1];
//
//
//         VERT_ID a_ptr = 0;
//         VERT_ID b_ptr = 0;
//
//         while ((a_ptr < self->g_Neigh_len[a])\
//              && (b_ptr < self->g_Neigh_len[b])){
//              
//
//              if (self->g_Neighbors[a][a_ptr].neighbor < self->g_Neighbors[b][b_ptr].neighbor){
//
//                    a_ptr++;
//
//              }
//              else if (self->g_Neighbors[a][a_ptr].neighbor > self->g_Neighbors[b][b_ptr].neighbor){
//
//                    b_ptr++;
//              }
//              else{
//                    
//                    VERT_ID c = self->g_Neighbors[a][a_ptr].neighbor;
//                    
//                    EDGE_ID o_ac = self->Neighbors[a][a_ptr].order;
//
//                    if (o_ac > o_ab){
//                        continue;
//                    }
//
//                    EDGE_ID o_bc = self->Neighbors[b][b_ptr].order;
//
//                    if (o_bc > o_ab){
//                        continue;
//                    }
//
//                    // This is a valid triangle
//
//                    int flag_append = 0;
//
//                    // Is this a part of trivial triangle in coH1?
//                    if ((all_lows[o_ab].low.key1 == o_ab)\
//                        && (all_lows[o_ab].low.key2 == c)){ 
//
//                        
//                        flag_append = 1;
//                        
//
//                    }
//                    else{
//                        
//                        
//                        // Find whether this triangle is pivot in coH1
//                        idx = search_H1_cohom_pivots(self->g_H1_cohom_pivots[o_ab]\
//                                          , 0 \
//                                          , self->g_H1_cohom_pivots_len[o_ab] - 1\
//                                          , c \
//                                          , self->g_n_valid_edges);
//
//                        if (idx != self->g_n_valid_edges){
//                            // Add to ws
//                            flag_append = 1;
//                        }
//
//                          
//                    }
//
//                    if (!flag_append) continue;
//
//
//                    // Workspace attributes
//                    boundary_H1_ws* this_ws = self->g_workspace_H1_info + self->g_ws_counter;
//
//                    // Initially, the original is at 0
//                    this_ws->original = 0;
//                    // Parallel control flags
//                    this_ws->flag_red_w_complex = 0;
//                    this_ws->flag_append_to_complex = 1;
//                    // Initial length of boundary
//                    this_ws->len = 3;
//                    // Initial pivot
//                    this_ws->pivot = o_ab;
//
//                    // Workspace
//                    boundary_H1* orig = self->g_workspace_H1[self->g_ws_counter];
//
//
//                    orig[2] = o_ab;
//                    
//                    if (o_ac > o_bc){
//
//                        orig[0] = o_bc;
//
//                        orig[1] = o_ac;
//
//                    }
//                    else {
//
//                        orig[0] = o_ac;
//
//                        orig[1] = o_bc;
//
//                    }
//
//                    self->g_ws_counter++;
//
//
//                    if (self->g_ws_counter == self->g_workspace_size){
//                          
//                          reduce_ws_H1(self);
//                    }
//
//
//              }
//
//               
//         }
//
//
//     }
//
//
//
//
//     // Reduction of final batch
//     while (self->g_ws_counter){
//          
//          allocate_jobs(self, self->g_ws_counter);
//          reduce_ws_H1(self);
//
//     }
//
//
//
//
//
//
//

      //return PyLong_FromLong(1);
      Py_RETURN_NONE;
    


}

VERT_ID search_Neighbors(filtration* self, VERT_ID v1, VERT_ID v2, VERT_ID l, VERT_ID r){

    if (r >= l) { 
        VERT_ID mid = l + (r - l) / 2; 
  
        // If the element is present at the middle 
        // itself 
        if (self->g_Neighbors[v1][mid].neighbor == v2) 
            return mid; 
  
        // If element is smaller than mid, then 
        // it can only be present in left subarray 
        if (self->g_Neighbors[v1][mid].neighbor > v2){ 
            if (!mid) return self->g_n_vert;
            return search_Neighbors(self, v1, v2, l, mid - 1); 
        }
  
        // Else the element can only be present 
        // in right subarray 
        return search_Neighbors(self, v1, v2, mid + 1, r); 
    } 
  
    // We reach here when element is not 
    // present in array 
    return self->g_n_vert; 

}


VERT_ID search_Neighbors_e(filtration* self, VERT_ID v1, EDGE_ID order, VERT_ID l, VERT_ID r, EDGE_ID len){
  
    int mid = l + (r - l) / 2; 

    if (self->g_Neighbors_e[v1][mid].order < order){
        
        if (mid < len-1)
          if (self->g_Neighbors_e[v1][mid+1].order > order)
            return mid+1;

        return search_Neighbors_e(self, v1, order, mid+1, r, len); 
          
    }
    else if (self->g_Neighbors_e[v1][mid].order > order){

        if (!mid) return 0;
        return search_Neighbors_e(self, v1, order, l, mid-1, len); 

    }
    else{
        
        return mid+1;

    }

}

//////////////////////////////////////////////////////////
// MERGING ALGORITHMS
//////////////////////////////////////////////////////////




//////////////////////////////////////////////////////////
// END OF MERGING ALGORITHMS
//////////////////////////////////////////////////////////




//////////////////////////////////////////////////////////
// CUSTOM BOUNDARIES
//////////////////////////////////////////////////////////
int simplex1_check(VERT_ID v1, VERT_ID v2, PAR dist, PAR thresh){

  if (dist == -1){
    return 0;
  }
  //if (dist == 0){
  //  return 0;
  //}
  
  if (dist > thresh){
    return 0;
  }

  return 1;

}

int simplex2_check(VERT_ID v1, VERT_ID v2, VERT_ID v3){

  return 1;

}

int simplex3_check(VERT_ID v1, VERT_ID v2, VERT_ID v3, VERT_ID v4){

  return 1;

}
//////////////////////////////////////////////////////////



int compare_simplices(simplex* s1, simplex* s2){
    
  // returns 1 if s1 > s2
  // returns 0 if s1 < s2
  // returns -1 if s1 = s2
      
    if (s1->key1 > s2->key1) return 1;
    else if (s1->key1 < s2->key1) return 0;
    else{
      if (s1->key2 > s2->key2) return 1;
      else if (s1->key2 < s2->key2) return 0;
      else return -1;
    }
      
}

int compare_simplices_keys(EDGE_ID key11, EDGE_ID key12 \
                         , EDGE_ID key21, EDGE_ID key22 \
                          ){
    
  // returns 1 if s1 > s2
  // returns 0 if s1 < s2
  // returns -1 if s1 = s2
      
    if (key11 > key21) return 1;
    else if (key11 < key21) return 0;
    else{
      if (key12 > key22) return 1;
      else if (key12 < key22) return 0;
      else return -1;
    }
      
}



void find_H1_cohom_low(filtration* self, coboundary_H1* V_info){


    VERT_ID a = self->g_edges_list[V_info->o_ab][0];
    VERT_ID b = self->g_edges_list[V_info->o_ab][1];

    V_info->a_ptr = 0;
    V_info->b_ptr = 0;

    EDGE_ID o_min = self->g_n_valid_edges;
    EDGE_ID v_min = 0;

    EDGE_ID o_s, v_s;


    while(1){

      if ((V_info->a_ptr < self->g_Neigh_len[a]) \
          && (V_info->b_ptr < self->g_Neigh_len[b])){


            if (self->g_Neighbors[a][V_info->a_ptr].neighbor < self->g_Neighbors[b][V_info->b_ptr].neighbor){

                V_info->a_ptr++;

            }
            else if (self->g_Neighbors[a][V_info->a_ptr].neighbor > self->g_Neighbors[b][V_info->b_ptr].neighbor){

                V_info->b_ptr++;

            }
            else{

               EDGE_ID ac = self->g_Neighbors[a][V_info->a_ptr].order;

               EDGE_ID bc = self->g_Neighbors[b][V_info->b_ptr].order;

               EDGE_ID c = self->g_Neighbors[a][V_info->a_ptr].neighbor;

               //printf("\n a, b, c: %d, %d, %d", a, b, c);
               //printf("\n ab, ac, bc: %d, %d, %d", V_info->o_ab, ac, bc);

               o_s = ac;
               v_s = b;

               if (bc > ac){
                  o_s = bc;
                  v_s = a;
               }

               if (o_s < V_info->o_ab){
                    
                    V_info->low.key1 = V_info->o_ab;
                    V_info->low.key2 = c;
                    return;
               }

               // Reached here means o_s > o_ab

               //printf("\n o_s, v_s, : %d, %d", o_s, v_s);
               if (o_s < o_min){
                 o_min = o_s;
                 v_min = v_s;
               }
               else if ((o_s == o_min) && (v_s < v_min)){
                 v_min = v_s;
               }
               //printf("\n o_min, v_min, : %d, %d", o_min, v_min);

               V_info->a_ptr++;
               V_info->b_ptr++;

            }

      }
      else{

          //printf("\nReturning lowest of > a(%d)b(%d) (%d, %d)", o_s, v_s);
          V_info->low.key1 = o_min;
          V_info->low.key2 = v_min;
          return;
        
      }

    }

}








// A recursive binary search function. It returns 
// location of x in given array arr[l..r] is present, 
// otherwise -1 
EDGE_ID search_H1_cohom_pivots(H1_cohom_pivots* arr, EDGE_ID l, EDGE_ID r, EDGE_ID key2, EDGE_ID max) 
{ 
    if (r >= l) { 
        EDGE_ID mid = l + (r - l) / 2; 

        if (arr[mid].key2 == key2) 
            return mid; 
  
        // If element is smaller than mid, then 
        // it can only be present in left subarray 
        if (arr[mid].key2 > key2) 
        {

          /// PRECAUTIONARY: CAN REMOVE LATER
            if (!mid){
                return max; 
                printf("\nMID 0 WILL GIVE ERROR FOR UNSIGNED NEXT");
                getchar();
            }
          ///////////////////
            return search_H1_cohom_pivots(arr, l, mid - 1, key2, max); 
        }
  
        // Else the element can only be present 
        // in right subarray 
        return search_H1_cohom_pivots(arr, mid + 1, r, key2, max); 
    } 
  
    // We reach here when element is not 
    // present in array 
    //printf("\nNOT FOUND");
    return max; 
} 



//void add_H1_pivot(H1_cohom_pivots* cohom_pivots, EDGE_ID* len, EDGE_ID* max_len){
//
//    
//
//}


// returns greater than or equal to
void find_H1_cohom_greater(filtration* self, coboundary_H1* V_info, simplex* pivot){


    EDGE_ID o_min = self->g_n_valid_edges;
    EDGE_ID v_min = 0;


    if (pivot->key1 < V_info->o_ab){
        
          // Find first low of o_ab
          find_H1_cohom_low(self, V_info);

          // If it has a low
          if (V_info->low.key1 < self->g_n_valid_edges){

                // Need to find a_ptr and b_ptr if first low.key1 > e
                if (V_info->low.key1 > V_info->o_ab){

                    VERT_ID a = self->g_edges_list[V_info->o_ab][0];
                    VERT_ID b = self->g_edges_list[V_info->o_ab][1];

                    V_info->a_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[a], 0, self->g_Neigh_len[a]-1\
                                                         , V_info->low.key1, self->g_Neigh_len[a]);

                    V_info->b_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[b], 0, self->g_Neigh_len[b]-1\
                                                            , V_info->low.key1, self->g_Neigh_len[b]);

                }

                
          }

          return;

          
    }
    else if (pivot->key1 == V_info->o_ab){

        VERT_ID a = self->g_edges_list[V_info->o_ab][0];
        VERT_ID b = self->g_edges_list[V_info->o_ab][1];

        V_info->a_ptr = 0;
        V_info->b_ptr = 0;

        EDGE_ID o_min = self->g_n_valid_edges;
        EDGE_ID v_min;

        EDGE_ID o_s;
        EDGE_ID v_s;


        V_info->a_ptr = bin_search_min_geq_N(self->g_Neighbors[a], 0, self->g_Neigh_len[a]-1\
                                                , pivot->key2, self->g_Neigh_len[a]);
        
        V_info->b_ptr = bin_search_min_geq_N(self->g_Neighbors[b], 0, self->g_Neigh_len[b]-1\
                                                             , pivot->key2, self->g_Neigh_len[b]);

        if ((self->g_Neighbors[a][V_info->a_ptr].neighbor == pivot->key2) \
            && (self->g_Neighbors[b][V_info->b_ptr].neighbor == pivot->key2)){

            V_info->low.key1 = V_info->o_ab;
            V_info->low.key2 = pivot->key2;
            return;
            
        }

        
        while(1) {
              
            if ((V_info->a_ptr < self->g_Neigh_len[a]) \
                && (V_info->b_ptr < self->g_Neigh_len[b])){

                if (self->g_Neighbors[a][V_info->a_ptr].neighbor < self->g_Neighbors[b][V_info->b_ptr].neighbor) {
                      
                      V_info->a_ptr++;

                }
                else if (self->g_Neighbors[a][V_info->a_ptr].neighbor > self->g_Neighbors[b][V_info->b_ptr].neighbor) {
                      
                      V_info->b_ptr++;

                }
                else {

                      EDGE_ID o_ac = self->g_Neighbors[a][V_info->a_ptr].order;
                      EDGE_ID o_bc = self->g_Neighbors[b][V_info->b_ptr].order;
                      EDGE_ID c = self->g_Neighbors[b][V_info->b_ptr].neighbor;

                      o_s = o_ac;
                      v_s = b;
                      if (o_bc > o_s){
                        o_s = o_bc;
                        v_s = a;
                      }

                      if (o_s < V_info->o_ab){

                          if ((c > pivot->key2) || (c == pivot->key2)) {
                                V_info->low.key1 = V_info->o_ab;
                                V_info->low.key2 = c;
                                return;
                          }
                          
                      }
                      else{

                          if (o_s < o_min){
                            o_min = o_s;
                            v_min = v_s;
                          }
                          else if (o_s == o_min){
                            if (v_s < v_min){
                              v_min = v_s;
                            }
                          }

                      }

                      V_info->a_ptr++;
                      V_info->b_ptr++;
                }

            }
            else{
                
                  if (o_min != self->g_n_valid_edges){

                      V_info->a_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[a], 0, self->g_Neigh_len[a]-1\
                                                            , o_min, self->g_Neigh_len[a]);
                      V_info->b_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[b], 0, self->g_Neigh_len[b]-1\
                                                            , o_min, self->g_Neigh_len[b]);

                  }

                  V_info->low.key1 = o_min;
                  V_info->low.key2 = v_min;
                  return;
            }

        }
        

    }
    else {

        VERT_ID a = self->g_edges_list[V_info->o_ab][0];
        VERT_ID b = self->g_edges_list[V_info->o_ab][1];

        V_info->a_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[a], 0, self->g_Neigh_len[a]-1\
                                              , pivot->key1, self->g_Neigh_len[a]);
        V_info->b_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[b], 0, self->g_Neigh_len[b]-1\
                                              , pivot->key1, self->g_Neigh_len[b]);

        //printf("\na_len b_len %d, %d", self->g_Neigh_len[a], self->g_Neigh_len[b]);

        while (1) {
              
            //printf("\nptrs are %d, %d", V_info->a_ptr, V_info->b_ptr);
            //getchar();
            if ((V_info->a_ptr < self->g_Neigh_len[a]) \
                && (V_info->b_ptr < self->g_Neigh_len[b])){

                  if (self->g_Neighbors_e[a][V_info->a_ptr].order < self->g_Neighbors_e[b][V_info->b_ptr].order){
                        
                        EDGE_ID o_ac = self->g_Neighbors_e[a][V_info->a_ptr].order;
                        VERT_ID c = self->g_Neighbors_e[a][V_info->a_ptr].neighbor;
                        VERT_ID idx = search_Neighbors(self, b, c, 0, self->g_Neigh_len[b]-1);
                        if (idx < self->g_n_vert) {
                              
                            EDGE_ID o_bc = self->g_Neighbors[b][idx].order;
                            if (o_bc < o_ac){

                                  if ((o_ac > pivot->key1)\
                                    ||((o_ac == pivot->key1) && (b > pivot->key2))\
                                    ||((o_ac == pivot->key1) && (b == pivot->key2))\
                                    ){

                                      V_info->low.key1 = o_ac;
                                      V_info->low.key2 = b;
                                      return;

                                  }

                            }

                        }
                        //else{
                        V_info->a_ptr++;
                        //}

                  }
                  else {
                        
                        EDGE_ID o_bc = self->g_Neighbors_e[b][V_info->b_ptr].order;
                        VERT_ID c = self->g_Neighbors_e[b][V_info->b_ptr].neighbor;
                        VERT_ID idx = search_Neighbors(self, a, c, 0, self->g_Neigh_len[a]-1);
                        if (idx < self->g_n_vert) {
                              
                            EDGE_ID o_ac = self->g_Neighbors[a][idx].order;
                            if (o_ac < o_bc){
                                  if ((o_bc > pivot->key1)\
                                    ||((o_bc == pivot->key1) && (a > pivot->key2))\
                                    ||((o_bc == pivot->key1) && (a == pivot->key2))\
                                    ){

                                      V_info->low.key1 = o_bc;
                                      V_info->low.key2 = a;
                                      return;
                                  }

                            }

                        }
                        //else{
                        V_info->b_ptr++;
                        //}

                  }

            }
            else if (V_info->a_ptr < self->g_Neigh_len[a]){
                  
                //Here b_ptr has reached end. So, o_bc should be less than o_ac

                EDGE_ID o_ac = self->g_Neighbors_e[a][V_info->a_ptr].order;
                VERT_ID c = self->g_Neighbors_e[a][V_info->a_ptr].neighbor;
                VERT_ID idx = search_Neighbors(self, b, c, 0, self->g_Neigh_len[b]-1);
                if (idx < self->g_n_vert) {
                    // check if errors 
                    //if (o_bc > o_ac){
                    //    printf("\nError check obc_oac");
                    //    getchar();
                    //}

                    if ((o_ac > pivot->key1)\
                      ||((o_ac == pivot->key1) && (b > pivot->key2))\
                      ||((o_ac == pivot->key1) && (b == pivot->key2))\
                      ){
                        V_info->low.key1 = o_ac;
                        V_info->low.key2 = b;
                        return;
                    }

                }

                //else{
                V_info->a_ptr++;
                //}
                
                  
            }
            else if (V_info->b_ptr < self->g_Neigh_len[b]){
                  
                //Here b_ptr has reached end. So, o_ac should be less than o_bc

                EDGE_ID o_bc = self->g_Neighbors_e[b][V_info->b_ptr].order;
                VERT_ID c = self->g_Neighbors_e[b][V_info->b_ptr].neighbor;
                VERT_ID idx = search_Neighbors(self, a, c, 0, self->g_Neigh_len[a]-1);
                if (idx < self->g_n_vert) {
                    // check if errors 
                    //if (o_ac > o_bc){
                    //    printf("\nError check obc_oac");
                    //    getchar();
                    //}

                    if ((o_bc > pivot->key1)\
                      ||((o_bc == pivot->key1) && (a > pivot->key2))\
                      ||((o_bc == pivot->key1) && (a == pivot->key2))\
                      ){
                        V_info->low.key1 = o_bc;
                        V_info->low.key2 = a;
                        return;
                    }

                }

                //else{
                V_info->b_ptr++;
                //}
                
                  
            }
            else{
              break;
            }

              
        }

        V_info->low.key1 = self->g_n_valid_edges;
        return;

    }

}

void find_H1_cohom_next (filtration* self, coboundary_H1* V_info){


      VERT_ID a = self->g_edges_list[V_info->o_ab][0];
      VERT_ID b = self->g_edges_list[V_info->o_ab][1];

      //printf("\nLOW of %d is (%d, %d)", V_info->o_ab, V_info->low.key1, V_info->low.key2);
      //

      //printf("\nCurrent c, ac, c, bc %d, %d, %d, %d", self->g_Neighbors[a][V_info->a_ptr].neighbor\
      //                                , self->g_Neighbors[a][V_info->a_ptr].order\
      //                                , self->g_Neighbors[b][V_info->b_ptr].neighbor\
      //                                , self->g_Neighbors[b][V_info->b_ptr].order\
      //                                );

      if (V_info->o_ab == V_info->low.key1){

          V_info->a_ptr++;
          V_info->b_ptr++;
          //printf("\naptr, max, bptr, max %d, %d, %d, %d", V_info->a_ptr\
          //                                              , self->g_Neigh_len[a]\
          //                                              , V_info->b_ptr\
          //                                              , self->g_Neigh_len[b]);
          //  
          //printf("\nstarting loop1");
          //EDGE_ID o_min = self->g_n_valid_edges;
          //EDGE_ID v_min;

          while (1){
                
                //printf("\naptr, max, bptr, max %d, %d, %d, %d", V_info->a_ptr\
                //                                        , self->g_Neigh_len[a]\
                //                                        , V_info->b_ptr\
                //                                        , self->g_Neigh_len[b]);
                
                if ((V_info->a_ptr < self->g_Neigh_len[a]) \
                    && (V_info->b_ptr < self->g_Neigh_len[b])){

                    if (self->g_Neighbors[a][V_info->a_ptr].neighbor < self->g_Neighbors[b][V_info->b_ptr].neighbor){
                          
                          V_info->a_ptr++;
                    }
                    else if (self->g_Neighbors[a][V_info->a_ptr].neighbor > self->g_Neighbors[b][V_info->b_ptr].neighbor){
                          
                          V_info->b_ptr++;
                    }
                    else{


                          EDGE_ID o_ac = self->g_Neighbors[a][V_info->a_ptr].order;
                          EDGE_ID o_bc = self->g_Neighbors[b][V_info->b_ptr].order;
                          EDGE_ID c = self->g_Neighbors[b][V_info->b_ptr].neighbor;
                          //printf("\nINSIDE FOUND NEXT COMMON %d", c);
                          EDGE_ID o_s = o_ac;
                          EDGE_ID v_s = b;
                          if (o_bc > o_ac) {
                              o_s = o_bc;
                              v_s = a;
                          }
                          
                          if (o_s < V_info->low.key1) {
                              
                                V_info->low.key2 = c;
                                //printf("\nReturn 1 ");
                                return;

                          }

                          //else if (o_s < o_min){
                          //      
                          //      o_min = o_s;
                          //      v_min = v_s;
                          //      
                          //}
                          //else if ((o_s == o_min) && (v_s < v_min)){

                          //      v_min = v_s;

                          //}

                          V_info->a_ptr++;
                          V_info->b_ptr++;


                    }
                    

                }
                else {
                    break;
                }

                
          }
          
          //printf("\nended loop1");

          //printf("\nsearching ptrs");

          V_info->a_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[a], 0, self->g_Neigh_len[a] - 1\
                                                , V_info->o_ab, self->g_Neigh_len[a]);
          V_info->b_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[b], 0, self->g_Neigh_len[b] - 1\
                                                , V_info->o_ab, self->g_Neigh_len[b]);


          //if (o_min < self->g_n_valid_edges){
          //      
          //      V_info->a_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[a], 0, self->g_Neigh_len[a] - 1\
          //                                            , o_min, self->g_Neigh_len[a]);
          //      V_info->b_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[b], 0, self->g_Neigh_len[b] - 1\
          //                                            , o_min, self->g_Neigh_len[b]);


          //}

          //V_info->low.key1 = o_min;
          //V_info->low.key2 = v_min;

          //printf("\nReturn 3 ");
          
          //return;
            
      }

      // Here 
      //V_info->low.key1 > V_info->o_ab


      //printf("\norders are ac: %d, bc: %d", self->g_Neighbors_e[a][V_info->a_ptr].order\
      //                          , self->g_Neighbors_e[b][V_info->b_ptr].order);

     if ((V_info->a_ptr < self->g_Neigh_len[a]) \
         && (V_info->b_ptr < self->g_Neigh_len[b])){

            if ((self->g_Neighbors_e[a][V_info->a_ptr].order < self->g_Neighbors_e[b][V_info->b_ptr].order)){

                 V_info->a_ptr++;

            }
            else{
                 V_info->b_ptr++;
            }

     }
     else{

            if (V_info->a_ptr < self->g_Neigh_len[a]){
                        V_info->a_ptr++;
            }
            else{
                        V_info->b_ptr++;
            }

     }


      //printf("\nstarting loop2");
      while (1){
            
            //printf("\naptr, max, bptr, max %d, %d, %d, %d", V_info->a_ptr\
            //                                        , self->g_Neigh_len[a]\
            //                                        , V_info->b_ptr\
            //                                        , self->g_Neigh_len[b]);
            if ((V_info->a_ptr < self->g_Neigh_len[a]) \
                && (V_info->b_ptr < self->g_Neigh_len[b])){
                
                 if (self->g_Neighbors_e[a][V_info->a_ptr].order < self->g_Neighbors_e[b][V_info->b_ptr].order) {

                      EDGE_ID o_ac = self->g_Neighbors_e[a][V_info->a_ptr].order;
                      EDGE_ID c = self->g_Neighbors_e[a][V_info->a_ptr].neighbor;
                      VERT_ID idx = search_Neighbors(self, b, c, 0, self->g_Neigh_len[b]-1);

                      if (idx < self->g_n_vert) {
                            
                          EDGE_ID o_bc = self->g_Neighbors[b][idx].order;
                          if (o_bc < o_ac){
                                V_info->low.key1 = o_ac;
                                V_info->low.key2 = b;
                                //printf("\nReturn 4 ");
                                return;

                          }

                      }

                      V_info->a_ptr++;
                 }
                 else{
                      EDGE_ID o_bc = self->g_Neighbors_e[b][V_info->b_ptr].order;
                      EDGE_ID c = self->g_Neighbors_e[b][V_info->b_ptr].neighbor;
                      VERT_ID idx = search_Neighbors(self, a, c, 0, self->g_Neigh_len[a]-1);

                      if (idx < self->g_n_vert) {
                            
                          EDGE_ID o_ac = self->g_Neighbors[a][idx].order;
                          if (o_ac < o_bc){
                                V_info->low.key1 = o_bc;
                                V_info->low.key2 = a;
                                //printf("\nReturn 5 ");
                                return;

                          }

                      }

                      V_info->b_ptr++;

                 }


            }
            else if (V_info->a_ptr < self->g_Neigh_len[a]){

                      EDGE_ID o_ac = self->g_Neighbors_e[a][V_info->a_ptr].order;
                      EDGE_ID c = self->g_Neighbors_e[a][V_info->a_ptr].neighbor;

                      VERT_ID idx = search_Neighbors(self, b, c, 0, self->g_Neigh_len[b]-1);
                      if (idx < self->g_n_vert) {
                            
                          //EDGE_ID o_bc = self->g_Neighbors[b][idx];
                          // SHOULD HAVE THE NEED TO CHECK
                          //if (o_bc < o_ac){
                          V_info->low.key1 = o_ac;
                          V_info->low.key2 = b;
                          //printf("\nReturn 5 ");
                          return;

                          //}

                      }

                      V_info->a_ptr++;
                  
            }
            else if (V_info->b_ptr < self->g_Neigh_len[b]){

                      EDGE_ID o_bc = self->g_Neighbors_e[b][V_info->b_ptr].order;
                      EDGE_ID c = self->g_Neighbors_e[b][V_info->b_ptr].neighbor;

                      VERT_ID idx = search_Neighbors(self, a, c, 0, self->g_Neigh_len[a]-1);
                      if (idx < self->g_n_vert) {
                            
                          //EDGE_ID o_bc = self->g_Neighbors[b][idx];
                          // SHOULD HAVE THE NEED TO CHECK
                          //if (o_bc < o_ac){
                          V_info->low.key1 = o_bc;
                          V_info->low.key2 = a;
                          //printf("\nReturn 6 ");
                          return;

                          //}

                      }

                      V_info->b_ptr++;
                  
            }
            else{
              break;
            }

      }
      //printf("\nended loop2");

      V_info->low.key1 = self->g_n_valid_edges;
      //printf("\nReturn 7 ");
      return;

}



EDGE_ID bin_search_min_geq_Ne(Neighbors* arr, VERT_ID l, VERT_ID r, VERT_ID x, EDGE_ID MAX){

    if (arr[r].order < x){
      return MAX;
    }

    if (arr[l].order > x){
      return l;
    }

    VERT_ID mid = l + (r-l)/2;

    if (arr[mid].order < x){
        
        l = mid + 1;
        if ((arr[l].order > x) || (arr[l].order == x)){
          return l;
        }
        bin_search_min_geq_Ne(arr, l, r, x, MAX);

    }
    else{
        r = mid;
        if (arr[r].order == x) return r;
        bin_search_min_geq_Ne(arr, l , r, x, MAX);

    }
     
}

EDGE_ID bin_search_min_geq_N(Neighbors* arr, VERT_ID l, VERT_ID r, VERT_ID x, EDGE_ID MAX){

    if (arr[r].neighbor < x){
      return MAX;
    }

    if (arr[l].neighbor > x){
      return l;
    }

    VERT_ID mid = l + (r-l)/2;

    if (arr[mid].neighbor < x){
        
        l = mid + 1;
        if ((arr[l].neighbor > x) || (arr[l].neighbor == x)){
          return l;
        }
        bin_search_min_geq_N(arr, l, r, x, MAX);

    }
    else{
        r = mid;
        if (arr[r].neighbor == x) return r;
        bin_search_min_geq_N(arr, l , r, x, MAX);

    }
     
}



void find_H2_cohom_low (filtration* self, coboundary_H2* V_info){


      V_info->c_ptr = 0;

      int flag = H2_case1 (self, V_info);
      if (H2_case1(self, V_info)){
        return;
      }

      
      VERT_ID a = self->g_edges_list[V_info->triangle.key1][0];
      VERT_ID b = self->g_edges_list[V_info->triangle.key1][1];


      V_info->a_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[a], 0, self->g_Neigh_len[a]-1\
                                              , V_info->triangle.key1, self->g_Neigh_len[a]);
      
      V_info->b_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[b], 0, self->g_Neigh_len[b]-1\
                                              , V_info->triangle.key1, self->g_Neigh_len[b]);


      V_info->a_ptr++;
      V_info->b_ptr++;

      H2_case2(self, V_info);


}



void find_H2_cohom_next (filtration* self, coboundary_H2* V_info){
    
      //clock_gettime(CLOCK_MONOTONIC, &(self->g_start_wall_clock));

      //printf("\nfinding H2 next");
      
      EDGE_ID o_ab = V_info->triangle.key1;
      VERT_ID c = V_info->triangle.key2;
      
      VERT_ID a = self->g_edges_list[o_ab][0];
      VERT_ID b = self->g_edges_list[o_ab][1];

      EDGE_ID o_ad, o_bd;
      VERT_ID idxa, idxb, d;

      int flag = 0;

      //if (V_info->low.key1 == o_ab){ 
      if (V_info->vertex == 0){ 

            V_info->c_ptr++;

            if (H2_case1(self, V_info)){
              return;
            }

            // Here means that we did not return and 
            // not a_ptr and b_ptr are at o_ab
            // So, both need to be incremented

            V_info->a_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[a], 0, self->g_Neigh_len[a]-1\
                                                 , V_info->triangle.key1, self->g_Neigh_len[a]);

            V_info->b_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[b], 0, self->g_Neigh_len[b]-1\
                                                    , V_info->triangle.key1, self->g_Neigh_len[b]);
            V_info->a_ptr++;
            V_info->b_ptr++;
            flag = 1;

      }

      if (!flag){

          if (V_info->vertex == 1){
                V_info->a_ptr++;
          }
          else if (V_info->vertex == 2){
                V_info->b_ptr++;
          }
          else if (V_info->vertex == 3){
                V_info->c_ptr++;
          }
            
            
      }


      H2_case2(self, V_info);


}


void find_H2_cohom_greater (filtration* self, coboundary_H2* V_info, simplex* pivot){

    //if (self->g_p_flag){
    //    printf("\nfinding H2 greater than (%d, %d) for (%d, %d)", pivot->key1, pivot->key2
    //                                                            , V_info->triangle.key1\
    //                                                            , V_info->triangle.key2);
    //    getchar();
    //}

    //EDGE_ID o_ab;
    VERT_ID c, a, b;


    if (pivot->key1 < V_info->triangle.key1){
        
          // Find first low of o_ab
          find_H2_cohom_low(self, V_info);
          return;

    }
    else if (pivot->key1 == V_info->triangle.key1){

          //o_ab = V_info->triangle.key1;
          
          VERT_ID c = V_info->triangle.key2;

          //a = self->g_edges_list[o_ab][0];
          //b = self->g_edges_list[o_ab][1];

          V_info->c_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[c], 0, self->g_Neigh_len[c]-1\
                                                  , pivot->key2, self->g_Neigh_len[c]);

          if (self->g_Neighbors_e[c][V_info->c_ptr].order == pivot->key2){
              
                V_info->low = *pivot;
                V_info->vertex= 0;
                return;

          }

          if (H2_case1(self, V_info)){
                return;
          }
          

          // Here means that we did not return and 
          // not a_ptr and b_ptr are at o_ab
          // So, both need to be incremented

          a = self->g_edges_list[V_info->triangle.key1][0];
          b = self->g_edges_list[V_info->triangle.key1][1];

          V_info->a_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[a], 0, self->g_Neigh_len[a]-1\
                                               , V_info->triangle.key1, self->g_Neigh_len[a]);

          V_info->b_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[b], 0, self->g_Neigh_len[b]-1\
                                                  , V_info->triangle.key1, self->g_Neigh_len[b]);

          V_info->a_ptr++;
          V_info->b_ptr++;
                

    }
    else{


          c = V_info->triangle.key2;

          a = self->g_edges_list[V_info->triangle.key1][0];
          b = self->g_edges_list[V_info->triangle.key1][1];

          V_info->a_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[a], 0, self->g_Neigh_len[a]-1\
                                               , pivot->key1, self->g_Neigh_len[a]);

          V_info->b_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[b], 0, self->g_Neigh_len[b]-1\
                                                  , pivot->key1, self->g_Neigh_len[b]);

          V_info->c_ptr = bin_search_min_geq_Ne(self->g_Neighbors_e[c], 0, self->g_Neigh_len[c]-1\
                                                  , pivot->key1, self->g_Neigh_len[c]);


    }

    while (1){

          H2_case2(self, V_info);

          if (((V_info->low.key1 == pivot->key1) && (V_info->low.key2 > pivot->key2))\
              ||(V_info->low.key1 > pivot->key1) || (V_info->low.key1 == self->g_n_valid_edges)\
              ||((V_info->low.key1 == pivot->key1) && (V_info->low.key2 == pivot->key2)) ){
              break;
          }

          if (V_info->vertex == 1){
                
                V_info->a_ptr++;
          }
          else if (V_info->vertex == 2){
                
                V_info->b_ptr++;
          }
          else if (V_info->vertex == 3){
                
                V_info->c_ptr++;
          }

    }


}

void add_H2_pivot(filtration* self, simplex* simp, int add_to_V , simplex bndry){
    

    EDGE_ID red_col;
    if (add_to_V){
          
          red_col = self->g_V_col_indices_ptr;
          if (self->g_V_col_indices_ptr+1 == self->g_V_col_indices_max){
                
                self->g_V_col_indices_max += 100;
                self->g_V_col_indices = (EDGE_ID*)realloc(self->g_V_col_indices
                                                      , self->g_V_col_indices_max*sizeof(EDGE_ID));
          
          }


          self->g_V_col_indices[self->g_V_col_indices_ptr] = self->g_V_sparse_beg_ptr;
          self->g_V_col_indices[self->g_V_col_indices_ptr+1] = self->g_V_sparse_end_ptr;

          self->g_V_col_indices_ptr++;


          
    }
    else{
          red_col = self->g_n_valid_edges;
    }


    // ADDING THE LOW
    EDGE_ID max_edge = simp->key1;
    EDGE_ID v3 = simp->key2;

    //printf("\nAdding pivot (%d, %d) for (%d,%d)", simp->key1, simp->key2, bndry.key1, bndry.key2);

    //FILE* fp = fopen("pivotsH2.txt", "a");
    //fprintf(fp, "%d, %d, %d, %d\n", simp->key1, simp->key2, bndry.key1, bndry.key2);
    //fclose(fp);

    if (self->g_H2_cohom_pivots_len[simp->key1] == self->g_H2_cohom_pivots_max_len[simp->key1]){
          
          self->g_H2_cohom_pivots_max_len[simp->key1] += 5;
          self->g_H2_cohom_pivots[simp->key1] = (H2_cohom_pivots*)realloc( \
                          self->g_H2_cohom_pivots[simp->key1] \
                          , self->g_H2_cohom_pivots_max_len[simp->key1]*sizeof(H2_cohom_pivots));
          //self->g_cohom_ALL_pivots_len += 5;

    }
          


    EDGE_ID old_ptr = self->g_H2_cohom_pivots_len[simp->key1];
    EDGE_ID new_ptr = self->g_H2_cohom_pivots_len[simp->key1];

    while (old_ptr){
          
          old_ptr--;
          
          if (self->g_H2_cohom_pivots[simp->key1][old_ptr].key2 > simp->key2){

                self->g_H2_cohom_pivots[simp->key1][new_ptr--] = self->g_H2_cohom_pivots[simp->key1][old_ptr];
                continue;

          }
          break;

    }

    self->g_H2_cohom_pivots[simp->key1][new_ptr].key2 = simp->key2;
    self->g_H2_cohom_pivots[simp->key1][new_ptr].col_idx = red_col;

    self->g_H2_cohom_pivots[simp->key1][new_ptr].bndry = bndry;

    self->g_H2_cohom_pivots_len[simp->key1]++;

    
    // PERS PAIRS
    // Add non-zero barcodes
        
    PAR birth = self->g_edge_parameter[bndry.key1];
    PAR death = self->g_edge_parameter[simp->key1];
    if (birth != death){

          //if (self->g_p_flag){
          //  printf("\nPair (%d, %d), (%d, %d)", bndry.key1, bndry.key2\
          //                                    , simp->key1, simp->key2);
          //  printf("\nError?");
          //  getchar();
          //}
          
          if (self->g_H2_pers_pairs_len+2 == self->g_H2_pers_pairs_max_len){
                self->g_H2_pers_pairs_max_len += 1000;
                self->g_H2_pers_pairs = (PAR*)realloc(self->g_H2_pers_pairs, self->g_H2_pers_pairs_max_len*sizeof(PAR));
          
          }
          self->g_H2_pers_pairs[self->g_H2_pers_pairs_len++] = birth;
          self->g_H2_pers_pairs[self->g_H2_pers_pairs_len++] = death;
          
    }

}


// A recursive binary search function. It returns 
// location of x in given array arr[l..r] is present, 
// otherwise -1 
EDGE_ID search_H2_cohom_pivots(H2_cohom_pivots* arr, EDGE_ID l, EDGE_ID r, EDGE_ID key2, EDGE_ID max) 
{ 
    if (r >= l) { 
        EDGE_ID mid = l + (r - l) / 2; 

        if (arr[mid].key2 == key2) 
            return mid; 
  
        // If element is smaller than mid, then 
        // it can only be present in left subarray 
        if (arr[mid].key2 > key2) 
        {

          /// PRECAUTIONARY: CAN REMOVE LATER
            if (!mid){
                return max; 
                printf("\nMID 0 WILL GIVE ERROR FOR UNSIGNED NEXT");
                getchar();
            }
          ///////////////////
            return search_H2_cohom_pivots(arr, l, mid - 1, key2, max); 
        }
  
        // Else the element can only be present 
        // in right subarray 
        return search_H2_cohom_pivots(arr, mid + 1, r, key2, max); 
    } 
  
    // We reach here when element is not 
    // present in array 
    //printf("\nNOT FOUND");
    return max; 
} 


// Reduces with complex in parallel

void* reduce_with_complex_H0(void* arg){
      
      filtration* self = arg;

      pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);

      pthread_mutex_lock(&(self->g_thread_lock));

      int tid = ++self->g_thread_id;



      for (;;){

          self->g_sleeping_threads++;
          
          if (self->g_sleeping_threads == self->g_cpu_count)
              pthread_cond_signal(&(self->g_start_boss));

          pthread_cond_wait(&(self->g_start_workers), &(self->g_thread_lock));

          if (self->g_delete_threads){
            //printf("\nexiting from thread %d", tid);
            pthread_mutex_unlock(&(self->g_thread_lock));
            pthread_exit(NULL);
          }

          self->g_sleeping_threads--;

          pthread_mutex_unlock(&(self->g_thread_lock));

          for (int ws_counter = self->g_jobs[tid - 1]; ws_counter < self->g_jobs[tid]; ws_counter++){


              boundary_H0_ws* this_ws = self->g_R_ws_H0_info + ws_counter;

              EDGE_ID* orig = self->g_R_ws_H0[ws_counter] + this_ws->original*this_ws->max_len;

              this_ws->flag_red_w_complex = 0;
              this_ws->flag_append_to_complex = 1;

              EDGE_ID idx = self->g_pivots_H0[this_ws->pivot];

              while(idx){

                    //reduced_col = self->g_pivots[self->g_dim_now][idx].red_col;
                    //reduced_col = self->g_pivots_H0[idx];


                    EDGE_ID red_start_idx = self->g_R_col_indices_H0[idx];

                    EDGE_ID red_finish_idx = self->g_R_col_indices_H0[idx+1];

                    EDGE_ID red_len = red_finish_idx - red_start_idx;

                    if ((this_ws->len + red_len) > this_ws->max_len){
                          
                        if (this_ws->original){
                              
                                for (EDGE_ID it=0; it < this_ws->len; it++){

                                      orig[it] = orig[it + this_ws->max_len];
                                      
                                }

                                this_ws->original = 0;

                        }

                        this_ws->max_len = this_ws->len + red_len + 100;

                        pthread_mutex_lock(&(self->g_thread_lock));

                        self->g_R_ws_H0[ws_counter] = (EDGE_ID*)realloc(self->g_R_ws_H0[ws_counter]\
                                                                          , 2*this_ws->max_len*sizeof(EDGE_ID));

                        pthread_mutex_unlock(&(self->g_thread_lock));

                        orig = self->g_R_ws_H0[ws_counter];


                          
                    }


                    EDGE_ID* scratch = self->g_R_ws_H0[ws_counter] + (1-this_ws->original)*this_ws->max_len;



                    EDGE_ID orig_ptr = 0;
                    EDGE_ID red_ptr = red_start_idx;
                    EDGE_ID scratch_ptr = 0;


                    while ((orig_ptr < this_ws->len) && (red_ptr < red_finish_idx)){

                        if (orig[orig_ptr] < self->g_R_sparse_H0[red_ptr]){

                              scratch[scratch_ptr++] = orig[orig_ptr++];

                        }
                        else if (orig[orig_ptr] > self->g_R_sparse_H0[red_ptr]){

                              scratch[scratch_ptr++] = self->g_R_sparse_H0[red_ptr++];

                        }
                        else{
                              orig_ptr++;
                              red_ptr++;
                        }

                    }

                    while (orig_ptr < this_ws->len){

                        scratch[scratch_ptr++] = orig[orig_ptr++];

                    }

                    while (red_ptr < red_finish_idx){
                          
                        scratch[scratch_ptr++] = self->g_R_sparse_H0[red_ptr++];
                          
                    }


                    this_ws->len = scratch_ptr;


                    if (!this_ws->len){

                        //idx = self->g_n_reduced_simplex[self->g_dim_now];
                        //idx = -1;
                        break;

                    }
                    else{
                    

                        this_ws->original = 1 - this_ws->original;

                        orig = self->g_R_ws_H0[ws_counter] + this_ws->original*this_ws->max_len;

                        this_ws->pivot = orig[this_ws->len-1];
                        
                        idx = self->g_pivots_H0[this_ws->pivot];

                    }
                    

              }


          }

          pthread_mutex_lock(&(self->g_thread_lock));

          self->g_processed_threads++;

            
      }


}


void allocate_jobs(filtration* self, int ws_size){
      
     int x = (int)(ws_size/self->g_cpu_count);
     int y = (ws_size % self->g_cpu_count);

     self->g_jobs[0] = 0;

     for (int i = 1; i < self->g_cpu_count+1; i++){
          
          if (i < y + 1)
            self->g_jobs[i] = self->g_jobs[i-1] + x + 1;
          else
            self->g_jobs[i] = self->g_jobs[i-1] + x;

     }
      
}

void reduce_ws_H0(filtration* self){


      //if (self->g_n_reduced_simplex_H0 > 0){

        
            self->g_processed_threads = 0;


            pthread_cond_broadcast(&(self->g_start_workers));


            while (self->g_processed_threads != self->g_cpu_count){
                  
                  pthread_cond_wait(&(self->g_start_boss) \
                                  ,&(self->g_thread_lock));
            }



      //}


      reduce_with_self_H0( \
                            self \
                            );

      int count_valid = 0;

      for (int ws_counter=0; ws_counter < self->g_ws_counter; ws_counter++){

            if (!self->g_R_ws_H0_info[ws_counter].len){continue;}

            if (self->g_R_ws_H0_info[ws_counter].flag_append_to_complex){

                  update_R_H0(self \
                            , ws_counter
                            );
                  continue;
                    
            }


            // Swap R
            EDGE_ID* temp = self->g_R_ws_H0[count_valid];
            self->g_R_ws_H0[count_valid] = self->g_R_ws_H0[ws_counter];
            self->g_R_ws_H0[ws_counter] = temp;

            // Swap R info
            boundary_H0_ws temp2 = self->g_R_ws_H0_info[count_valid];
            self->g_R_ws_H0_info[count_valid] = self->g_R_ws_H0_info[ws_counter];
            self->g_R_ws_H0_info[ws_counter] = temp2;


            // At this point, this has to be a non-zero column
            self->g_R_ws_H0_info[count_valid].flag_non_empty = 1;

            count_valid += 1;

      }

      self->g_ws_counter = count_valid;

      //if (dim)
      //  self->g_H0_MAX = self->g_n_reduced_simplex[dim];

}


void reduce_with_self_H0( \
                      filtration* self \
                      ){


    int m;
    EDGE_ID orig_ptr, scratch_ptr, m_ptr, idx;
    EDGE_ID *orig, *scratch, *original_m;
    

    for (int ws_counter=0; ws_counter < self->g_ws_counter; ws_counter++){

        boundary_H0_ws* this_ws = self->g_R_ws_H0_info + ws_counter;

        // If the simplex has already been reduced to 0
      // then continue
        if (!this_ws->len){ 
          this_ws->flag_append_to_complex = 0;
          continue;

        }



        m = 0;
        while (m < ws_counter){

            boundary_H0_ws* m_ws = self->g_R_ws_H0_info + m;

            if (!m_ws->len){
                m++;
                continue;
            }

            if (m_ws->pivot > this_ws->pivot){
                  
                  if (m_ws->flag_red_w_complex){
                        
                        this_ws->flag_append_to_complex = 0;
                        break;
                  }
                  m++;
                  continue;
                  
            }


            if (m_ws->pivot < this_ws->pivot){
                    m++;
                    continue;
            }

            if (!m_ws->flag_append_to_complex){
                    m++;
                    continue;
            }

            orig = self->g_R_ws_H0[ws_counter] + this_ws->original*this_ws->max_len;

            if ((this_ws->len + m_ws->len) > this_ws->max_len){

                if (this_ws->original){
                      
                    for (EDGE_ID it = 0; it < this_ws->len; it++){
                          
                          orig[it] = orig[it + this_ws->max_len];
                    }
                       
                    this_ws->original = 0;

                }

                this_ws->max_len = this_ws->len + m_ws->len + 100;

                self->g_R_ws_H0[ws_counter] = (EDGE_ID*)realloc(self->g_R_ws_H0[ws_counter]\
                                                        , 2*this_ws->max_len*sizeof(EDGE_ID));

                orig = self->g_R_ws_H0[ws_counter];

                
            }

            scratch = self->g_R_ws_H0[ws_counter] + (1-this_ws->original)*this_ws->max_len;

            original_m = self->g_R_ws_H0[m] + m_ws->original*m_ws->max_len;

            
            // Store the result in scratch

            orig_ptr = 0;
            scratch_ptr = 0;
            m_ptr = 0;

            while ((orig_ptr < this_ws->len) && (m_ptr < m_ws->len)){
                  
                  if (orig[orig_ptr] < original_m[m_ptr]){
                        
                        scratch[scratch_ptr++]  = orig[orig_ptr++];
                  }
                  else if (orig[orig_ptr] > original_m[m_ptr]){
                        
                        scratch[scratch_ptr++]  = original_m[m_ptr++];
                  }
                  else{
                        orig_ptr++;
                        m_ptr++;
                  }


            }

            while (orig_ptr < this_ws->len){
                
                scratch[scratch_ptr++]  = orig[orig_ptr++];

            }

            while (m_ptr < m_ws->len){
                
                scratch[scratch_ptr++]  = original_m[m_ptr++];

            }


            this_ws->len = scratch_ptr;

            if (!scratch_ptr){
                  
                  this_ws->flag_append_to_complex = 0;
                  break;
                  
            }


            this_ws->pivot = scratch[scratch_ptr - 1];

            this_ws->original = 1 - this_ws->original;


            //if (self->g_n_reduced_simplex_H0){

                idx = self->g_pivots_H0[this_ws->pivot];
                // If the pivot is in red complex, then this has to be reduced w/ complex
                //if (idx != self->g_n_reduced_simplex[self->g_dim_now]){
                if (idx){
                      
                      this_ws->flag_red_w_complex = 1;
                      this_ws->flag_append_to_complex = 0;
                      break;

                }

            //}

            m = 0;

        }//End of m loop

    }

}//End of red_ws_w_self_single


void update_R_H0(filtration* self, int ws_counter){


      boundary_H0_ws* this_ws = self->g_R_ws_H0_info + ws_counter;

      EDGE_ID* orig = self->g_R_ws_H0[ws_counter] + this_ws->original*this_ws->max_len;


      // Check space for R Sparse
      if ((this_ws->len + self->g_R_sparse_ptr_H0) > self->g_R_sparse_max_H0 ){

          self->g_R_sparse_max_H0 = this_ws->len + self->g_R_sparse_ptr_H0 + 1000;

          self->g_R_sparse_H0 = (EDGE_ID*)realloc(self->g_R_sparse_H0\
                                                , self->g_R_sparse_max_H0*sizeof(EDGE_ID));
            
      }

      // Check space for R col indices
      if ((self->g_R_col_indices_ptr_H0 + 3) > self->g_R_col_indices_max_H0){
            
            self->g_R_col_indices_max_H0 += 100;
            self->g_R_col_indices_H0 = (EDGE_ID*)realloc(self->g_R_col_indices_H0\
                                                       , self->g_R_col_indices_max_H0*sizeof(EDGE_ID));
      }



      self->g_pivots_H0[this_ws->pivot] = self->g_R_col_indices_ptr_H0;

      self->g_R_col_indices_H0[self->g_R_col_indices_ptr_H0++] = self->g_R_sparse_ptr_H0;


      for (EDGE_ID j=0; j < this_ws->len; j++){

          self->g_R_sparse_H0[self->g_R_sparse_ptr_H0++] = orig[j];

      }

      self->g_R_col_indices_H0[self->g_R_col_indices_ptr_H0++] = self->g_R_sparse_ptr_H0;


      // Update edges with pivots for H0 to be used in clearing algo
      self->g_edges_with_pivots_H0[this_ws->cob] = 1;
      



}



//////////////////////////////////////////////////////////
// MERGE SORT ALGORITHMS
//////////////////////////////////////////////////////////

// Merges two subarrays of arr[]. 
// First subarray is arr[l..m] 
// Second subarray is arr[m+1..r] 
void merge(PAR* arr, EDGE_ID** aux, EDGE_ID l, EDGE_ID m, EDGE_ID r) 
{ 
    EDGE_ID i, j, k; 
    EDGE_ID n1 = m - l + 1; 
    EDGE_ID n2 =  r - m; 
    //printf("\nn1, n2: %u, %u", n1, n2);
  
    /* create temp arrays */
    PAR *L, *R;
    L = (PAR*)malloc(n1*sizeof(PAR));
    R = (PAR*)malloc(n2*sizeof(PAR));

    /* create temp arrays */
    EDGE_ID** L_aux;
    EDGE_ID** R_aux;
    L_aux = (EDGE_ID**)malloc(n1*sizeof(EDGE_ID*));
    R_aux = (EDGE_ID**)malloc(n2*sizeof(EDGE_ID*));

    
    //int L[n1], R[n2]; 
  
    /* Copy data to temp arrays L[] and R[] */
    for (i = 0; i < n1; i++){ 
        L[i] = arr[l + i]; 
        L_aux[i] = aux[l + i]; 
    }

    for (j = 0; j < n2; j++) {
        R[j] = arr[m + 1+ j]; 
        R_aux[j] = aux[m + 1+ j]; 
    }
  
    /* Merge the temp arrays back into arr[l..r]*/
    i = 0; // Initial index of first subarray 
    j = 0; // Initial index of second subarray 
    k = l; // Initial index of merged subarray 


    while (i < n1 && j < n2) 
    { 

          if (L[i] <= R[j]) 
          { 
              arr[k] = L[i]; 
	            aux[k] = L_aux[i];
              i++; 
          } 
          else
          { 
              arr[k] = R[j]; 
              aux[k] = R_aux[j]; 
              j++; 
          } 

          k++;

    }

  
    /* Copy the remaining elements of L[], if there 
       are any */
    while (i < n1) 
    { 
        arr[k] = L[i]; 
        aux[k] = L_aux[i]; 
        i++; 
        k++; 
    } 
  
    /* Copy the remaining elements of R[], if there 
       are any */
    while (j < n2) 
    { 
        arr[k] = R[j]; 
        aux[k] = R_aux[j]; 
        j++; 
        k++; 
    } 

    free(L);
    free(R);
    free(L_aux);
    free(R_aux);
} 
  
/* l is for left index and r is right index of the 
   sub-array of arr to be sorted */
void mergeSort(PAR* arr, EDGE_ID** aux, EDGE_ID l, EDGE_ID r) 
{ 
    if (l < r) 
    { 
        // Same as (l+r)/2, but avoids overflow for 
        // large l and h 
        EDGE_ID m = l+(r-l)/2; 
  
        // Sort first and second halves 
        mergeSort(arr, aux, l, m); 
        mergeSort(arr, aux, m+1, r); 
  
        merge(arr, aux, l, m, r); 
    } 
} 





void add_H1_pivot(filtration* self, simplex* simp, int add_to_V, EDGE_ID bndry){


    EDGE_ID red_col;

    if (add_to_V){
          
          red_col = self->g_V_col_indices_ptr;
          if (self->g_V_col_indices_ptr+1 == self->g_V_col_indices_max){
                
                self->g_V_col_indices_max += 100;
                self->g_V_col_indices = (EDGE_ID*)realloc(self->g_V_col_indices
                                                      , self->g_V_col_indices_max*sizeof(EDGE_ID));
          
          }


          self->g_V_col_indices[self->g_V_col_indices_ptr] = self->g_V_sparse_beg_ptr;
          self->g_V_col_indices[self->g_V_col_indices_ptr+1] = self->g_V_sparse_end_ptr;

          self->g_V_col_indices_ptr++;


          
    }
    else{
          red_col = self->g_n_valid_edges;
    }
    

    // ADDING THE LOW
    EDGE_ID max_edge = simp->key1;
    EDGE_ID v3 = simp->key2;

    //printf("\nAdding pivot (%d, %d) for %d", simp->key1, simp->key2, bndry);

    //FILE* fp = fopen("pivotsH1.txt", "a");
    //fprintf(fp, "%d, %d, %d\n", simp->key1, simp->key2, bndry);
    //fclose(fp);

    if (self->g_H1_cohom_pivots_len[simp->key1] == self->g_H1_cohom_pivots_max_len[simp->key1]){
          
          self->g_H1_cohom_pivots_max_len[simp->key1] += 5;
          self->g_H1_cohom_pivots[simp->key1] = (H1_cohom_pivots*)realloc( \
                          self->g_H1_cohom_pivots[simp->key1] \
                          , self->g_H1_cohom_pivots_max_len[simp->key1]*sizeof(H1_cohom_pivots));
          //self->g_cohom_ALL_pivots_len += 5;

    }
          


    EDGE_ID old_ptr = self->g_H1_cohom_pivots_len[simp->key1];
    EDGE_ID new_ptr = self->g_H1_cohom_pivots_len[simp->key1];

    while (old_ptr){
          
          old_ptr--;
          
          if (self->g_H1_cohom_pivots[simp->key1][old_ptr].key2 > simp->key2){

                self->g_H1_cohom_pivots[simp->key1][new_ptr--] = self->g_H1_cohom_pivots[simp->key1][old_ptr];
                continue;

          }
          break;

    }

    self->g_H1_cohom_pivots[simp->key1][new_ptr].key2 = simp->key2;
    self->g_H1_cohom_pivots[simp->key1][new_ptr].col_idx = red_col;

    self->g_H1_cohom_pivots[simp->key1][new_ptr].bndry = bndry;

    self->g_H1_cohom_pivots_len[simp->key1]++;


    // PROCESSING TO HELP WITH PERS PAIRS EXTRACTION

    // Note that this edge is a pivot of a 2-simplex
    self->g_edges_with_pivots_H0[bndry] = 2;

    // Add non-zero barcodes
    if (bndry != simp->key1){
        
          PAR birth = self->g_edge_parameter[bndry];
          PAR death = self->g_edge_parameter[simp->key1];
          if (birth != death){

                //if (birth > death){
                //    printf("\nError %d at pair (%lf, %lf)", bndry, birth, death);
                //    getchar();
                //  
                //}

                //if (birth < 0.5295){
                //    printf("\nAdding %d at pair (%lf, %lf)", bndry, birth, death);
                //    getchar();
                //}
                //

                //printf("\nAdding %.16f, %.16f for edge %d with pivot (%d, %d)"\
                //                                        , birth\
                //                                        , death\
                //                                        , bndry\
                //                                        , simp->key1\
                //                                        , simp->key2\
                //                                        );
                //
                if (self->g_H1_pers_pairs_len+2 == self->g_H1_pers_pairs_max_len){
                      self->g_H1_pers_pairs_max_len += 1000;
                      self->g_H1_pers_pairs = (PAR*)realloc(self->g_H1_pers_pairs, self->g_H1_pers_pairs_max_len*sizeof(PAR));
                
                }
                self->g_H1_pers_pairs[self->g_H1_pers_pairs_len++] = birth;
                self->g_H1_pers_pairs[self->g_H1_pers_pairs_len++] = death;
                
          }

    }


    // NOTE: Length is updated in add_nz_reduced

    
}


////void H2_reduce(filtration* self, coboundary_H2* coH2_lows_buffer, EDGE_ID buffer_len){
//void H2_reduce(filtration* self, coboundary_H2* coH2_lows_buffer, EDGE_ID buffer_len, int p_flag){
//
//        //printf("\nStarting reduction");
//
//        //self->g_p_flag = 0;
//
//        for (int it = 0; it < buffer_len; it++){
//        
//            
//              if (coH2_lows_buffer[it].flag_trivial_pair){
//
//                  // Trivial pair, no need to add to pivots or to V
//                  //printf("\nSkipping");
//                  // Its barcode length will be 0
//                  // CHANGEHEEHEHEIAHFKLAJHFAHFKLALKFHAKLFHLKFHLKDFHLKL
//                  // CHANGEHEEHEHEIAHFKLAJHFAHFKLALKFHAKLFHLKFHLKDFHLKL
//                  // CHANGEHEEHEHEIAHFKLAJHFAHFKLALKFHAKLFHLKFHLKDFHLKL
//                  // CHANGEHEEHEHEIAHFKLAJHFAHFKLALKFHAKLFHLKFHLKDFHLKL
//
//                  //self->g_p_flag = 1;
//                  //add_H2_pivot(self, &(coH2_lows_buffer[it].low), 0, coH2_lows_buffer[it].triangle);
//                  //self->g_p_flag = 0;
//
//                  continue;
//
//              }
//
//              //printf("\rreducing %d", it);
//              
//              self->g_V_temp_H2[0] = coH2_lows_buffer[it]; 
//              
//              if ((self->g_V_temp_H2[0].triangle.key1 == 25730)\
//                &&(self->g_V_temp_H2[0].triangle.key2 == 157)){
//                self->g_p_flag = 0;
//              }
//
//              //find_H2_cohom_low(self, &(self->g_V_temp_H2[0]));
//
//              self->g_this_pivot = self->g_V_temp_H2[0].low;
//
//              //EDGE_ID o_ab = self->g_V_temp_H2[0].low.key1;
//              //EDGE_ID o_cd = self->g_V_temp_H2[0].low.key2;
//              //VERT_ID d = self->g_edges_list[o_cd][1];
//
//              //if ((o_ab == self->g_V_temp_H2[0].triangle.key1)\
//              //    &&(d == self->g_V_temp_H2[0].triangle.key2)){
//              //     
//              //     //printf("\nflg trivial");
//              //     //coH2_lows_buffer[mm].flag_trivial_pair = 1;
//              //     //getchar();
//              //     
//              //     //add_H2_pivot(self, &(this_pivot), 0, self->g_V_temp_H2[0].triangle);
//              //     //return;
//              //     continue;
//              //}
//              
//
//              self->g_V_temp_len_H2 = 1;
//
//              EDGE_ID V_col_idx;
//              simplex reduce_w_bndry;
//
//              //coboundary_H2 temp;
//              //temp.triangle.key1 = o_ab;
//              //temp.triangle.key2 = d;
//
//              //find_H2_cohom_low(self, &(temp));
//
//              //// Check if the low of this triangle is same as this_pivot
//              //if ((temp.low.key1 == this_pivot.key1)\
//              //    && (temp.low.key2 == this_pivot.key2)){
//
//              //        reduce_w_bndry.key1 = temp.triangle.key1;
//              //        reduce_w_bndry.key2 = temp.triangle.key2;
//              //        V_col_idx = self->g_n_valid_edges;
//              //        //continue;
//
//              //}
//
//              // CHANGEHEEHEHEIAHFKLAJHFAHFKLALKFHAKLFHLKFHLKDFHLKL
//              // CHANGEHEEHEHEIAHFKLAJHFAHFKLALKFHAKLFHLKFHLKDFHLKL
//              // CHANGEHEEHEHEIAHFKLAJHFAHFKLALKFHAKLFHLKFHLKDFHLKL
//              // CHANGEHEEHEHEIAHFKLAJHFAHFKLALKFHAKLFHLKFHLKDFHLKL
//              // CHANGEHEEHEHEIAHFKLAJHFAHFKLALKFHAKLFHLKFHLKDFHLKL
//              // CHANGEHEEHEHEIAHFKLAJHFAHFKLALKFHAKLFHLKFHLKDFHLKL
//              // CHANGEHEEHEHEIAHFKLAJHFAHFKLALKFHAKLFHLKFHLKDFHLKL
//              //
//              // CHANGE to == 1 TO ENABLE THIS
//              if (self->g_V_temp_H2[0].flag_reduce_with_triangle == 1){
//
//                  reduce_w_bndry = self->g_V_temp_H2[0].reduce_with_triangle;
//                  V_col_idx = self->g_n_valid_edges;
//
//                    
//              }
//              else{
//                // If this low is not a pivot
//                if (!self->g_H2_cohom_pivots_len[self->g_this_pivot.key1]){
//
//                  //Then add this to pivots but NOT to V_reduced
//                  // red_col = g_n_valid_edges
//                  
//                  //printf("\nAdding pivot pair: (%d, %d) has lowest (%d, %d)"\
//                  //                        , self->g_V_temp_H2[0].triangle.key1\
//                  //                        , self->g_V_temp_H2[0].triangle.key2\
//                  //                        , self->g_V_temp_H2[0].low.key1\
//                  //                        , self->g_V_temp_H2[0].low.key2\
//                  //                        );
//                  
//                  //EDGE_ID oo = self->g_V_temp_H2[0].low.key2;
//                  //EDGE_ID dd = self->g_edges_list[oo][1];
//                  //printf("\nmax triangle is (%d, %d)", self->g_V_temp_H2[0].low.key1, dd);
//
//
//                  //coboundary_H2 ttemp;
//                  //ttemp.triangle.key1 = self->g_V_temp_H2[0].low.key1;
//                  //ttemp.triangle.key2 = dd;
//                  //find_H2_cohom_low(self, &(ttemp));
//
//                  //printf("\nmax triangle (%d, %d) has low (%d, %d)"\
//                  //                                  , self->g_V_temp_H2[0].low.key1\
//                  //                                  , dd\
//                  //                                  , ttemp.low.key1\
//                  //                                  , ttemp.low.key2\
//                  //                                  );
//
//                  add_H2_pivot(self, &(self->g_this_pivot), 0, self->g_V_temp_H2[0].triangle);
//
//                  //getchar();
//
//                  //return;
//                  continue;
//
//                }
//
//                EDGE_ID idx = search_H2_cohom_pivots(self->g_H2_cohom_pivots[self->g_this_pivot.key1]\
//                                          , 0 \
//                                          , self->g_H2_cohom_pivots_len[self->g_this_pivot.key1] - 1\
//                                          , self->g_this_pivot.key2 \
//                                          , self->g_n_valid_edges);
//
//                // If this low is not a pivot
//                if (idx == self->g_n_valid_edges){
//
//                      //Then add this to pivots but NOT to V_reduced because no reduction operations
//                      //were done
//                      // red_col = g_n_valid_edges, no reductions were done
//                      add_H2_pivot(self, &(self->g_this_pivot), 0, self->g_V_temp_H2[0].triangle);
//
//                      //return;
//                      continue;
//                        
//
//                }
//
//                reduce_w_bndry = self->g_H2_cohom_pivots[self->g_this_pivot.key1][idx].bndry;
//                V_col_idx = self->g_H2_cohom_pivots[self->g_this_pivot.key1][idx].col_idx;
//
//
//              }
//
//
//              //printf("\n\n");
//              //printf("\nMain boundary (%d, %d)",self->g_V_temp_H2[0].triangle.key1\
//              //                                 ,self->g_V_temp_H2[0].triangle.key2);
//
//              // Begin reduction
//              int empty = 0;
//
//              while(1){
//
//                    if (self->g_p_flag){
//
//                        printf("\nVtemp before reduction is");
//                        for (int gg = 0; gg < self->g_V_temp_len_H2; gg++){
//                          printf("\n(%d, %d)", self->g_V_temp_H2[gg].triangle.key1\
//                                              , self->g_V_temp_H2[gg].triangle.key2);
//                        }
//
//
//                        printf("\nReducing with (%d, %d)" , reduce_w_bndry.key1\
//                                                          , reduce_w_bndry.key2\
//                                                             );
//
//                    }
//                    
//                    // Add the first boundary to reduce with
//
//                    self->g_V_temp_H2[self->g_V_temp_len_H2].triangle = reduce_w_bndry;
//
//                    find_H2_cohom_greater(self, &(self->g_V_temp_H2[self->g_V_temp_len_H2]), self->g_this_pivot);
//
//                    if (self->g_p_flag){
//                        printf("\nfound H2 greater");
//                        getchar();
//                    }
//                    self->g_V_temp_len_H2++;
//
//                    if (self->g_V_temp_len_H2 == self->g_V_temp_max_len_H2){
//                        self->g_V_temp_max_len_H2 += 10;
//                        self->g_V_temp_H2 = (coboundary_H2*)realloc(self->g_V_temp_H2\
//                                                , self->g_V_temp_max_len_H2*sizeof(coboundary_H2));
//
//                    }
//                    
//
//                    
//                    if (V_col_idx != self->g_n_valid_edges){
//                          
//                        EDGE_ID start = self->g_V_col_indices[V_col_idx];
//                        //EDGE_ID V_len = V_col_indices[V_col_idx+1] - start;
//                        EDGE_ID end = self->g_V_col_indices[V_col_idx+1];
//
//                        // Check space
//                        if ((end - start + self->g_V_temp_len_H2) > self->g_V_temp_max_len_H2){
//
//                              self->g_V_temp_max_len_H2 += end - start + self->g_V_temp_len_H2 + 10;
//                              self->g_V_temp_H2 = (coboundary_H2*)realloc(self->g_V_temp_H2\
//                                                    , self->g_V_temp_max_len_H2*sizeof(coboundary_H2));
//
//                        }
//                          
//                        // Add V
//                        // Maybe this can be in parallel
//                        for (EDGE_ID mm = start; mm < end; mm++){
//                              
//                            //printf("\nFinding geq for %d", V_temp[V_temp_len].o_ab);
//                            self->g_V_temp_H2[self->g_V_temp_len_H2].triangle = self->g_V_sparse_H2[mm];
//                            // Find the first low greater than or equal pivot
//                            find_H2_cohom_greater(self, &(self->g_V_temp_H2[self->g_V_temp_len_H2]), self->g_this_pivot);
//                            //printf("\nFOUND (%d, %d) geq (%d, %d)", V_temp[V_temp_len].low.key1, V_temp[V_temp_len].low.key2\
//                            //                            , self->g_this_pivot.key1, self->g_this_pivot.key2);
//
//                            self->g_V_temp_len_H2++;
//                            //if (V_temp_len == V_temp_max_len){
//                            //    V_temp_max_len += 10;
//                            //    V_temp = (coboundary*)realloc(V_temp, V_temp_max_len*sizeof(coboundary));
//
//                            //}
//                            
//                        }
//
//                          
//                    }
//
//                    //printf("\nBefore Reduction");
//                    //for (EDGE_ID mm = 0; mm < self->g_V_temp_len_H2; mm++){
//                    //      
//                    //      printf("\nidx %d, (%d, %d) has low (%d, %d)", mm\
//                    //                                                  , self->g_V_temp_H2[mm].triangle.key1\
//                    //                                                  , self->g_V_temp_H2[mm].triangle.key2\
//                    //                                                  , self->g_V_temp_H2[mm].low.key1\
//                    //                                                  , self->g_V_temp_H2[mm].low.key2\
//                    //                                                  );
//                    //}
//
//
//
//                    // Reduce
//                    
//                    int coeff;
//                    int reduction_counter = 0;
//
//                    while (1){
//
//                            
//                          EDGE_ID o_min = self->g_n_valid_edges;
//                          EDGE_ID v_min = 0;
//
//
//
//                          //printf("\n %d", self->g_V_temp_len_H2);
//                            
//                          reduction_counter++;
//
//                          empty = 1;
//
//                          if (self->g_V_temp_len_H2 > 10000){
//
//                                  //////////////////////////////////////
//                                  // Allocate jobs
//                                  //////////////////////////////////////
//                                  //printf("\nAllocating jobs for parallel");
//                                  //getchar();
//
//                                  //printf("\nOld pivot is (%d, %d)", self->g_this_pivot.key1, self->g_this_pivot.key2);
//                                  //getchar();
//                                  
//                                  //printf("\nDoing parallel");
//
//                                  allocate_jobs(self, self->g_V_temp_len_H2);
//
//                                  self->g_processed_threads = 0;
//
//                                  pthread_cond_broadcast(&(self->g_start_workers));
//
//
//                                  while (self->g_processed_threads != self->g_cpu_count){
//                                        
//                                        pthread_cond_wait(&(self->g_start_boss) \
//                                                        ,&(self->g_thread_lock));
//                                  }
//
//
//                                  for (int tid = 0; tid < self->g_cpu_count; tid++){
//
//                                      //printf("\nthread %d, low is (%d, %d)", tid, self->g_this_pivot_thread[tid].key1\
//                                      //                                          , self->g_this_pivot_thread[tid].key2);
//
//                                      if (self->g_this_pivot_thread[tid].key1 == self->g_n_valid_edges){
//                                        continue;
//                                      }
//
//                                      if (self->g_empty_thread[tid]){
//                                        continue;
//                                      }
//
//                                      empty = 0;
//
//                                      if ((self->g_this_pivot_thread[tid].key1 < o_min) \
//                                        || ((self->g_this_pivot_thread[tid].key1 == o_min) && (self->g_this_pivot_thread[tid].key2 < v_min))){
//                                            
//                                              
//                                              o_min = self->g_this_pivot_thread[tid].key1;
//                                              v_min = self->g_this_pivot_thread[tid].key2;
//                                              coeff = self->g_coeff_thread[tid];
//
//                                      }
//                                      else if ((self->g_this_pivot_thread[tid].key1 == o_min) && (self->g_this_pivot_thread[tid].key2 == v_min)){
//                                            
//                                            coeff += self->g_coeff_thread[tid];
//                                            
//                                      }
//                                        
//
//                                  }
//
//                                  coeff = coeff % 2;
//
//
//
//                          }
//                          else{
//
//                                  
//                                  if (self->g_p_flag){
//                                      printf("\nOld pivot for (%d, %d) is (%d, %d)"\
//                                                                 , self->g_V_temp_H2[0].triangle.key1\
//                                                                 , self->g_V_temp_H2[0].triangle.key2\
//                                                                 , self->g_this_pivot.key1\
//                                                                 , self->g_this_pivot.key2);
//                                  }
//
//                                  for (EDGE_ID mm = 0; mm < self->g_V_temp_len_H2; mm++){
//                                        
//                                      
//
//                                      if (self->g_V_temp_H2[mm].low.key1 == self->g_n_valid_edges){
//                                        continue;
//
//                                      }
//                                      if (self->g_p_flag){
//
//                                        printf("\n");
//                                        printf("\n idx %d, low of (%d,%d) is (%d, %d)", mm, self->g_V_temp_H2[mm].triangle.key1\
//                                                                                        , self->g_V_temp_H2[mm].triangle.key2\
//                                                                                        , self->g_V_temp_H2[mm].low.key1\
//                                                                                        , self->g_V_temp_H2[mm].low.key2);
//                                      }
//
//                                      if ((self->g_V_temp_H2[mm].low.key1 == self->g_this_pivot.key1)\
//                                          && (self->g_V_temp_H2[mm].low.key2 == self->g_this_pivot.key2)){
//
//                                            //if (self->g_p_flag){
//                                            //printf("\n before a_ptr %d, b_ptr %d, c_ptr, %d, vertex %d"\
//                                            //                              , self->g_V_temp_H2[mm].a_ptr\
//                                            //                              , self->g_V_temp_H2[mm].b_ptr\
//                                            //                              , self->g_V_temp_H2[mm].c_ptr\
//                                            //                              , self->g_V_temp_H2[mm].vertex\
//                                            //                              );
//                                            //}
//                                            
//                                            //printf("\npointers are %d, %d", V_temp[mm].a_ptr, V_temp[mm].b_ptr);
//                                            find_H2_cohom_next(self, &(self->g_V_temp_H2[mm]));
//
//                                            //if (self->g_p_flag){
//                                            //printf("\n idx %d, NEXT low of (%d,%d) is (%d, %d)", mm, self->g_V_temp_H2[mm].triangle.key1\
//                                            //                                          , self->g_V_temp_H2[mm].triangle.key2\
//                                            //                                          , self->g_V_temp_H2[mm].low.key1\
//                                            //                                          , self->g_V_temp_H2[mm].low.key2);
//
//                                            //printf("\n after a_ptr %d, b_ptr %d, c_ptr, %d, vertex %d"\
//                                            //                              , self->g_V_temp_H2[mm].a_ptr\
//                                            //                              , self->g_V_temp_H2[mm].b_ptr\
//                                            //                              , self->g_V_temp_H2[mm].c_ptr\
//                                            //                              , self->g_V_temp_H2[mm].vertex\
//                                            //                              );
//                                            //printf("\n");
//                                            //}
//                                            //getchar();
//
//                                      }
//
//                                      empty = 0;
//
//                                      if ((self->g_V_temp_H2[mm].low.key1 < o_min) \
//                                        || ((self->g_V_temp_H2[mm].low.key1 == o_min) && (self->g_V_temp_H2[mm].low.key2 < v_min))){
//                                            
//                                              
//                                              o_min = self->g_V_temp_H2[mm].low.key1;
//                                              v_min = self->g_V_temp_H2[mm].low.key2;
//                                              coeff = 1;
//                                        }
//                                      else if ((self->g_V_temp_H2[mm].low.key1 == o_min) && (self->g_V_temp_H2[mm].low.key2 == v_min)){
//                                            
//                                            coeff = 1 - coeff;
//                                            
//                                      }
//
//                                  }
//
//
//                          }
//
//
//
//
//                          self->g_this_pivot.key1 = o_min;
//                          self->g_this_pivot.key2 = v_min;
//
//                          //printf("\nCoeff is %d for low (%d, %d)", coeff, self->g_this_pivot.key1, self->g_this_pivot.key2);
//
//                          if ((coeff) || (empty)){
//                              // We have a new pivot or it is empty
//                              break;
//                          }
//
//
//
//                    }
//
//                    if (empty){
//                          break;
//                    }
//
//
//                    //printf("\nAfter Reduction");
//                    //for (EDGE_ID mm = 0; mm < self->g_V_temp_len_H2; mm++){
//                    //      
//                    //      printf("\nidx %d, (%d, %d) has low (%d, %d)", mm\
//                    //                                                  , self->g_V_temp_H2[mm].triangle.key1\
//                    //                                                  , self->g_V_temp_H2[mm].triangle.key2\
//                    //                                                  , self->g_V_temp_H2[mm].low.key1\
//                    //                                                  , self->g_V_temp_H2[mm].low.key2\
//                    //                                                  );
//                    //}
//                    if (self->g_p_flag){
//                        printf("\nNew pivot for (%d, %d) is (%d, %d)"\
//                                                                     , self->g_V_temp_H2[0].triangle.key1\
//                                                                     , self->g_V_temp_H2[0].triangle.key2\
//                                                                     , self->g_this_pivot.key1\
//                                                                     , self->g_this_pivot.key2);
//                        getchar();
//
//                    }
//
//                    //getchar();
//
//                    //////// NEEEEEEWWWWWWWWWWWWWWWW
//                    //////// CHeck whether the low of the max triangle for this pivot is the same low 
//
//                    coboundary_H2 temp;
//
//
//                    // Get low for triangle <ab, d> in this_pivot
//                    temp.triangle.key1 = self->g_this_pivot.key1;
//                    temp.triangle.key2 = self->g_edges_list[self->g_this_pivot.key2][1];
//
//                    find_H2_cohom_low(self, &temp);
//                    
//                    // Check if the low of this triangle is same as self->g_this_pivot
//                    if ((temp.low.key1 == self->g_this_pivot.key1)\
//                        && (temp.low.key2 == self->g_this_pivot.key2)){
//
//                            reduce_w_bndry.key1 = temp.triangle.key1;
//                            reduce_w_bndry.key2 = temp.triangle.key2;
//                            V_col_idx = self->g_n_valid_edges;
//                            //printf("\nReducing with trivial pair (%d, %d)", temp.triangle.key1\
//                            //                                              , temp.triangle.key2);
//                            continue;
//
//                    }
//
//                    //// If this low is not a pivot, exit main reduction
//                    if (!self->g_H2_cohom_pivots_len[self->g_this_pivot.key1]){
//                        break;
//                    }
//
//                    EDGE_ID idx = search_H2_cohom_pivots(self->g_H2_cohom_pivots[self->g_this_pivot.key1]\
//                                    , 0 \
//                                    , self->g_H2_cohom_pivots_len[self->g_this_pivot.key1] - 1\
//                                    , self->g_this_pivot.key2 \
//                                    , self->g_n_valid_edges);
//
//
//                    if (idx == self->g_n_valid_edges){
//                        //if (self->g_new_debug){
//
//                        //    printf("\nThis pivot was not found in complex.\n", self->g_this_pivot.key1, self->g_this_pivot.key2);
//                        //    getchar();
//                        //}
//                      break;
//                    }
//
//
//                    reduce_w_bndry = self->g_H2_cohom_pivots[self->g_this_pivot.key1][idx].bndry;
//                    V_col_idx = self->g_H2_cohom_pivots[self->g_this_pivot.key1][idx].col_idx;
//                    if (self->g_p_flag){
//
//                      printf("\nReducing with pivot found (%d, %d): V_col_idx is %d", reduce_w_bndry.key1\
//                                                                   , reduce_w_bndry.key2, V_col_idx);
//                      getchar();
//                    }
//
//
//              }
//
//
//
//              // This was not reduced to 0, add this_pivot and update V_reduced
//              if (!empty) {
//                    
//                    
//                    //printf("\nV to be added is ");
//                    //for (int gg = 0; gg < self->g_V_temp_len_H2; gg++){
//                    //  printf("\n(%d, %d)", self->g_V_temp_H2[gg].triangle.key1\
//                    //                      , self->g_V_temp_H2[gg].triangle.key2);
//                    //}
//
//                    if ((self->g_V_sparse_ptr + self->g_V_temp_len_H2 - 1) > self->g_V_sparse_max){
//
//                          self->g_V_sparse_max += (self->g_V_sparse_ptr + self->g_V_temp_len_H2 + 1000);
//                          self->g_V_sparse_H2 = (simplex*)realloc(self->g_V_sparse_H2\
//                                                          , self->g_V_sparse_max*sizeof(simplex));
//                          
//                    }
//
//                    self->g_V_sparse_beg_ptr = self->g_V_sparse_ptr;
//
//                    //simplex prev = self->g_V_temp_H2[1].triangle;
//
//                    int sort_flag = 0;
//
//                    for (EDGE_ID mm = 1; mm < self->g_V_temp_len_H2-1; mm++){
//
//                          self->g_V_sparse_H2[self->g_V_sparse_ptr++] = self->g_V_temp_H2[mm].triangle;
//
//                          if ((self->g_V_temp_H2[mm+1].triangle.key1 < self->g_V_temp_H2[mm].triangle.key1)\
//                              ||((self->g_V_temp_H2[mm+1].triangle.key1 == self->g_V_temp_H2[mm].triangle.key1)\
//                                    && (self->g_V_temp_H2[mm+1].triangle.key2 < self->g_V_temp_H2[mm].triangle.key2))){
//                            sort_flag = 1;
//                          }
//                          //else{
//                          //      prev = self->g_V_temp_H2[mm].triangle;
//                          //}
//
//
//                    }
//
//
//                    self->g_V_sparse_H2[self->g_V_sparse_ptr++] =\
//                                                     self->g_V_temp_H2[self->g_V_temp_len_H2-1].triangle;
//
//                    self->g_V_sparse_end_ptr = self->g_V_sparse_ptr;
//
//                    if (sort_flag){
//                      
//                        if (p_flag){
//                            printf("\nBefore");
//                            for (EDGE_ID mm = self->g_V_sparse_beg_ptr; mm < self->g_V_sparse_ptr; mm++){
//
//                                  printf("\n(%d, %d)", self->g_V_sparse_H2[mm].key1, self->g_V_sparse_H2[mm].key2);
//
//                            }
//
//                        }
//
//                        sorter4_tim_sort(&(self->g_V_sparse_H2[self->g_V_sparse_beg_ptr])\
//                                            , self->g_V_sparse_end_ptr - self->g_V_sparse_beg_ptr);
//
//                        if (p_flag){
//                            printf("\nAfter");
//                            for (EDGE_ID mm = self->g_V_sparse_beg_ptr; mm < self->g_V_sparse_ptr; mm++){
//
//                                  printf("\n(%d, %d)", self->g_V_sparse_H2[mm].key1, self->g_V_sparse_H2[mm].key2);
//
//                            }
//                            //getchar();
//
//                        }
//
//
//                    }
//
//
//
//                     if ((self->g_V_sparse_end_ptr - self->g_V_sparse_beg_ptr) > 2){
//
//
//                          
//                            self->g_V_sparse_ptr = self->g_V_sparse_beg_ptr;
//
//                            int count = 1;
//                            for (EDGE_ID mm = self->g_V_sparse_beg_ptr; mm < self->g_V_sparse_end_ptr-1; mm++){
//
//                                  if ((self->g_V_sparse_H2[mm+1].key1 == self->g_V_sparse_H2[mm].key1)\
//                                      && (self->g_V_sparse_H2[mm+1].key2 == self->g_V_sparse_H2[mm].key2)){
//                                      count = 1 - count;
//                                  } 
//                                  else{
//
//                                      if (count){
//                                          self->g_V_sparse_H2[self->g_V_sparse_ptr++] = self->g_V_sparse_H2[mm];
//                                      }
//                                      count = 1;
//
//                                  }
//                                
//                            }
//
//                            if (count){
//
//                                self->g_V_sparse_H2[self->g_V_sparse_ptr++] = self->g_V_sparse_H2[self->g_V_sparse_end_ptr-1];
//
//                            }
//
//                            self->g_V_sparse_end_ptr = self->g_V_sparse_ptr;
//
//                            if (self->g_V_sparse_end_ptr == self->g_V_sparse_beg_ptr){
//                                // this_pivot is the pivot to be added
//                                //free(V_reduced[V_reduced_col_idx]);
//                                //Empty V, do not add V, but add the bndry as pivot
//
//                                add_H2_pivot(self, &(self->g_this_pivot), 0 , self->g_V_temp_H2[0].triangle);
//                                continue;
//
//                            }
//                            else {
//
//                                add_H2_pivot(self, &(self->g_this_pivot), 1 , self->g_V_temp_H2[0].triangle);
//                                continue;
//
//                            }
//
//
//                     }
//                     else{
//
//                              add_H2_pivot(self, &(self->g_this_pivot), 1 , self->g_V_temp_H2[0].triangle);
//                              continue;
//                     }
//
//
//
//              }
//
//        
//              // Here means that it is undead
//
//              float birth = self->g_V_temp_H2[0].triangle.key1;
//              float death = -1;
//                    
//              if (self->g_H2_pers_pairs_len+2 == self->g_H2_pers_pairs_max_len){
//                    self->g_H2_pers_pairs_max_len += 1000;
//                    self->g_H2_pers_pairs = (PAR*)realloc(self->g_H2_pers_pairs\
//                                                        , self->g_H2_pers_pairs_max_len*sizeof(PAR));
//              
//              }
//              self->g_H2_pers_pairs[self->g_H2_pers_pairs_len++] = birth;
//              self->g_H2_pers_pairs[self->g_H2_pers_pairs_len++] = death;
//                    
//        
//        }
//
//
//}
//
void* parallel_coH1_next(void* arg){

      filtration* self = arg;



      pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);

      pthread_mutex_lock(&(self->g_thread_lock));

      int tid = ++self->g_thread_id;

      int mm;

      for (;;){

          self->g_sleeping_threads++;
          
          if (self->g_sleeping_threads == self->g_cpu_count)
              pthread_cond_signal(&(self->g_start_boss));

          pthread_cond_wait(&(self->g_start_workers), &(self->g_thread_lock));

          if (self->g_delete_threads){
            //printf("\nexiting from thread %d", tid);
            pthread_mutex_unlock(&(self->g_thread_lock));
            pthread_exit(NULL);
          }

          self->g_sleeping_threads--;

          pthread_mutex_unlock(&(self->g_thread_lock));

          self->g_empty_thread[tid-1] = 1;
          EDGE_ID o_min = self->g_n_valid_edges;
          EDGE_ID v_min = 0;

          //printf("\nBeginning of thread %d, o_min, v_min is (%d, %d)", tid-1\
          //                                                           , o_min\
          //                                                           , v_min);

          for (mm = self->g_jobs[tid - 1]; mm < self->g_jobs[tid]; mm++){
                  
                    // DO THINGS HERE
                    // DO THINGS HERE
                    // DO THINGS HERE

                        if (self->g_V_temp[mm].low.key1 == self->g_n_valid_edges){
                          continue;

                        }

                        if ((self->g_V_temp[mm].low.key1 == self->g_this_pivot.key1)\
                            && (self->g_V_temp[mm].low.key2 == self->g_this_pivot.key2)){

                              //printf("\nfinding next in worker %d of tid %d, because current is (%d,%d)"\
                              //                        , mm\
                              //                        , tid-1\
                              //                        , self->g_V_temp[mm].low.key1\
                              //                        , self->g_V_temp[mm].low.key2\
                              //                        );
                              
                              find_H1_cohom_next(self, &(self->g_V_temp[mm]));

                              //printf("\nnext low in worker %d of tid %d, is (%d,%d)"\
                              //                        , mm\
                              //                        , tid-1\
                              //                        , self->g_V_temp[mm].low.key1\
                              //                        , self->g_V_temp[mm].low.key2\
                              //                        );

                        }

                        self->g_empty_thread[tid-1] = 0;

                        if ((self->g_V_temp[mm].low.key1 < o_min) \
                          || ((self->g_V_temp[mm].low.key1 == o_min) && (self->g_V_temp[mm].low.key2 < v_min))){
                              
                                //printf("\ngetting low from worker %d", mm);
                                o_min = self->g_V_temp[mm].low.key1;
                                v_min = self->g_V_temp[mm].low.key2;
                                self->g_coeff_thread[tid-1] = 1;
                          }
                        else if ((self->g_V_temp[mm].low.key1 == o_min) && (self->g_V_temp[mm].low.key2 == v_min)){
                              
                              self->g_coeff_thread[tid-1] = 1 - self->g_coeff_thread[tid-1];
                              
                        }
                    // DO THINGS HERE
                    // DO THINGS HERE
                    // DO THINGS HERE
                    // DO THINGS HERE

          }



          self->g_this_pivot_thread[tid-1].key1 = o_min;
          self->g_this_pivot_thread[tid-1].key2 = v_min;

          //printf("\nlowest of this tid %d is (%d,%d)"\
          //                        , tid - 1\
          //                        , self->g_this_pivot_thread[tid-1].key1\
          //                        , self->g_this_pivot_thread[tid-1].key2\
          //                        );

          pthread_mutex_lock(&(self->g_thread_lock));

          self->g_processed_threads++;


      }
    

}




//void* parallel_coH2_next(void* arg){
//
//
//    
//      filtration* self = arg;
//
//
//      pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);
//
//      pthread_mutex_lock(&(self->g_thread_lock));
//
//      int tid = ++self->g_thread_id;
//
//      int mm;
//
//      for (;;){
//
//
//          self->g_sleeping_threads++;
//          
//          if (self->g_sleeping_threads == self->g_cpu_count)
//              pthread_cond_signal(&(self->g_start_boss));
//
//          pthread_cond_wait(&(self->g_start_workers), &(self->g_thread_lock));
//
//          if (self->g_delete_threads){
//            //printf("\nexiting from thread %d", tid);
//            pthread_mutex_unlock(&(self->g_thread_lock));
//            pthread_exit(NULL);
//          }
//
//          self->g_sleeping_threads--;
//
//          pthread_mutex_unlock(&(self->g_thread_lock));
//
//          self->g_empty_thread[tid-1] = 1;
//          EDGE_ID o_min = self->g_n_valid_edges;
//          EDGE_ID v_min = 0;
//
//          for (mm = self->g_jobs[tid - 1]; mm < self->g_jobs[tid]; mm++){
//                  
//                    // DO THINGS HERE
//                    // DO THINGS HERE
//                    // DO THINGS HERE
//
//                        if (self->g_V_temp_H2[mm].low.key1 == self->g_n_valid_edges){
//                          continue;
//
//                        }
//
//                        if ((self->g_V_temp_H2[mm].low.key1 == self->g_this_pivot.key1)\
//                            && (self->g_V_temp_H2[mm].low.key2 == self->g_this_pivot.key2)){
//
//                              //printf("\nfinding next in worker %d of tid %d, because current is (%d,%d)"\
//                              //                        , mm\
//                              //                        , tid-1\
//                              //                        , self->g_V_temp[mm].low.key1\
//                              //                        , self->g_V_temp[mm].low.key2\
//                              //                        );
//                              
//                              find_H2_cohom_next(self, &(self->g_V_temp_H2[mm]));
//
//                              //printf("\nnext low in worker %d of tid %d, is (%d,%d)"\
//                              //                        , mm\
//                              //                        , tid-1\
//                              //                        , self->g_V_temp[mm].low.key1\
//                              //                        , self->g_V_temp[mm].low.key2\
//                              //                        );
//
//                        }
//
//                        self->g_empty_thread[tid-1] = 0;
//
//                        if ((self->g_V_temp_H2[mm].low.key1 < o_min) \
//                          || ((self->g_V_temp_H2[mm].low.key1 == o_min) && (self->g_V_temp_H2[mm].low.key2 < v_min))){
//                              
//                                //printf("\ngetting low from worker %d", mm);
//                                o_min = self->g_V_temp_H2[mm].low.key1;
//                                v_min = self->g_V_temp_H2[mm].low.key2;
//                                self->g_coeff_thread[tid-1] = 1;
//                          }
//                        else if ((self->g_V_temp_H2[mm].low.key1 == o_min) && (self->g_V_temp_H2[mm].low.key2 == v_min)){
//                              
//                              self->g_coeff_thread[tid-1] = 1 - self->g_coeff_thread[tid-1];
//                              
//                        }
//                    // DO THINGS HERE
//                    // DO THINGS HERE
//                    // DO THINGS HERE
//                    // DO THINGS HERE
//
//          }
//
//
//
//          self->g_this_pivot_thread[tid-1].key1 = o_min;
//          self->g_this_pivot_thread[tid-1].key2 = v_min;
//
//          //printf("\nlowest of this tid %d is (%d,%d)"\
//          //                        , tid - 1\
//          //                        , self->g_this_pivot_thread[tid-1].key1\
//          //                        , self->g_this_pivot_thread[tid-1].key2\
//          //                        );
//
//          pthread_mutex_lock(&(self->g_thread_lock));
//
//          self->g_processed_threads++;
//
//
//
//      }
//
//
//
//
//}


int H2_case1(filtration* self, coboundary_H2* V_info){
      
      //if (self->g_p_flag){
      //    printf("\nstarting H2 case 1");
      //    getchar();
      //}

      //EDGE_ID o_ab = V_info->triangle.key1;
      
      VERT_ID a = self->g_edges_list[V_info->triangle.key1][0];
      VERT_ID b = self->g_edges_list[V_info->triangle.key1][1];
      VERT_ID c = V_info->triangle.key2;
      
      VERT_ID idxa, idxb, idxc;

      while ((V_info->c_ptr < self->g_Neigh_len[c])\
            && (self->g_Neighbors_e[c][V_info->c_ptr].order < V_info->triangle.key1)){


            VERT_ID d = self->g_Neighbors_e[c][V_info->c_ptr].neighbor;

            idxa = search_Neighbors(self, a, d, 0, self->g_Neigh_len[a] - 1);

            if (idxa == self->g_n_vert){
                  V_info->c_ptr++;
                  continue;
            }

            if (self->g_Neighbors[a][idxa].order > V_info->triangle.key1){
                  V_info->c_ptr++;
                  continue;
            }

            idxb = search_Neighbors(self, b, d, 0, self->g_Neigh_len[b] - 1);

            if (idxb == self->g_n_vert){
                  V_info->c_ptr++;
                  continue;
            }

            if (self->g_Neighbors[b][idxb].order > V_info->triangle.key1){
                  V_info->c_ptr++;
                  continue;
            }

            V_info->low.key1 = V_info->triangle.key1;
            V_info->low.key2 = self->g_Neighbors_e[c][V_info->c_ptr].order;
            V_info->vertex = 0;
            return 1;

      }

      return 0;
      
      
      
}

void H2_case2 ( filtration* self, coboundary_H2* V_info){
      
      //if (self->g_p_flag){
      //    printf("\nstarting H2 case 2");
      //    getchar();
      //}
      VERT_ID idxa, idxb, idxc, idx;
      VERT_ID a, b, c;
      EDGE_ID o_ad, o_bd, o_cd;

      c = V_info->triangle.key2;
      
      a = self->g_edges_list[V_info->triangle.key1][0];
      b = self->g_edges_list[V_info->triangle.key1][1];


      
      while (1){
            
            
          EDGE_ID ep = self->g_n_valid_edges;
          VERT_ID d;
          int flag = -1;

          if (V_info->a_ptr < self->g_Neigh_len[a]){
                
                ep = self->g_Neighbors_e[a][V_info->a_ptr].order;
                flag = 1;

          }

          if (V_info->b_ptr < self->g_Neigh_len[b]){
                
                if (self->g_Neighbors_e[b][V_info->b_ptr].order < ep){

                    ep = self->g_Neighbors_e[b][V_info->b_ptr].order;
                    flag = 2;

                }

          }

          if (V_info->c_ptr < self->g_Neigh_len[c]){
                
                if (self->g_Neighbors_e[c][V_info->c_ptr].order < ep){

                    ep = self->g_Neighbors_e[c][V_info->c_ptr].order;
                    flag = 3;

                }

          }

          if (flag == -1){
                
                V_info->low.key1 = ep;
                V_info->vertex = -1;
                return;
          }
          else if (flag == 1){
                
                d = self->g_Neighbors_e[a][V_info->a_ptr].neighbor;
                idxb = search_Neighbors(self, b, d, 0, self->g_Neigh_len[b]-1);

                if (idxb == self->g_n_vert){
                    V_info->a_ptr++;
                    continue;
                }

                o_bd = self->g_Neighbors[b][idxb].order;

                if (o_bd > ep){
                    V_info->a_ptr++;
                    continue;
                }

                idxc = search_Neighbors(self, c, d, 0, self->g_Neigh_len[c]-1);

                if (idxc == self->g_n_vert){
                    V_info->a_ptr++;
                    continue;
                }

                o_cd = self->g_Neighbors[c][idxc].order;

                if (o_cd > ep){
                    V_info->a_ptr++;
                    continue;
                }

                V_info->low.key1 = ep;

                idx = search_Neighbors(self, b, c, 0, self->g_Neigh_len[b]-1);
                V_info->low.key2 = self->g_Neighbors[b][idx].order;

                V_info->vertex = 1;
                return;




          }
          else if (flag == 2){
                
                d = self->g_Neighbors_e[b][V_info->b_ptr].neighbor;

                idxa = search_Neighbors(self, a, d, 0, self->g_Neigh_len[a]-1);
                if (idxa == self->g_n_vert){
                    V_info->b_ptr++;
                    continue;
                }

                o_ad = self->g_Neighbors[a][idxa].order;

                if (o_ad > ep){
                    V_info->b_ptr++;
                    continue;
                }

                idxc = search_Neighbors(self, c, d, 0, self->g_Neigh_len[c]-1);

                if (idxc == self->g_n_vert){
                    V_info->b_ptr++;
                    continue;
                }

                o_cd = self->g_Neighbors[c][idxc].order;

                if (o_cd > ep){
                    V_info->b_ptr++;
                    continue;
                }

                V_info->low.key1 = ep;

                idx = search_Neighbors(self, a, c, 0, self->g_Neigh_len[a]-1);
                V_info->low.key2 = self->g_Neighbors[a][idx].order;

                V_info->vertex = 2;
                return;


          }
          else if (flag == 3){
                
                d = self->g_Neighbors_e[c][V_info->c_ptr].neighbor;

                idxb = search_Neighbors(self, b, d, 0, self->g_Neigh_len[b]-1);

                if (idxb == self->g_n_vert){
                    V_info->c_ptr++;
                    continue;
                }

                o_bd = self->g_Neighbors[b][idxb].order;

                if (o_bd > ep){
                    V_info->c_ptr++;
                    continue;
                }

                idxa = search_Neighbors(self, a, d, 0, self->g_Neigh_len[a]-1);

                if (idxa == self->g_n_vert){
                    V_info->c_ptr++;
                    continue;
                }

                o_ad = self->g_Neighbors[a][idxa].order;

                if (o_ad > ep){
                    V_info->c_ptr++;
                    continue;
                }

                V_info->low.key1 = ep;

                //idx = search_Neighbors(self, a, c, 0, self->g_Neigh_len[a]-1);
                V_info->low.key2 = V_info->triangle.key1;

                V_info->vertex = 3;
                return;

          }
            
      }

      //V_info->low.key1 = self->g_n_valid_edges;
      //V_info->vertex = -1;
      
      
}



void reduce_ws_coH2(filtration* self){

      
      //if (self->g_p_flag){

      //printf("\nBefore with complex, the pivots are:");
      //for (EDGE_ID nn = 0; nn < self->g_cohom_ws_size; nn++){
      //      
      //      coboundary_H2_ws* this_ws = self->g_V_ws_H2_info + nn;

      //      coboundary_H2* orig = self->g_V_ws_H2[nn] + this_ws->original*this_ws->max_len;


      //      printf("\n%d: (%d, %d) has pivot (%d, %d), red_w_c %d, red_w_triv %d, to_comp %d"\
      //                              , nn\
      //                              , orig[0].triangle.key1\
      //                              , orig[0].triangle.key2\
      //                              , this_ws->pivot.key1\
      //                              , this_ws->pivot.key2\
      //                              , this_ws->flag_red_w_complex\
      //                              , this_ws->flag_red_w_trivial\
      //                              , this_ws->flag_append_to_complex\
      //                              );
      //}
      //}

      //struct timespec start_wall_clock, finish_wall_clock;

      //clock_gettime(CLOCK_MONOTONIC, &start_wall_clock);

      self->g_processed_threads = 0;

      pthread_cond_broadcast(&(self->g_start_workers));

      while (self->g_processed_threads != self->g_cpu_count){
            
            pthread_cond_wait(&(self->g_start_boss) \
                            ,&(self->g_thread_lock));
      }

      //clock_gettime(CLOCK_MONOTONIC, &finish_wall_clock);

     //self->g_timer_coH2_parallel += (finish_wall_clock.tv_sec - start_wall_clock.tv_sec);
     //self->g_timer_coH2_parallel += (finish_wall_clock.tv_nsec - start_wall_clock.tv_nsec) / 1000000000.0;


      //clock_gettime(CLOCK_MONOTONIC, &start_wall_clock);

      reduce_with_self_coH2(self);

      //clock_gettime(CLOCK_MONOTONIC, &finish_wall_clock);

      //self->g_timer_coH2_serial += (finish_wall_clock.tv_sec - start_wall_clock.tv_sec);
      //self->g_timer_coH2_serial += (finish_wall_clock.tv_nsec - start_wall_clock.tv_nsec) / 1000000000.0;


      int count_valid = 0;

      int report = 0;

      for (int ws_counter = 0; ws_counter < self->g_ws_counter; ws_counter++){

            
            if (!self->g_V_ws_H2_info[ws_counter].flag_non_empty){
                // Add the undead H2
                
                if (self->g_H2_pers_pairs_len+2 == self->g_H2_pers_pairs_max_len){
                      self->g_H2_pers_pairs_max_len += 1000;
                      self->g_H2_pers_pairs = (PAR*)realloc(self->g_H2_pers_pairs\
                                              , self->g_H2_pers_pairs_max_len*sizeof(PAR));
                
                }

                self->g_H2_pers_pairs[self->g_H2_pers_pairs_len++] = \
                                                     self->g_edge_parameter[self->g_V_ws_H2[ws_counter][0].triangle.key1];

                self->g_H2_pers_pairs[self->g_H2_pers_pairs_len++] = -1;
              
                continue;
            }

            if (self->g_V_ws_H2_info[ws_counter].flag_append_to_complex){
                  report++;
                  update_V_coH2(self, ws_counter);
                  continue;
            }


            // Swap V
            coboundary_H2* temp = self->g_V_ws_H2[count_valid];
            self->g_V_ws_H2[count_valid] = self->g_V_ws_H2[ws_counter];
            self->g_V_ws_H2[ws_counter] = temp;

            // Swap V info
            coboundary_H2_ws temp2 = self->g_V_ws_H2_info[count_valid];
            self->g_V_ws_H2_info[count_valid] = self->g_V_ws_H2_info[ws_counter];
            self->g_V_ws_H2_info[ws_counter] = temp2;


            // At this point, this has to be a non-zero column
            self->g_V_ws_H2_info[count_valid].flag_non_empty = 1;

            
            count_valid++;

      }

      //if (report < self->g_cohom_ws_size){

      //    printf("\nCleared %d", report);
      //}
      self->g_ws_counter = count_valid;
      //getchar();


}


void reduce_with_self_coH2(filtration* self){

      EDGE_ID find_pivot, orig_ptr, scratch_ptr, red_ptr, o_min, v_min;
      EDGE_ID ws_counter, m, compare, check_len;
      simplex* this_pivot;
      coboundary_H2 *orig, *scratch, *original_m;

      // Now we have to reduce
      int coeff;
      EDGE_ID mm;
      int empty;

      for (ws_counter = 0; ws_counter < self->g_ws_counter; ws_counter++){


            coboundary_H2_ws* this_ws = self->g_V_ws_H2_info + ws_counter;
            orig = self->g_V_ws_H2[ws_counter];

            //if ((orig[0].triangle.key1 == 36920) && (orig[0].triangle.key2 == 161)){
            //      self->g_p_flag = 0;
            //}
            //else{
            //      self->g_p_flag = 0;
            //}

            //if (self->g_p_flag){

            //        printf("\nInside self, the pivots are:");
            //        for (EDGE_ID nn = 0; nn < self->g_cohom_ws_size; nn++){
            //              
            //              coboundary_H2_ws* this_ws = self->g_V_ws_H2_info + nn;

            //              coboundary_H2* orig = self->g_V_ws_H2[nn] + this_ws->original*this_ws->max_len;

            //              printf("\n%d: (%d, %d) has pivot (%d, %d), red_w_c %d, red_w_triv %d, to_comp %d"\
            //                                      , nn\
            //                                      , orig[0].triangle.key1\
            //                                      , orig[0].triangle.key2\
            //                                      , this_ws->pivot.key1\
            //                                      , this_ws->pivot.key2\
            //                                      , this_ws->flag_red_w_complex\
            //                                      , this_ws->flag_red_w_trivial\
            //                                      , this_ws->flag_append_to_complex\
            //                                      );
            //        }
            //}




            // If empty, then continue and don't append to complex
            if (!this_ws->flag_non_empty){
                  //this_ws->flag_append_to_complex = 0;
                  continue;
            }

            m = 0;

            // Keep reducing if reduce with complex flag is 0 and reduce with trivial flag is 0
            while((m < ws_counter)\
                && (!this_ws->flag_red_w_complex)\
                && (!this_ws->flag_red_w_trivial)){

                  coboundary_H2_ws* m_ws = self->g_V_ws_H2_info + m;


                  // If m is empty, continue
                  if (!m_ws->flag_non_empty){
                        m++;
                        continue;
                  }


                  compare = compare_simplices(&(m_ws->pivot) \
                                                , &(this_ws->pivot));


                  // If pivot of m is lower than pivot of ws_counter
                  // then if m has to be reduced, we have to hold ws_counter
                  if (compare == 0){

                        if (m_ws->flag_red_w_complex || m_ws->flag_red_w_trivial){
                              
                              this_ws->flag_append_to_complex = 0;
                              break;
                        }
                        m++;
                        continue;
                  }


                  // If pivot of m is higher than pivot of ws_counter
                  // then we don't care
                  if (compare == 1){
                        m++;
                        continue;
                  }

                  // At this point, pivot of m is equal to ws_counter
                  // if m cannot be added to complex, then we cannot use this for reduction
                  // REVIEW LOGIC HERE!!!!!!!!!!!!!!!!!!!!
                  // REVIEW LOGIC HERE!!!!!!!!!!!!!!!!!!!!
                  // REVIEW LOGIC HERE!!!!!!!!!!!!!!!!!!!!
                  // REVIEW LOGIC HERE!!!!!!!!!!!!!!!!!!!!
                  // REVIEW LOGIC HERE!!!!!!!!!!!!!!!!!!!!
                  //
                  //
                  // so...we should break not continue?????
                  //
                  //
                  // REVIEW LOGIC HERE!!!!!!!!!!!!!!!!!!!!
                  // REVIEW LOGIC HERE!!!!!!!!!!!!!!!!!!!!
                  // REVIEW LOGIC HERE!!!!!!!!!!!!!!!!!!!!
                  // REVIEW LOGIC HERE!!!!!!!!!!!!!!!!!!!!
                  // REVIEW LOGIC HERE!!!!!!!!!!!!!!!!!!!!

                  if (m_ws->flag_red_w_complex || m_ws->flag_red_w_trivial){
                        this_ws->flag_append_to_complex = 0;
                        //m++;
                        break;
                        //continue;
                  }

                  // Now we reduce ws_counter with m

                  //printf("\nreducing in serial for %d", ws_counter);


                  original_m = self->g_V_ws_H2[m];

                  orig = self->g_V_ws_H2[ws_counter];

                  this_pivot = &(this_ws->pivot);

                  check_len = m_ws->len + this_ws->len;


                  //if ((orig[0].triangle.key1 == 36920) && (orig[0].triangle.key2 == 161)){
                  //      self->g_p_flag = 0;
                  //}
                  //else{
                  //      self->g_p_flag = 0;
                  //}

                  //if (self->g_p_flag){
                  //    printf("\nReducing (%d, %d) with (%d, %d)"\
                  //                  , orig[0].triangle.key1\
                  //                  , orig[0].triangle.key2\
                  //                  , original_m[0].triangle.key1\
                  //                  , original_m[0].triangle.key2\
                  //                  );
                  //}

                  //if (self->g_p_flag){
                  //printf("\nV at beginning in serial ");
                  //for (EDGE_ID nn = 0; nn < this_ws->len; nn++){
                  //      printf("\n(%d, %d) has low (%d, %d) coeff %d" , orig[nn].triangle.key1\
                  //                                              , orig[nn].triangle.key2\
                  //                                              , orig[nn].low.key1\
                  //                                              , orig[nn].low.key2\
                  //                                              , orig[nn].coeff);
                  //    
                  //}
                  //}

                  if (check_len > this_ws->max_len){


                            this_ws->max_len = check_len + 1000;
                            

                            self->g_V_ws_H2[ws_counter] = (coboundary_H2*)realloc(self->g_V_ws_H2[ws_counter]\
                                                                        , this_ws->max_len*sizeof(coboundary_H2));

                            orig = self->g_V_ws_H2[ws_counter];



                  }



                  for (EDGE_ID nn = 0; nn < m_ws->len; nn++){
                        
                        orig[this_ws->len++] = original_m[nn];
                  }

                  // Now we have to reduce
                  //int coeff;
                  //EDGE_ID mm;
                  //int empty;

                  //if (self->g_p_flag){
                  //printf("\nV at beginning of red loop in serial ");
                  //for (EDGE_ID nn = 0; nn < this_ws->len; nn++){
                  //      printf("\n(%d, %d) has low (%d, %d) coeff %d" , scratch[nn].triangle.key1\
                  //                                              , scratch[nn].triangle.key2\
                  //                                              , scratch[nn].low.key1\
                  //                                              , scratch[nn].low.key2\
                  //                                              , scratch[nn].coeff);
                  //    
                  //}
                  //printf("\nOld pivot is (%d, %d)", this_ws->pivot.key1, this_ws->pivot.key2);
                  //}
                  while (1){

                      //printf("\nInside reduction pivot for %d is (%d, %d)", i, this_pivot.key1, this_pivot.key2);

                      //printf("\nLength of scratch is %d", V_temp_len[scratch]);

                      o_min = self->g_n_valid_edges;
                      v_min = 0;

                      empty = 1;

                      //if (this_ws->len > 1000){
                      //  printf("%d, ", this_ws->len);
                      //}

                      for ( mm = 0; mm < this_ws->len; mm++){
                            
                          
                          if (orig[mm].low.key1 == self->g_n_valid_edges){
                            continue;
                          }

                          if ((orig[mm].low.key1 == this_pivot->key1)\
                              && (orig[mm].low.key2 == this_pivot->key2)){
                                
                                find_H2_cohom_next(self, &(orig[mm]));

                          }

                          empty = 0;


                          if ((orig[mm].low.key1 < o_min) \
                            || ((orig[mm].low.key1 == o_min) && (orig[mm].low.key2 < v_min))){
                                
                                  
                                  o_min = orig[mm].low.key1;
                                  v_min = orig[mm].low.key2;
                                  coeff = 1;
                            }
                          else if ((orig[mm].low.key1 == o_min) && (orig[mm].low.key2 == v_min)){
                                
                                coeff = 1 - coeff;
                                //coeff += 1;
                                
                          }

                      }


                      this_pivot->key1 = o_min;
                      this_pivot->key2 = v_min;

                      //if ((this_pivot->key1 == self->g_n_valid_edges) \
                      //  && (!empty)){
                      //      
                      //      printf("\nError, empty but pivot max?");
                      //      getchar();
                      //}

                      if (this_pivot->key1 == self->g_n_valid_edges){
                          
                            empty = 1;
                      }

                      //printf("\nCoeff is %d for low (%d, %d)", coeff, this_pivot.key1, this_pivot.key2);

                      if ((coeff) || (empty)){
                          // We have a new pivot or it is empty
                          break;
                      }
                      
                  }


                  //if (self->g_p_flag){
                  //printf("\nV at end of red loop in serial ");
                  //for (EDGE_ID nn = 0; nn < this_ws->len; nn++){
                  //      printf("\n(%d, %d) has low (%d, %d) coeff %d" , scratch[nn].triangle.key1\
                  //                                              , scratch[nn].triangle.key2\
                  //                                              , scratch[nn].low.key1\
                  //                                              , scratch[nn].low.key2\
                  //                                              , scratch[nn].coeff);
                  //    
                  //}
                  //printf("\nNew pivot is (%d, %d)", this_ws->pivot.key1, this_ws->pivot.key2);
                  //}


                  //orig = scratch;
                  //scratch = 1 - orig;

                  this_ws->flag_non_empty = 1 - empty;

                  if (!this_ws->flag_non_empty){
                    break;
                  }

                 //printf("\nChecking pivot after cohomology reduction with self");
                 //test_simp(self, self->g_cohom_low + ws_counter);

                 // CHECK FOR TRIVIAL PAIR
                 // Get low for triangle <ab, d> in this_pivot
                 coboundary_H2 temp;
                 temp.triangle.key1 = this_pivot->key1;
                 temp.triangle.key2 = self->g_edges_list[this_pivot->key2][1];

                 find_H2_cohom_low(self, &temp);
                 
                 if ((temp.low.key1 == this_pivot->key1)\
                     && (temp.low.key2 == this_pivot->key2)){


                         this_ws->flag_red_w_trivial = 1;
                         this_ws->reduce_with_trivial = temp.triangle;

                         //if (self->g_p_flag)
                         //printf("\nFOUND IN serial as trivial: reduce with (%d, %d)"\
                         //                                     ,temp.triangle.key1\
                         //                                     ,temp.triangle.key2\
                         //                                     );

                         this_ws->flag_red_w_complex = 0;

                         this_ws->flag_append_to_complex = 0;

                         break;

                         //printf("\nReducing with trivial pair (%d, %d)", temp.triangle.key1\
                         //                                              , temp.triangle.key2);
                         //continue;

                 }

                 
                 // Check with reduced complex
                 if (self->g_H2_cohom_pivots_len[this_pivot->key1]){
                      
                      find_pivot = search_H2_cohom_pivots(self->g_H2_cohom_pivots[this_pivot->key1]\
                                    , 0 \
                                    , self->g_H2_cohom_pivots_len[this_pivot->key1] - 1\
                                    , this_pivot->key2 \
                                    , self->g_n_valid_edges);

                      // If this pivot is in red complex...
                      if (find_pivot != self->g_n_valid_edges){
                            
                            this_ws->flag_red_w_trivial = 0;

                            this_ws->flag_red_w_complex = 1;
                            //this_ws->complex_find_pivot = find_pivot;
                            this_ws->reduce_w_bndry = self->g_H2_cohom_pivots[this_pivot->key1][find_pivot].bndry;
                            this_ws->V_col_idx = self->g_H2_cohom_pivots[this_pivot->key1][find_pivot].col_idx;

                            //if (self->g_p_flag)
                            //printf("\nFOUND IN serial as in complex at %d: reduce with (%d, %d) that has pivot (%d, %d) "\
                            //                                  , find_pivot\
                            //                                  , self->g_H2_cohom_pivots[this_pivot->key1][find_pivot].bndry.key1\
                            //                                  , self->g_H2_cohom_pivots[this_pivot->key1][find_pivot].bndry.key2\
                            //                                  , this_pivot->key1\
                            //                                  , this_pivot->key2\
                            //                                  );
                            this_ws->flag_append_to_complex = 0;
                            break;

                      }

                 }

                 m = 0;

            }

      }

}


void* reduce_with_complex_coH2(void* arg){
        
      filtration* self = arg;


      pthread_mutex_lock(&(self->g_thread_lock));

      int tid = ++self->g_thread_id;

      EDGE_ID idx, find_pivot, red_w_column, start_idx, len_V, count;

      EDGE_ID ws_counter;
      simplex reduce_w_bndry;
      EDGE_ID V_col_idx;
      coboundary_H2 temptemp;
      coboundary_H2* temp;
      simplex* this_pivot;
      coboundary_H2_ws* this_ws;

      EDGE_ID  check_len;

      pthread_mutex_unlock(&(self->g_thread_lock));

      for (;;){

          pthread_mutex_lock(&(self->g_thread_lock));

          self->g_sleeping_threads++;
          self->g_processed_threads++;

          if (self->g_sleeping_threads == self->g_cpu_count){

              pthread_cond_signal(&(self->g_start_boss));
          }


          pthread_cond_wait(&(self->g_start_workers), &(self->g_thread_lock));

          if (self->g_delete_threads){
            pthread_mutex_unlock(&(self->g_thread_lock));
            pthread_exit(NULL);
          }

          self->g_sleeping_threads--;

          pthread_mutex_unlock(&(self->g_thread_lock));


          for (ws_counter = self->g_jobs[tid - 1]; ws_counter < self->g_jobs[tid]; ws_counter++){


                this_ws = self->g_V_ws_H2_info + ws_counter;

                this_pivot = &(this_ws->pivot);

                coboundary_H2* orig = self->g_V_ws_H2[ws_counter];

                //if ((orig[0].triangle.key1 == 195055) && (orig[0].triangle.key2 == 5069)){
                //      self->g_p_flag = 1;
                //}
                //else{
                //      self->g_p_flag = 0;
                //}
                
                //if ((orig[0].triangle.key1 == 233068) && (orig[0].triangle.key2 == 1385)){
                //      self->g_p_flag = 0;
                //}
                //else{
                //      self->g_p_flag = 0;
                //}
                

                if (!this_ws->flag_non_empty){
                    // We are sure that we will exit only if there is no reduction
                    // required with existing complex or with trivial pair
                    this_ws->flag_red_w_complex = 0;
                    this_ws->flag_red_w_trivial = 0;
                    this_ws->flag_append_to_complex = 0;
                    continue;
                }


                if (this_ws->flag_red_w_trivial){
                      
                      reduce_w_bndry = this_ws->reduce_with_trivial;
                      V_col_idx = 0;

                      //if (self->g_p_flag){
                      //  printf("\nReducing with trivial found in self");
                      //  printf("\nreducing with triangle (%d,%d)", reduce_w_bndry.key1, reduce_w_bndry.key2);
                      //  printf("\nV_col_idx %d", V_col_idx);
                      //}
                      
                      // We are sure that we will exit only if there is no reduction
                      // required with existing complex or with trivial pair
                      this_ws->flag_red_w_complex = 0;
                      this_ws->flag_red_w_trivial = 0;
                      this_ws->flag_append_to_complex = 1;

                      
                }
                else if (this_ws->flag_red_w_complex){

                      reduce_w_bndry = this_ws->reduce_w_bndry;

                      V_col_idx = this_ws->V_col_idx;

                      //if (self->g_p_flag){
                      //  printf("\nReducing with complex a %d found in self", this_ws->complex_find_pivot);
                      //  printf("\nreducing with triangle (%d,%d)", reduce_w_bndry.key1, reduce_w_bndry.key2);
                      //  printf("\ncurrent pivot is (%d,%d)", this_ws->pivot.key1, this_ws->pivot.key2);
                      //  printf("\nV_col_idx %d", V_col_idx);
                      //}
                      // We are sure that we will exit only if there is no reduction
                      // required with existing complex or with trivial pair
                      this_ws->flag_red_w_complex = 0;
                      this_ws->flag_red_w_trivial = 0;
                      this_ws->flag_append_to_complex = 1;

                }
                else{


                      // We are sure that we will exit only if there is no reduction
                      // required with existing complex or with trivial pair
                      this_ws->flag_red_w_complex = 0;
                      this_ws->flag_red_w_trivial = 0;
                      this_ws->flag_append_to_complex = 1;

                      // Check with trivial pair


                      temptemp.triangle.key1 = this_pivot->key1;
                      
                      temptemp.triangle.key2 = self->g_edges_list[this_pivot->key2][1];

                      find_H2_cohom_low(self, &(temptemp));

                      if ((temptemp.low.key1 == this_pivot->key1)\
                          &&(temptemp.low.key2 == this_pivot->key2)){

                            reduce_w_bndry = temptemp.triangle;
                            V_col_idx = 0;

                            //if (self->g_p_flag){
                            //  printf("\nReducing with trivial found here");
                            //  printf("\nreducing with triangle (%d,%d)", reduce_w_bndry.key1, reduce_w_bndry.key2);
                            //  printf("\nV_col_idx %d", V_col_idx);
                            //}
                      }
                      else{


                            if (!self->g_H2_cohom_pivots_len[this_pivot->key1])
                              continue;

                            idx = search_H2_cohom_pivots(self->g_H2_cohom_pivots[this_pivot->key1]\
                                                , 0 \
                                                , self->g_H2_cohom_pivots_len[this_pivot->key1] - 1\
                                                , this_pivot->key2 \
                                                , self->g_n_valid_edges);
                            

                            if (idx == self->g_n_valid_edges){

                              continue;

                            }

                            reduce_w_bndry = self->g_H2_cohom_pivots[this_pivot->key1][idx].bndry;
                            V_col_idx = self->g_H2_cohom_pivots[this_pivot->key1][idx].col_idx;

                            //if (self->g_p_flag){
                            //  printf("\nReducing with complex found here");
                            //  printf("\nreducing with triangle (%d,%d)", reduce_w_bndry.key1, reduce_w_bndry.key2);
                            //  printf("\nV_col_idx %d", V_col_idx);
                            //}


                      }


                }

                  
                    

                //self->g_V_ws_flag_R_nonzero[ws_counter] = 1;






                //printf("\rreducing %d with complex", ws_counter);
                //getchar();


                while(1){

                      //printf("\nReducing %d with %d and V_col_idx is %d", orig[0].o_ab\
                      //                                                  , self->g_H1_cohom_pivots[this_pivot->key1][idx].bndry\
                      //                                                  , self->g_H1_cohom_pivots[this_pivot->key1][idx].col_idx);

                      //V_col_idx = self->g_H1_cohom_pivots[this_pivot->key1][idx].col_idx;
                      
                      //if (self->g_p_flag){
                      //printf("\nV at beginning of red loop ");
                      //for (EDGE_ID nn = 0; nn < this_ws->len; nn++){
                      //      printf("\n(%d, %d) has low (%d, %d) " , orig[nn].triangle.key1\
                      //                                              , orig[nn].triangle.key2\
                      //                                              , orig[nn].low.key1\
                      //                                              , orig[nn].low.key2\
                      //                                              );
                      //    
                      //}
                      //}
                      
                      check_len = this_ws->len + 1;

                      if (V_col_idx){

                            EDGE_ID start = self->g_V_col_indices[V_col_idx];
                            //EDGE_ID V_len = V_col_indices[V_col_idx+1] - start;
                            EDGE_ID end = self->g_V_col_indices[V_col_idx+1];
                        
                            check_len += end - start;
                              
                      }

                      if (check_len > this_ws->max_len){

                            //printf("\nreallocating");


                            this_ws->max_len = check_len + 1000;
                            
                            pthread_mutex_lock(&(self->g_thread_lock));

                            self->g_V_ws_H2[ws_counter] = (coboundary_H2*)realloc(self->g_V_ws_H2[ws_counter]\
                                                                        , this_ws->max_len*sizeof(coboundary_H2));

                            pthread_mutex_unlock(&(self->g_thread_lock));

                            orig = self->g_V_ws_H2[ws_counter];


                      }


                      orig[this_ws->len].triangle = reduce_w_bndry;
                      find_H2_cohom_greater(self, &(orig[this_ws->len]), this_pivot);
                      this_ws->len++;




                      // IF the V was recorded, add the bndries
                      if (V_col_idx){
                          //printf("\nAdding remaining V from complex");

                          for (EDGE_ID nn = self->g_V_col_indices[V_col_idx]; nn < self->g_V_col_indices[V_col_idx+1]; nn++){

                              orig[this_ws->len].triangle = self->g_V_sparse_H2[nn];
                              find_H2_cohom_greater(self, &(orig[this_ws->len]), this_pivot);
                              this_ws->len++;

                          }
                          
                      }




                      //if (self->g_p_flag){
                      //printf("\nV after adding V and before reduction ");
                      //for (EDGE_ID nn = 0; nn < this_ws->len; nn++){
                      //      printf("\n(%d, %d) has low (%d, %d)" , orig[nn].triangle.key1\
                      //                                              , orig[nn].triangle.key2\
                      //                                              , orig[nn].low.key1\
                      //                                              , orig[nn].low.key2\
                      //                                              );
                      //    
                      //}
                      //}



                      // Now we have to reduce
                      int coeff, empty;
                      EDGE_ID mm;

                      while (1){


                          EDGE_ID o_min = self->g_n_valid_edges;
                          EDGE_ID v_min = 0;

                          empty = 1;

                          //if (this_ws->len > 1000){
                          //  printf("\n(%d, %d): %d, ", orig[0].triangle.key1, orig[0].triangle.key2, this_ws->len);
                          //}

                          for (mm = 0; mm < this_ws->len; mm++){
                                
                              
                              if (orig[mm].low.key1 == self->g_n_valid_edges){
                                continue;
                              }

                              if ((orig[mm].low.key1 == this_pivot->key1)\
                                  && (orig[mm].low.key2 == this_pivot->key2)){
                                    
                                    find_H2_cohom_next(self, &(orig[mm]));

                              }

                              empty = 0;


                              if ((orig[mm].low.key1 < o_min) \
                                || ((orig[mm].low.key1 == o_min) && (orig[mm].low.key2 < v_min))){
                                    
                                      
                                      o_min = orig[mm].low.key1;
                                      v_min = orig[mm].low.key2;
                                      coeff = 1;
                                }
                              else if ((orig[mm].low.key1 == o_min) && (orig[mm].low.key2 == v_min)){
                                    
                                    coeff = 1 - coeff;
                                    
                              }

                          }


                          this_pivot->key1 = o_min;
                          this_pivot->key2 = v_min;

                          if (this_pivot->key1 == self->g_n_valid_edges){
                              empty = 1;
                          }
                          


                          if ((coeff) || (empty)){
                              // We have a new pivot or it is empty
                              break;
                          }
                          
                      }


                      //getchar();

                      this_ws->flag_non_empty = 1 - empty;


                      //if (self->g_p_flag){
                      //printf("\nAfter reduction");
                      //for (EDGE_ID nn = 0; nn < this_ws->len; nn++){
                      //      printf("\n(%d, %d) has low (%d, %d)" , orig[nn].triangle.key1\
                      //                                              , orig[nn].triangle.key2\
                      //                                              , orig[nn].low.key1\
                      //                                              , orig[nn].low.key2\
                      //                                              );
                      //    
                      //}

                      //printf("\nnew pivot for (%d,%d) is pivot (%d, %d) with parameter %f\n"\
                      //                                                    , orig[0].triangle.key1\
                      //                                                    , orig[0].triangle.key2\
                      //                                                    , this_ws->pivot.key1\
                      //                                                    , this_ws->pivot.key2\
                      //                                                    , self->g_edge_parameter[this_ws->pivot.key1]\
                      //                                                    );
                      //getchar();

                      //}

                      if (!this_ws->flag_non_empty){
                            break;
                      }

                      
                      // CHECK FOR TRIVIAL PAIR
                      // Get low for maximum triangle <ab, d> in this_pivot
                      temptemp.triangle.key1 = this_pivot->key1;
                      temptemp.triangle.key2 = self->g_edges_list[this_pivot->key2][1];

                      find_H2_cohom_low(self, &temptemp);
                      
                      // Check if the low of this triangle is same as self->g_this_pivot
                      if ((temptemp.low.key1 == this_pivot->key1)\
                          && (temptemp.low.key2 == this_pivot->key2)){

                              reduce_w_bndry = temptemp.triangle;
                              V_col_idx = 0;
                              //if (self->g_p_flag){
                              //printf("\nReducing with trivial pair (%d, %d)", temptemp.triangle.key1\
                              //                                              , temptemp.triangle.key2);

                              //}
                              continue;

                      }



                      // If this low is not a pivot
                      if (!self->g_H2_cohom_pivots_len[this_pivot->key1]){
                          break;
                      }

                      idx = search_H2_cohom_pivots(self->g_H2_cohom_pivots[this_pivot->key1]\
                                          , 0 \
                                          , self->g_H2_cohom_pivots_len[this_pivot->key1] - 1\
                                          , this_pivot->key2 \
                                          , self->g_n_valid_edges);

                      //if (self->g_p_flag){
                      //for (EDGE_ID nn = 0; nn < this_ws->len; nn++){
                      //      printf("\n(%d, %d) has low (%d, %d) coeff %d" , orig[nn].triangle.key1\
                      //                                              , orig[nn].triangle.key2\
                      //                                              , orig[nn].low.key1\
                      //                                              , orig[nn].low.key2\
                      //                                              , orig[nn].coeff);
                      //    
                      //}

                      //printf("\nnew pivot for (%d,%d) is pivot (%d, %d)\n"\
                      //                                                    , orig[0].triangle.key1\
                      //                                                    , orig[0].triangle.key2\
                      //                                                    , this_ws->pivot.key1\
                      //                                                    , this_ws->pivot.key2);
                      //getchar();

                      //}

                      if (idx == self->g_n_valid_edges){
                        break;
                      }
                      
                      reduce_w_bndry = self->g_H2_cohom_pivots[this_pivot->key1][idx].bndry;
                      V_col_idx = self->g_H2_cohom_pivots[this_pivot->key1][idx].col_idx;
                      //if (self->g_p_flag){
                      //    printf("\nReducing with complex with (%d, %d)", reduce_w_bndry.key1, reduce_w_bndry.key2);
                      //}

                       
                }
                //if (self->g_p_flag){
                //    printf("\nDone reducing %d with complex", ws_counter);
                //}
                
                  
                  
          }

      }
        
}




void update_V_coH2(filtration* self, int ws_counter){


    coboundary_H2_ws* this_ws = self->g_V_ws_H2_info + ws_counter;

    coboundary_H2* orig = self->g_V_ws_H2[ws_counter];

    //printf("\nAdding pivot of (%d, %d) for the triangle (%d, %d)"\
    //                                          , this_ws->pivot.key1\
    //                                          , this_ws->pivot.key2\
    //                                          , orig[0].triangle.key1\
    //                                          , orig[0].triangle.key2\
    //                                          );
    
    //getchar();

    // Check space in V sparse H2
    if ((this_ws->len + self->g_V_sparse_ptr) > self->g_V_sparse_max){

          self->g_V_sparse_max = self->g_V_sparse_ptr + this_ws->len + 10000;
          self->g_V_sparse_H2 = (simplex*)realloc(self->g_V_sparse_H2, self->g_V_sparse_max*sizeof(simplex));

    }


    self->g_V_sparse_beg_ptr = self->g_V_sparse_ptr;


    if (this_ws->len > 2){
          
          sorter6_tim_sort(orig, this_ws->len);          

          int count = 1;
          for (EDGE_ID mm = 1; mm < this_ws->len-1; mm++){
                
                if ((orig[mm+1].triangle.key1 != orig[mm].triangle.key1) \
                    || (orig[mm+1].triangle.key2 != orig[mm].triangle.key2)){
                      
                    if (count){
                        self->g_V_sparse_H2[self->g_V_sparse_ptr++] = orig[mm].triangle;
                    }
                    count = 1;
                }
                else{
                    count = 1 - count;
                }
                
          }

          if (count){

                self->g_V_sparse_H2[self->g_V_sparse_ptr++] = orig[this_ws->len-1].triangle;

          }

    }
    else if (this_ws->len == 2){
          
          self->g_V_sparse_H2[self->g_V_sparse_ptr++] = orig[1].triangle;
          
    }





    EDGE_ID red_col;

          
    if ((self->g_V_sparse_ptr - self->g_V_sparse_beg_ptr) > 0){

          red_col = self->g_V_col_indices_ptr;

          if (self->g_V_col_indices_ptr+1 == self->g_V_col_indices_max){
                
                self->g_V_col_indices_max += 100;
                self->g_V_col_indices = (EDGE_ID*)realloc(self->g_V_col_indices
                                                      , self->g_V_col_indices_max*sizeof(EDGE_ID));
          
          }


          self->g_V_col_indices[self->g_V_col_indices_ptr] = self->g_V_sparse_beg_ptr;
          self->g_V_col_indices[self->g_V_col_indices_ptr+1] = self->g_V_sparse_ptr;

          self->g_V_col_indices_ptr++;
          
          
    }
    else{
          //self->g_counter++;
          red_col = 0;
          //printf("\nSkipped %d", self->g_counter);
    }


    // ADDING THE LOW
    //EDGE_ID key1 = self->g_V_ws_pivot[ws_counter].key1;
    //EDGE_ID key2 = self->g_V_ws_pivot[ws_counter].key2;

    //printf("\nAdding pivot (%d, %d) for (%d,%d)", simp->key1, simp->key2, bndry.key1, bndry.key2);

    //FILE* fp = fopen("pivotsH2.txt", "a");
    //fprintf(fp, "%d, %d, %d, %d\n", simp->key1, simp->key2, bndry.key1, bndry.key2);
    //fclose(fp);

    if (self->g_H2_cohom_pivots_len[this_ws->pivot.key1]\
                            == self->g_H2_cohom_pivots_max_len[this_ws->pivot.key1]){
          
          self->g_H2_cohom_pivots_max_len[this_ws->pivot.key1] += 5;
          self->g_H2_cohom_pivots[this_ws->pivot.key1] = (H2_cohom_pivots*)realloc( \
                          self->g_H2_cohom_pivots[this_ws->pivot.key1] \
                          , self->g_H2_cohom_pivots_max_len[this_ws->pivot.key1]*sizeof(H2_cohom_pivots));
          //self->g_cohom_ALL_pivots_len += 5;

    }
          


    EDGE_ID old_ptr = self->g_H2_cohom_pivots_len[this_ws->pivot.key1];
    EDGE_ID new_ptr = self->g_H2_cohom_pivots_len[this_ws->pivot.key1];

    while (old_ptr){
          
          old_ptr--;
          
          if (self->g_H2_cohom_pivots[this_ws->pivot.key1][old_ptr].key2 > this_ws->pivot.key2){

                self->g_H2_cohom_pivots[this_ws->pivot.key1][new_ptr--] =\
                                                       self->g_H2_cohom_pivots[this_ws->pivot.key1][old_ptr];
                continue;

          }
          break;

    }

    self->g_H2_cohom_pivots[this_ws->pivot.key1][new_ptr].key2 = this_ws->pivot.key2;
    self->g_H2_cohom_pivots[this_ws->pivot.key1][new_ptr].col_idx = red_col;
                            
    self->g_H2_cohom_pivots[this_ws->pivot.key1][new_ptr].bndry = orig[0].triangle;

    self->g_H2_cohom_pivots_len[this_ws->pivot.key1]++;

    
    // PERS PAIRS
    // Add non-zero barcodes
        
    PAR birth = self->g_edge_parameter[orig[0].triangle.key1];
    PAR death = self->g_edge_parameter[this_ws->pivot.key1];
    if (birth != death){

           //printf("\nNon trivial pers pair (%f, %f)", birth, death);

           //printf("\nAdding pivot of (%d, %d) for the triangle (%d, %d)"\
           //                                   , this_ws->pivot.key1\
           //                                   , this_ws->pivot.key2\
           //                                   , orig[0].triangle.key1\
           //                                   , orig[0].triangle.key2\
           //                                   );
           //getchar();

          //if (self->g_p_flag){
          //  printf("\nPair (%d, %d), (%d, %d)", orig[0].triangle.key1, orig[0].triangle.key2\
          //                                    , this_ws->pivot.key1, this_ws->pivot.key1);
          //  printf("\nError?");
          //  getchar();
          //}
          
          //if (birth == 0.844754){
            //printf("\nAdding (%f, %f)", birth, death);
            //printf("\n(%d, %d) is pivot of (%d, %d)"\
            //                            ,this_ws->pivot.key1\
            //                            ,this_ws->pivot.key2\
            //                            ,orig[0].triangle.key1\
            //                            ,orig[0].triangle.key2\
            //                            );
            //getchar();
          //}
          if (self->g_H2_pers_pairs_len+2 == self->g_H2_pers_pairs_max_len){
                self->g_H2_pers_pairs_max_len += 1000;
                self->g_H2_pers_pairs = (PAR*)realloc(self->g_H2_pers_pairs\
                                                    , self->g_H2_pers_pairs_max_len*sizeof(PAR));
          
          }
          self->g_H2_pers_pairs[self->g_H2_pers_pairs_len++] = birth;
          self->g_H2_pers_pairs[self->g_H2_pers_pairs_len++] = death;
          
    }
    



}



void update_V_coH1(filtration* self){



    //printf("\nAdding pivot of (%d, %d) for the triangle (%d, %d)"\
    //                                          , this_ws->pivot.key1\
    //                                          , this_ws->pivot.key2\
    //                                          , orig[0].triangle.key1\
    //                                          , orig[0].triangle.key2\
    //                                          );
    
    //getchar();



    self->g_V_sparse_beg_ptr = self->g_V_sparse_ptr;

    //if (self->g_new_debug){
    //      
    //    printf("\n V len is %d", self->g_V_temp_len);
    //    getchar();
    //      
    //}

    if (self->g_V_temp_len > 2){
          
          // Check space in V sparse H2
          if ((self->g_V_temp_len + self->g_V_sparse_ptr) + 1 > self->g_V_sparse_max){

                self->g_V_sparse_max = self->g_V_sparse_ptr + self->g_V_temp_len + 10000;
                self->g_V_sparse_H1 = (EDGE_ID*)realloc(self->g_V_sparse_H1\
                                                    , self->g_V_sparse_max*sizeof(EDGE_ID));

          }
          
          sorter5_tim_sort(self->g_V_temp, self->g_V_temp_len);          

          int count = 1;
          for (EDGE_ID mm = 1; mm < self->g_V_temp_len-1; mm++){
                
                if (self->g_V_temp[mm+1].o_ab != self->g_V_temp[mm].o_ab){
                      
                    if (count){
                        self->g_V_sparse_H1[self->g_V_sparse_ptr++] = self->g_V_temp[mm].o_ab;
                    }
                    count = 1;
                }
                else{
                    count = 1 - count;
                }
                
          }

          if (count){

                self->g_V_sparse_H1[self->g_V_sparse_ptr++] = self->g_V_temp[self->g_V_temp_len-1].o_ab;

          }

    }
    else if (self->g_V_temp_len == 2){
          
          // Check space in V sparse H2
          if ( self->g_V_sparse_ptr + 2 > self->g_V_sparse_max){

                self->g_V_sparse_max = self->g_V_sparse_ptr + self->g_V_temp_len + 10000;
                self->g_V_sparse_H1 = (EDGE_ID*)realloc(self->g_V_sparse_H1\
                                                    , self->g_V_sparse_max*sizeof(EDGE_ID));

          }

          self->g_V_sparse_H1[self->g_V_sparse_ptr++] = self->g_V_temp[1].o_ab;
          
    }

    EDGE_ID red_col;

    //if (self->g_new_debug){
    //    printf("\nself->g_V_col_indices_ptr %d", self->g_V_col_indices_ptr);
    //}


    if ((self->g_V_sparse_ptr - self->g_V_sparse_beg_ptr) > 0){

          red_col = self->g_V_col_indices_ptr;

          if (self->g_V_col_indices_ptr+1 == self->g_V_col_indices_max){
                
                self->g_V_col_indices_max += 1000;
                self->g_V_col_indices = (EDGE_ID*)realloc(self->g_V_col_indices
                                                      , self->g_V_col_indices_max*sizeof(EDGE_ID));
          
          }


          self->g_V_col_indices[self->g_V_col_indices_ptr] = self->g_V_sparse_beg_ptr;
          self->g_V_col_indices[self->g_V_col_indices_ptr+1] = self->g_V_sparse_ptr;

          self->g_V_col_indices_ptr++;


    }
    else{

          red_col = 0;

    }

    //if (self->g_new_debug){
    //      
    //    printf("\nred col is %d", red_col);
    //    getchar();
    //      
    //}



    // ADDING THE LOW
    //EDGE_ID key1 = self->g_V_ws_pivot[ws_counter].key1;
    //EDGE_ID key2 = self->g_V_ws_pivot[ws_counter].key2;

    //printf("\nAdding pivot (%d, %d) for (%d,%d)", simp->key1, simp->key2, bndry.key1, bndry.key2);

    //FILE* fp = fopen("pivotsH2.txt", "a");
    //fprintf(fp, "%d, %d, %d, %d\n", simp->key1, simp->key2, bndry.key1, bndry.key2);
    //fclose(fp);

    if (self->g_H1_cohom_pivots_len[self->g_this_pivot.key1]\
                            == self->g_H1_cohom_pivots_max_len[self->g_this_pivot.key1]){
          
          self->g_H1_cohom_pivots_max_len[self->g_this_pivot.key1] += 5;
          self->g_H1_cohom_pivots[self->g_this_pivot.key1] = (H1_cohom_pivots*)realloc( \
                          self->g_H1_cohom_pivots[self->g_this_pivot.key1] \
                          , self->g_H1_cohom_pivots_max_len[self->g_this_pivot.key1]*sizeof(H1_cohom_pivots));
          //self->g_cohom_ALL_pivots_len += 5;

    }
          


    EDGE_ID old_ptr = self->g_H1_cohom_pivots_len[self->g_this_pivot.key1];
    EDGE_ID new_ptr = self->g_H1_cohom_pivots_len[self->g_this_pivot.key1];

    while (old_ptr){
          
          old_ptr--;
          
          if (self->g_H1_cohom_pivots[self->g_this_pivot.key1][old_ptr].key2 > self->g_this_pivot.key2){

                self->g_H1_cohom_pivots[self->g_this_pivot.key1][new_ptr--] =\
                                                       self->g_H1_cohom_pivots[self->g_this_pivot.key1][old_ptr];
                continue;

          }
          break;

    }

    self->g_H1_cohom_pivots[self->g_this_pivot.key1][new_ptr].key2 = self->g_this_pivot.key2;
    self->g_H1_cohom_pivots[self->g_this_pivot.key1][new_ptr].col_idx = red_col;
                            
    self->g_H1_cohom_pivots[self->g_this_pivot.key1][new_ptr].bndry = self->g_V_temp[0].o_ab;

    self->g_H1_cohom_pivots_len[self->g_this_pivot.key1]++;

    
    // PERS PAIRS
    // Add non-zero barcodes
        
    PAR birth = self->g_edge_parameter[self->g_V_temp[0].o_ab];
    PAR death = self->g_edge_parameter[self->g_this_pivot.key1];
    if (birth != death){

           //printf("\nNon trivial pers pair (%f, %f)", birth, death);
           

           if (birth > death){
               printf("\nBirth, death (%lf, %lf)", birth, death);
               printf("\nError %d at pair (%d, %d)", self->g_V_temp[0].o_ab\
                                                    , self->g_this_pivot.key1\
                                                    , self->g_this_pivot.key2);
               getchar();
             
           }


           //printf("\nAdding pivot of (%d, %d) for the edge %d"\
           //                                   , this_ws->pivot.key1\
           //                                   , this_ws->pivot.key2\
           //                                   , orig[0].o_ab\
           //                                   );
           //getchar();

          //if (self->g_p_flag){
          //  printf("\nPair (%d, %d), (%d, %d)", orig[0].triangle.key1, orig[0].triangle.key2\
          //                                    , this_ws->pivot.key1, this_ws->pivot.key1);
          //  printf("\nError?");
          //  getchar();
          //}
          
          //if (birth < 0.5295){
          //  printf("\nAdding (%f, %f)", birth, death);
          //  printf("\n(%d, %d) is pivot of %d"\
          //                              ,this_ws->pivot.key1\
          //                              ,this_ws->pivot.key2\
          //                              ,orig[0].o_ab\
          //                              );
          //  getchar();
          //}


          //printf("\nAdding %.16f, %.16f for edge %d with pivot (%d, %d)"\
          //                                        , birth\
          //                                        , death\
          //                                        , self->g_V_temp[0].o_ab\
          //                                        , self->g_this_pivot.key1\
          //                                        , self->g_this_pivot.key2);


          if (self->g_H1_pers_pairs_len+2 == self->g_H1_pers_pairs_max_len){
                self->g_H1_pers_pairs_max_len += 1000;
                self->g_H1_pers_pairs = (PAR*)realloc(self->g_H1_pers_pairs\
                                              , self->g_H1_pers_pairs_max_len*sizeof(PAR));
          
          }
          self->g_H1_pers_pairs[self->g_H1_pers_pairs_len++] = birth;
          self->g_H1_pers_pairs[self->g_H1_pers_pairs_len++] = death;
          
    }
    





}

//void reduce_ws_H1(filtration* self){
//
//
//
//        
//      self->g_processed_threads = 0;
//
//      pthread_cond_broadcast(&(self->g_start_workers));
//
//      while (self->g_processed_threads != self->g_cpu_count){
//            
//            pthread_cond_wait(&(self->g_start_boss) \
//                            ,&(self->g_thread_lock));
//      }
//
//
//
//      reduce_with_self_H1( \
//                            self \
//                            );
//
//      int count_valid = 0;
//
//      for (int ws_counter=0; ws_counter < self->g_ws_counter; ws_counter++){
//
//            if (self->g_workspace_H1_info[ws_counter].flag_append_to_complex){
//
//                  update_R_H1(self \
//                                , ws_counter\
//                                );
//                  continue;
//                    
//            }
//            
//            if (!self->g_workspace_H1_info[ws_counter].len){continue;}
//
//
//            // Swap R
//            boundary_H1* temp = self->g_workspace_H1[count_valid];
//            self->g_workspace_H1[count_valid] = self->g_workspace_H1[ws_counter];
//            self->g_workspace_H1[ws_counter] = temp;
//
//            // Swap R info
//            boundary_H1_ws temp2 = self->g_workspace_H1_info[count_valid];
//            self->g_workspace_H1_info[count_valid] = self->g_workspace_H1_info[ws_counter];
//            self->g_workspace_H1_info[ws_counter] = temp2;
//
//
//            // At this point, this has to be a non-zero column
//            self->g_workspace_H1_info[count_valid].flag_non_empty = 1;
//
//            count_valid += 1;
//
//      }
//
//      self->g_ws_counter = count_valid;
//
//      //if (dim)
//      //  self->g_H0_MAX = self->g_n_reduced_simplex[dim];
//
//}
//
//void reduce_with_self_H1( \
//                      filtration* self \
//                      ){
//
//    int compare;
//
//    int i, m;
//    int original_i, original_m;
//    int idx;
//
//    EDGE_ID *original_simp_i ,*original_simp_m ,*scratch_simp;
//    EDGE_ID len_i, len_m , simp_max_len_i , simp_max_len_m , pivot_i, pivot_m , count, j, k;
//    count = 0;
//
//
//    for (i=0; i < self->g_ws_counter; i++){
//
//        boundary_H1_ws* this_ws = self->g_workspace_H1_info + i;
//
//
//
//        // If the simplex has already been reduced to 0
//      // then continue
//        if (!this_ws->len){ 
//
//          this_ws->flag_append_to_complex = 0;
//          continue;
//
//        }
//
//        
//        boundary_H1* orig = self->g_workspace_H1[i] + this_ws->original*this_ws->max_len;
//
//        m = 0;
//        while (m < i){
//
//            boundary_H1_ws* m_ws = self->g_workspace_H1_info + m;
//
//            if (!m_ws->len){
//                m++;
//                continue;
//            }
//
//
//            boundary_H1* original_m = self->g_workspace_H1[m] + m_ws->original*m_ws->max_len;
//
//            orig = self->g_workspace_H1[i] + this_ws->original*this_ws->max_len;
//
//
//            if (m_ws->pivot > m_ws->pivot){
//                  
//                  if (this_ws->flag_red_w_complex){
//                        
//                        this_ws->flag_append_to_complex = 0;
//                        break;
//                  }
//                  m++;
//                  continue;
//                  
//            }
//
//
//            if (m_ws->pivot < m_ws->pivot){
//                    m++;
//                    continue;
//            }
//
//            if (!m_ws->flag_append_to_complex){
//                    m++;
//                    continue;
//            }
//
//
//            if (this_ws->len + m_ws->len > this_ws->max_len ){
//
//                //printf("\nReallocating inside self");
//                //simp_max_len_i = len_i + len_m + 1000;
//
//
//                if (this_ws->original){
//                      
//                      for (EDGE_ID mm = 0; mm < this_ws->len; mm++){
//                            orig[mm] = orig[mm + this_ws->max_len];
//                      }
//
//                      this_ws->original = 0;
//
//
//                      
//                }
//
//                this_ws->max_len = this_ws->len + m_ws->len + 1000;
//
//                self->g_workspace_H1[i] = (EDGE_ID*)realloc(self->g_workspace_H1[i]\
//                                                      , 2*this_ws->max_len*sizeof(EDGE_ID));
//
//                orig  = self->g_workspace_H1[i];
//
//
//            }
//
//            boundary_H1* scratch = self->g_workspace_H1[i] + (1-this_ws->original)*this_ws->max_len;
//
//            // Store the result in scratch
//
//            count = 0;
//
//            j = 0;
//            k = 0;
//
//            while ((j < this_ws->len) && (k < m_ws->len)){
//
//                if (orig[j] < original_m[k]){
//                    scratch[count++] = orig[j++];
//                }
//                else if (orig[j] > original_m[k]){
//                    scratch[count++] = original_m[k++];
//                }
//                else{
//                    j++;
//                    k++;
//                }
//
//            }
//
//            while (j < this_ws->len){
//                    scratch[count++] = orig[j++];
//            }
//
//            while (k < m_ws->len){
//                    scratch[count++] = original_m[k++];
//            }
//
//            this_ws->len = count;
//
//            this_ws->original = 1 - this_ws->original;
//
//            if (!count){
//                  
//                  this_ws->flag_append_to_complex = 0;
//                  this_ws->flag_non_empty = 0;
//                  break;
//                  
//            }
//
//            this_ws->pivot = orig[this_ws->len-1];
//
//            // Check if pivot
//
//            //if (self->g_n_reduced_simplex_H0){
//
//             idx = self->g_pivots_H1[this_ws->pivot];
//             // If the pivot is in red complex, then this has to be reduced w/ complex
//             //if (idx != self->g_n_reduced_simplex[self->g_dim_now]){
//             if (idx){
//                   
//                   this_ws->flag_red_w_complex = 1;
//                   this_ws->flag_append_to_complex = 0;
//                   break;
//
//             }
//
//
//            //}
//
//            m = 0;
//
//        }//End of m loop
//
//    }
//
//}//End of red_ws_w_self_single
//
//
//
//void* reduce_with_complex_H1(void* arg){
//      
//      filtration* self = arg;
//
//      pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);
//
//      pthread_mutex_lock(&(self->g_thread_lock));
//
//      int tid = ++self->g_thread_id;
//
//
//      EDGE_ID *simp, *original_simp, *scratch_simp, *red_start_idx, *red_start;
//      EDGE_ID simp_len ,simp_max_len ,max_el ,j, k ,len_a2 ,count;
//
//      //EDGE_ID reduced_col;
//      int i ,reduced_col ,idx;
//
//
//      for (;;){
//
//          self->g_sleeping_threads++;
//          
//          if (self->g_sleeping_threads == self->g_cpu_count)
//              pthread_cond_signal(&(self->g_start_boss));
//
//          pthread_cond_wait(&(self->g_start_workers), &(self->g_thread_lock));
//
//          if (self->g_delete_threads){
//            //printf("\nexiting from thread %d", tid);
//            pthread_mutex_unlock(&(self->g_thread_lock));
//            pthread_exit(NULL);
//          }
//
//          self->g_sleeping_threads--;
//
//          pthread_mutex_unlock(&(self->g_thread_lock));
//
//          for (i = self->g_jobs[tid - 1]; i < self->g_jobs[tid]; i++){
//
//              boundary_H1_ws* this_ws = self->g_workspace_H1_info + i;
//
//              boundary_H1* orig = self->g_workspace_H1[i] \
//                                  + this_ws->original*this_ws->max_len;
//
//              boundary_H1* scratch = self->g_workspace_H1[i] \
//                                  + (1-this_ws->original)*this_ws->max_len;
//                                    
//
//              idx = self->g_pivots_H1[this_ws->pivot];
//
//              while(idx){
//
//                    red_start = self->g_R_col_idx_H1 + idx;
//
//                    len_a2 = self->g_R_col_idx_H1[idx+1] - self->g_R_col_idx_H1[idx];
//
//
//                    if ( this_ws->len + len_a2 > this_ws->max_len){
//
//
//                        if (this_ws->original){
//                              
//                              
//                              for (EDGE_ID mm = 0; mm < this_ws->len; mm++){
//                                    
//                                    orig[mm] = orig[mm + this_ws->max_len];
//                                    
//                              }
//
//                              this_ws->original = 0;
//                              
//                        }
//
//                        this_ws->max_len = this_ws->len + len_a2 + 1000;
//
//                        self->g_workspace_H1[i] = (EDGE_ID*)realloc(self->g_workspace_H1[i]\
//                                                            , 2*this_ws->max_len*sizeof(EDGE_ID));
//
//
//                        orig = self->g_workspace_H1[i];
//
//                        
//                    }
//                    
//                    
//                    scratch = self->g_workspace_H1[i] \
//                                    + (1-this_ws->original)*this_ws->max_len;
//
//                    count = 0;
//                    j = 0;
//                    k = 0;
//
//
//                    while ((j < this_ws->len) && (k < len_a2)){
//
//                        if (orig[j] < red_start[k]){
//                            scratch[count++] = orig[j++];
//                        }
//                        else if (orig[j] > red_start[k]){
//                            scratch[count++] = red_start[k++];
//                        }
//                        else{
//                            j++;
//                            k++;
//                        }
//
//                    }
//
//                    while (j < this_ws->len){
//
//                        scratch[count++] = orig[j++];
//                    }
//
//                    while (k < len_a2){
//
//                        scratch[count++] = red_start[k++];
//                    }
//
//                    this_ws->original = 1 - this_ws->original;
//
//                    this_ws->len = count;
//
//
//                    if (!this_ws->len){
//
//                        //idx = self->g_n_reduced_simplex[self->g_dim_now];
//                        //idx = -1;
//                        this_ws->flag_non_empty = 0;
//                        break;
//
//                    }
//                    else{
//                    
//                        orig = self->g_workspace_H1[i] + this_ws->orig*this_ws->max_len;
//
//                        this_ws->pivot = orig[this_ws->len];
//
//                        idx = self->g_pivots_H1[this_ws->pivot];
//
//                    }
//                    
//
//              }
//
//
//          }
//
//          pthread_mutex_lock(&(self->g_thread_lock));
//
//          self->g_processed_threads++;
//
//            
//      }
//
//
//}
//
//
//
//
//
//void update_R_H1 (filtration* self, int ws_counter){
//      
//
//      boundary_H1_ws* this_ws = self->g_workspace_H1_info + ws_counter;
//      boundary_H1* orig = self->g_workspace_H1[ws_counter] \
//                                    + this_ws->original*this_ws->max_len;
//      
//      // Update R
//      if ((self->g_R_len_H1 + this_ws->len) > self->g_R_max_len_H1){
//            
//            self->g_R_max_len_H1 = self->g_R_len_H1 + this_ws->len;
//
//            self->g_R_H1 = (EDGE_ID*)realloc(self->g_R_H1, self->g_R_max_len_H1*sizeof(EDGE_ID));
//            
//      }
//
//
//      // Update R col idx
//      self->g_R_col_idx_H1_ptr++;
//
//      if (self->g_R_col_idx_H1_ptr == self->g_R_col_idx_max_len_H1 - 1){
//              
//            self->g_R_col_idx_max_len_H1 += 1000;
//
//            self->g_R_col_idx_H1 = (EDGE_ID*)realloc(self->g_R_col_idx_H1\
//                                          , self->g_R_col_idx_max_len_H1*sizeof(EDGE_ID));
//
//
//      }
//
//
//      self->g_pivots_H1[this_ws->pivot] = self->g_R_col_idx_H1_ptr;
//      self->g_R_col_idx_H1[self->g_R_col_idx_H1_ptr] = self->g_R_len_H1;
//
//
//      for (EDGE_ID mm = 0; mm < this_ws->len; mm++){
//            
//            self->g_R_H1[self->g_R_len_H1++] = orig[mm];
//
//      }
//
//      
//      self->g_R_col_idx_H1[self->g_R_col_idx_H1_ptr+1] = self->g_R_len_H1;
//
//
//      
//
//}





void deallocator(filtration* self){

      // Deallocate edges
      free(self->g_edge_parameter);


      for (EDGE_ID mm = 0; mm < self->g_n_valid_edges; mm++){
          
            free(self->g_edges_list[mm]);
            if (self->g_dim_lim > 0){

                if (self->g_H1_cohom_pivots_max_len[mm]){
                      
                      free(self->g_H1_cohom_pivots[mm]);

                }

                if (self->g_dim_lim > 1){

                      if (self->g_H2_cohom_pivots_max_len[mm]){
                            
                            free(self->g_H2_cohom_pivots[mm]);

                      }
                      
                }
                  
            }

      }


      free(self->g_edges_list);

      // Deallocate Neighbors

      for (EDGE_ID mm = 0; mm < self->g_n_vert; mm++){
          
            if (self->g_Neigh_len[mm]){

                free(self->g_Neighbors[mm]);
                free(self->g_Neighbors_e[mm]);

            }

      }

      free(self->g_Neighbors);
      free(self->g_Neighbors_e);
      free(self->g_Neigh_len);



      // Deallocate R0
      free(self->g_pivots_H0);
      free(self->g_H0_pers_file);
      free(self->g_R_sparse_H0);
      free(self->g_R_col_indices_H0);
      free(self->g_edges_with_pivots_H0);


      if (self->g_dim_lim > 0){
          
            free(self->g_coH1_all_lows);
            free(self->g_H1_cohom_pivots);
            free(self->g_H1_cohom_pivots_len);
            free(self->g_H1_cohom_pivots_max_len);
            free(self->g_H1_pers_file);
            free(self->g_H1_pers_pairs);

            free(self->g_V_col_indices);
            free(self->g_this_pivot_thread);
            free(self->g_coeff_thread);
            free(self->g_empty_thread);
            
            if (self->g_dim_lim > 1){
                  
                  free(self->g_H2_cohom_pivots);
                  free(self->g_H2_cohom_pivots_len);
                  free(self->g_H2_cohom_pivots_max_len);
                  free(self->g_H2_pers_file);
                  free(self->g_V_sparse_H2);
                  free(self->g_H2_pers_pairs);
                  
            }

            
      }



      free(self);
      

}

static PyMethodDef DoryMethods[] = {
      
      {"compute_PH", compute_PH, METH_VARARGS, "Compute PH"},
      {NULL, NULL, 0, NULL}

};

static struct PyModuleDef dorymodule = {
      PyModuleDef_HEAD_INIT,
      "pydory", /* name of module*/
      NULL, /* documentation */
      -1, /* ??? */
      DoryMethods
};


PyMODINIT_FUNC PyInit_pydory(void){
      return PyModule_Create(&dorymodule);
}




