#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>

//////////////////
#define VREDUCE1
//////////////////
#define VREDUCE2

//#define SAVEV

//#define COMBIDX

//#define DEBUGCOMBIDX

//#define SAVEPD

//#define PRINT

//

//#define VDEBUG

//#define DEBUGPIVOTS

//#define COH1DEBUG


//#define COMB_IDX(a, b)((a) > (b) ? 
//                            self->g_edges_comb_idx[(EDGE_ID)((a*(a-1))/2 + b)] : \
//                                        ( (a) < (b) ? self->g_edges_comb_idx[(EDGE_ID)((b*(b-1))/2 + a)] \
//                                              : self->g_n_valid_edges))
//




#define COMB_IDX0(a,b) ( a > b ? (EDGE_ID)((a*(a-1))/2 + b) : (EDGE_ID)((b*(b-1))/2 + a) )

#define COMB_IDX(a,b) ( a == b ? self->g_n_valid_edges : self->g_edges_comb_idx[COMB_IDX0(a, b)])


typedef unsigned long long int BIGINT;

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{
    
  VERT_ID a_ptr;
  VERT_ID 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;

      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;

typedef struct{
    
    EDGE_ID max_len;
    EDGE_ID last;
    EDGE_ID* o_ab;
}edges_list;

typedef struct{
    
    EDGE_ID k2;
    EDGE_ID o_ab;
    EDGE_ID a_ptr;
    EDGE_ID b_ptr;
    int flag_next;
}implicit_keys2;


typedef struct{
    
    EDGE_ID k1;

    int flag_empty;

    implicit_keys2* keys2;
    EDGE_ID max_len;
    EDGE_ID last;

}implicit_keys1;


typedef struct{
    
    EDGE_ID k1_ptr;
    EDGE_ID k2_ptr;

    EDGE_ID edge;

    implicit_keys1* keys1;
    edges_list v_edges;

    EDGE_ID max_len;
    EDGE_ID last;

    simplex pivot;


    int flag_first;
    int flag_red_w_complex;
    int flag_red_w_trivial;

    EDGE_ID reduce_w_bndry;
    EDGE_ID V_col_idx;

    int flag_append_to_complex;
    int flag_non_empty;


}coboundary_H1_ws;



typedef struct{
    
    EDGE_ID max_len;
    EDGE_ID last;
    simplex* o_abc;

}triangles_list;

typedef struct{
    
    EDGE_ID k2;
    simplex o_abc;

    VERT_ID a_ptr;
    VERT_ID b_ptr;
    VERT_ID c_ptr;
    int vertex;

    int flag_next;

}coH2_implicit_keys2;


typedef struct{
    
    EDGE_ID k1;

    int flag_empty;

    coH2_implicit_keys2* keys2;
    EDGE_ID max_len;
    EDGE_ID last;

}coH2_implicit_keys1;


typedef struct{
    
    EDGE_ID k1_ptr;
    EDGE_ID k2_ptr;

    simplex triangle;

    coH2_implicit_keys1* keys1;
    triangles_list v_triangles;

    EDGE_ID max_len;
    EDGE_ID last;

    simplex pivot;

    int flag_first;
    int flag_red_w_complex;
    int flag_red_w_trivial;
    simplex reduce_w_bndry;
    EDGE_ID V_col_idx;

    int flag_append_to_complex;
    int flag_non_empty;


}coboundary_H2_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"




#undef SORT_NAME
#undef SORT_TYPE
#undef SORT_CMP

#define SORT_NAME sorter7
//#define SORT_TYPE int64_t
#define SORT_TYPE implicit_keys2

int compare_implicit_keys2(implicit_keys2 s1, implicit_keys2 s2){
    
      if (s1.k2 < s2.k2) return -1;
      else if (s1.k2 > s2.k2) return 1;
      else{

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

      }

 
}

#define SORT_CMP(x, y) compare_implicit_keys2((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 "sort7.h"




#undef SORT_NAME
#undef SORT_TYPE
#undef SORT_CMP

#define SORT_NAME sorter8
//#define SORT_TYPE int64_t
#define SORT_TYPE EDGE_ID

int compare_edges(EDGE_ID s1, EDGE_ID s2){
    
      if (s1 < s2) return -1;
      else if (s1 > s2) return 1;
      else return 0;

 
}

#define SORT_CMP(x, y) compare_edges((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 "sort8.h"


#undef SORT_NAME
#undef SORT_TYPE
#undef SORT_CMP

#define SORT_NAME sorter9
//#define SORT_TYPE int64_t
#define SORT_TYPE coH2_implicit_keys2

int coH2_compare_implicit_keys2(coH2_implicit_keys2 s1, coH2_implicit_keys2 s2){
    
      if (s1.k2 < s2.k2) return -1;
      else if (s1.k2 > s2.k2) return 1;
      else{

            if (s1.o_abc.key1 < s2.o_abc.key1) return -1;
            else if (s1.o_abc.key1 > s2.o_abc.key1) return 1;
            else{

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

      }

 
}

#define SORT_CMP(x, y) coH2_compare_implicit_keys2((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 "sort9.h"


int compare_implicit(implicit_keys2 s1, coboundary_H1 phi){
    
      if (s1.k2 < phi.low.key2) return 0;
      else if (s1.k2 > phi.low.key2) return 1;
      else{

            if (s1.o_ab < phi.o_ab) return 0;
            else return 1;
          

      }

 
}


int coH2_compare_implicit(coH2_implicit_keys2 s1, coboundary_H2 phi){
    
      if (s1.k2 < phi.low.key2) return 0;
      else if (s1.k2 > phi.low.key2) return 1;
      else{

            if (s1.o_abc.key1 < phi.triangle.key1) return 0;
            else if (s1.o_abc.key1 > phi.triangle.key1) return 1;
            else{

                if (s1.o_abc.key2 < phi.triangle.key2) return 0;
                else return 1;

            }

      }

 
}


typedef struct{

    char* filename;
    char* source;
    char* target;


#ifdef SAVEPD
    char* g_H0_pers_file;
    char* g_H1_pers_file;
    char* g_H2_pers_file;
#endif

#ifdef SAVEV
    char* g_coH1_V_file;
    char* g_coH2_V_file;
#endif

    //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;

    VERT_ID g_n_vert;

    EDGE_ID g_n_valid_edges;

    BIGINT g_n_all_simp;

    EDGE_ID** g_edges_list;

    PAR* g_edge_parameter;

#ifdef COMBIDX
    // Combinatorial idx
    EDGE_ID g_n_edges;
    EDGE_ID* g_edges_comb_idx;

#endif


    // 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
    ////////////////////////////////////
   
    EDGE_ID g_this_edge;

    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 workspace
    coboundary_H1_ws* g_V_ws_H1;


    // 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_ws* g_V_ws_H2; 

    
    // 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_input;
    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;
    int g_new_debug2;
    EDGE_ID g_debug_edge;
    simplex g_debug_triangle;
    
} 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);



void compute_num_simplices(filtration* );

// H1 cohomology functions


void update_V_coH1 (filtration*, int);

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 insert_in_implicit_v(filtration* , int, coboundary_H1*, int);
void print_v_implicit(filtration*, int );

void reduce_ws_coH1(filtration* );
void reduce_with_self_coH1(filtration* );
void* reduce_with_complex_coH1(void* );
void reduce_hash_table_coH1(filtration*, int );

void coH2_insert_in_implicit_v(filtration*, int , coboundary_H2* , int );
void reduce_hash_table_coH2(filtration*, int );
void coH2_print_v_implicit(filtration*, int );


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 update_V_coH2(filtration* , int );
void add_H2_pivot(filtration*, simplex, simplex, EDGE_ID);

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){

     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;
     //////////////////////////////////////////////////////
     //////////////////////////////////////////////////////

     //int file_len = strlen(argv[1]) + 4;

     //printf("\nfile_len is %d", file_len);
     

     //self->g_file_prefix = strdup(argv[5]);


     //self->filename = (char*)malloc(file_len*sizeof(char));

     //self->filename  = strcat(argv[1], ".csv");

      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, argv[1]);
     //strcat(duplicate, ".csv");


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

#if defined(SAVEPD) || defined(SAVEV)

     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);

#ifdef SAVEV
          strcpy(duplicate, self->g_target);
          strcat(duplicate, "coH1_V_data.txt");
          self->g_coH1_V_file = strdup(duplicate);
#endif

          if (self->g_dim_lim > 1){

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

#ifdef SAVEV
               strcpy(duplicate, self->g_target);
               strcat(duplicate, "coH2_V_data.txt");
               self->g_coH2_V_file = strdup(duplicate);
#endif

          }

     }

#endif

     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 (VERT_ID 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);    

     printf("\nNumber of vertices %d", self->g_n_vert);
     mergeSort(self->g_edge_parameter, self->g_edges_list, 0, self->g_n_valid_edges-1);
     printf("\nSorted %d edges\n", self->g_n_valid_edges);
     //exit(0);





     clock_gettime(CLOCK_MONOTONIC, &finish_wall_clock);
     self->g_timer_process_input = (finish_wall_clock.tv_sec - start_wall_clock.tv_sec);
     self->g_timer_process_input += (finish_wall_clock.tv_nsec - start_wall_clock.tv_nsec) / 1000000000.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));

     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]);

          }
          
     }


#ifdef COMBIDX
     self->g_n_edges = (EDGE_ID)((self->g_n_vert) * (self->g_n_vert-1))/2;

     self->g_edges_comb_idx = (EDGE_ID*)malloc(self->g_n_edges*sizeof(EDGE_ID));

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

          self->g_edges_comb_idx[mm] = self->g_n_valid_edges;
          
     }

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

          
          EDGE_ID idx = COMB_IDX0(self->g_edges_list[mm][0], self->g_edges_list[mm][1]);

          self->g_edges_comb_idx[idx] = mm;
#ifdef DEBUGCOMBIDX
          VERT_ID idx2 = search_Neighbors(self\
                                          , self->g_edges_list[mm][0]\
                                          , self->g_edges_list[mm][1]\
                                          , 0\
                                          , self->g_Neigh_len[self->g_edges_list[mm][0]] - 1);
          if (idx2 == self->g_n_vert){

              if (idx != self->g_n_valid_edges){
                  printf("\nERRRRROR 0");
                  getchar();
              }

          }
          else{
              if (self->g_Neighbors[self->g_edges_list[mm][0]][idx2].order != self->g_edges_comb_idx[idx]){
                  printf("\nERRRRROR 1");
                  getchar();

              }
          }

#endif

     }
#endif

     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);

     //compute_num_simplices(self);
     //exit(1);

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
//            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);

#ifdef SAVEPD
     // 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);

#endif

#ifdef PRINT
 
     printf("\nPers pairs in dim 0");
     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]){
                     printf("\n%.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]){
                       printf("\n%.12lf,", self->g_edge_parameter[it]);
                    }
                    
              }
     }
      
#endif


     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;


     ////////////////////////////////////////////////////
     // INITIALIZE WORKSPACE
     ////////////////////////////////////////////////////
     self->g_cohom_ws_size = 100;
     self->g_V_ws_H1 = (coboundary_H1_ws*)malloc(self->g_cohom_ws_size*sizeof(coboundary_H1_ws));
     for (int mm = 0; mm < self->g_cohom_ws_size; mm++){
          
          self->g_V_ws_H1[mm].max_len = 10;
          self->g_V_ws_H1[mm].last = 0;
          self->g_V_ws_H1[mm].keys1 = (implicit_keys1*)malloc(self->g_V_ws_H1[mm].max_len*sizeof(implicit_keys1));

          for (EDGE_ID nn = 0; nn < self->g_V_ws_H1[mm].max_len; nn++){
                
                self->g_V_ws_H1[mm].keys1[nn].max_len = 10;
                self->g_V_ws_H1[mm].keys1[nn].last = 0;
                self->g_V_ws_H1[mm].keys1[nn].flag_empty = 1;

                self->g_V_ws_H1[mm].keys1[nn].keys2 =\
                                        (implicit_keys2*)malloc(self->g_V_ws_H1[mm].keys1[nn].max_len*sizeof(implicit_keys2));

          }

          self->g_V_ws_H1[mm].v_edges.max_len = 100;
          self->g_V_ws_H1[mm].v_edges.last = 0;
          self->g_V_ws_H1[mm].v_edges.o_ab = (EDGE_ID*)malloc(self->g_V_ws_H1[mm].v_edges.max_len*sizeof(EDGE_ID));
          

     }

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


     // 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));



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


     ////////////////////////////////////////////////////////////////
     // Allocate jobs/threads for parallel coH1
     ////////////////////////////////////////////////////////////////
     
     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_coH1 \
                                , (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]);
          }

     }



     self->g_this_edge = self->g_n_valid_edges;

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

     //getchar();

    
     
     self->g_new_debug = 0;
     self->g_new_debug2 = 0;

     self->g_ws_counter = 0;

     //self->g_debug_edge = self->g_n_valid_edges;
     self->g_debug_edge = 307605;

     while(self->g_this_edge){
          
          self->g_this_edge--;

          //if (self->g_this_edge%10000 == 0){
          //  printf("\nProcessing edge %d", self->g_this_edge);
          //}

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

          if (self->g_edges_with_pivots_H0[self->g_this_edge]){
            //This edge has a pivot in H0. So, skip it. Continue;
            //skip++;
            
#ifdef COH1DEBUG
              if (self->g_this_edge == self->g_debug_edge ){
                printf("\nskipping because cleared. so, cannot have anything relevant");
              }
#endif
            continue;
          }
          ///////////////////////////////////////////////////
          ///////////////////////////////////////////////////


          if (self->g_coH1_all_lows[self->g_this_edge].low.key1 == self->g_n_valid_edges){
            // This edge has no coboundary
            //if (self->g_new_debug){
            //  printf("\nno cob, skipping");
            //}
              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++] = \
                                                   self->g_edge_parameter[self->g_this_edge];

              self->g_H1_pers_pairs[self->g_H1_pers_pairs_len++] = -1;
#ifdef COH1DEBUG
              if (self->g_this_edge == self->g_debug_edge ){
                printf("\nskipping because has no cob");
              }
#endif
            continue;
          }


          // This is a trivial pair
          if (self->g_coH1_all_lows[self->g_this_edge].low.key1 == self->g_this_edge){

#ifdef COH1DEBUG
              if (self->g_this_edge == self->g_debug_edge ){
                printf("\nis a trivial pers pair. dont have to add pivot.");
              }
#endif
            // I DO NOT KNOW WHY I HAVE THIS HERE
            //self->g_edges_with_pivots_H0[self->g_this_edge]  = 10;

            continue;

          }

#ifdef COH1DEBUG
          if (self->g_this_edge == self->g_debug_edge ){
              printf("\nhave to start reduction");
              self->g_new_debug = 1;
              self->g_new_debug2 = 1;
          }
#endif
          coboundary_H1_ws* this_ws = self->g_V_ws_H1 + self->g_ws_counter;


          this_ws->edge = self->g_this_edge;

          this_ws->pivot = self->g_coH1_all_lows[this_ws->edge].low;

          this_ws->flag_first = 1;
          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;

          
          // FIRST ENTRY IN hash-table

          this_ws->k1_ptr = 0;
          this_ws->k2_ptr = 0;
          this_ws->last = 1;

          this_ws->keys1[0].last = 1;
          this_ws->keys1[0].flag_empty = 0;
          this_ws->keys1[0].k1 = this_ws->pivot.key1;

          this_ws->keys1[0].keys2[0].k2 = this_ws->pivot.key2;
          this_ws->keys1[0].keys2[0].o_ab = self->g_coH1_all_lows[this_ws->edge].o_ab;
          this_ws->keys1[0].keys2[0].a_ptr = self->g_coH1_all_lows[this_ws->edge].a_ptr;
          this_ws->keys1[0].keys2[0].b_ptr = self->g_coH1_all_lows[this_ws->edge].b_ptr;
          this_ws->keys1[0].keys2[0].flag_next = 1;

          this_ws->v_edges.last = 0;

          self->g_ws_counter++;

          if (self->g_ws_counter == self->g_cohom_ws_size){
                reduce_ws_coH1(self);
          }


    }

    while(self->g_ws_counter){

        allocate_jobs(self, self->g_ws_counter);
        reduce_ws_coH1(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("\nsparse V coH1 length %d", self->g_V_sparse_ptr);


    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);

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

    PAR ddeath;

    if (self->g_filetype == 1){

        for (EDGE_ID it = 0; it < self->g_H1_pers_pairs_len; it+=2){

              if (self->g_H1_pers_pairs[it+1] == -1){
                      ddeath = -1;
              }
              else{
                      ddeath = sqrt(self->g_H1_pers_pairs[it+1]);
              }
              
              fprintf(fp2, "%0.12lf, %0.12lf\n", sqrt(self->g_H1_pers_pairs[it]), ddeath);
              
        }

    }
    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);
#endif


#ifdef PRINT
    // TEXT FILE

     printf("\nPers pairs in dim 1");
    if (self->g_filetype == 1){

        for (EDGE_ID it = 0; it < self->g_H1_pers_pairs_len; it+=2){
              
              printf("\n%0.12lf, %0.12lf", 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){
              
              printf("\n%0.12lf, %0.12lf", self->g_H1_pers_pairs[it], self->g_H1_pers_pairs[it+1]);
              
        }

    }

#endif



#ifdef SAVEV
    // TEXT FILE
    fp2 = fopen(self->g_coH1_V_file, "w");

        for (EDGE_ID it = 1; it < self->g_V_sparse_max; it++){
              
              fprintf(fp2, "%d\n", self->g_V_sparse_H1[it]);

    }

    fclose(fp2);
#endif


    // FREE coH1 Workspace
    for (int bb = 0; bb < self->g_cohom_ws_size; bb++){
         
          for (EDGE_ID mm = 0; mm < self->g_V_ws_H1[bb].max_len; mm++){
                
                free(self->g_V_ws_H1[bb].keys1[mm].keys2);
                
          }

          free(self->g_V_ws_H1[bb].keys1);
          free(self->g_V_ws_H1[bb].v_edges.o_ab);

    }

    // 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("\nTime to process input : %lf" , self->g_timer_process_input);
      printf("\nTime to create neigh: %lf" , self->g_timer_neigh);
      printf("\nTime to compute H0: %lf"   , self->g_timer_H0);
      printf("\nTime to compute coH1: %lf" , self->g_timer_coH1);
      printf("\nTotal time taken: %lf", \
                                      self->g_timer_process_input\
                                    + self->g_timer_neigh\
                                    + self->g_timer_H0\
                                    + self->g_timer_coH1\
                                    );
      deallocator(self);
      printf("\nQuitting after coH1");
      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 = 100000;
    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;



     ////////////////////////////////////////////////////
     // INITIALIZE WORKSPACE
     ////////////////////////////////////////////////////
     self->g_cohom_ws_size = 100;
     self->g_V_ws_H2 = (coboundary_H2_ws*)malloc(self->g_cohom_ws_size*sizeof(coboundary_H2_ws));
     for (int mm = 0; mm < self->g_cohom_ws_size; mm++){
          
          self->g_V_ws_H2[mm].max_len = 10;
          self->g_V_ws_H2[mm].last = 0;
          self->g_V_ws_H2[mm].keys1 = (coH2_implicit_keys1*)malloc(self->g_V_ws_H2[mm].max_len*sizeof(coH2_implicit_keys1));

          for (EDGE_ID nn = 0; nn < self->g_V_ws_H2[mm].max_len; nn++){
                
                self->g_V_ws_H2[mm].keys1[nn].max_len = 10;
                self->g_V_ws_H2[mm].keys1[nn].last = 0;
                self->g_V_ws_H2[mm].keys1[nn].flag_empty = 1;

                self->g_V_ws_H2[mm].keys1[nn].keys2 =\
                                        (coH2_implicit_keys2*)malloc(self->g_V_ws_H2[mm].keys1[nn].max_len*sizeof(coH2_implicit_keys2));

          }

          self->g_V_ws_H2[mm].v_triangles.max_len = 100;
          self->g_V_ws_H2[mm].v_triangles.last = 0;
          self->g_V_ws_H2[mm].v_triangles.o_abc = (simplex*)malloc(self->g_V_ws_H2[mm].v_triangles.max_len*sizeof(simplex));
          

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



    // 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));
         
    }



     // 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));

     }

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



    // BUFFER
    EDGE_ID buffer_len = 100000;
    EDGE_ID buffer_ptr = 0;
    coboundary_H2* coH2_lows_buffer = (coboundary_H2*)malloc(buffer_len*sizeof(coboundary_H2));
    


    ///////////////////////////////////////////////////
    // MAIN coH2 loop 
    ///////////////////////////////////////////////////
    
    EDGE_ID i = self->g_n_valid_edges;

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

    coboundary_H2 temp_triangle;


    self->g_debug_triangle.key1 = self->g_n_valid_edges ;
    self->g_debug_triangle.key2 = self->g_n_valid_edges ;

    //self->g_debug_triangle.key1 = 46494 ;
    //self->g_debug_triangle.key2 = 269 ;


    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;


          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;

                      temp_triangle.triangle.key1 = ab;
                      temp_triangle.triangle.key2 = (EDGE_ID)c;

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

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

                          EDGE_ID has_pivot = search_H1_cohom_pivots(self->g_H1_cohom_pivots[temp_triangle.triangle.key1]\
                                        , 0 \
                                        , self->g_H1_cohom_pivots_len[temp_triangle.triangle.key1] - 1\
                                        , temp_triangle.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;
                          }

                      }
                      // END OF CLEARING ALGORITHM
                      ///////////////////////////////////////////////////

                      temp_temp_triangles[temp_temp_len++] = temp_triangle;

                }

          }
          

          while (temp_temp_len > 0){
              
                temp_temp_len--;
                coH2_lows_buffer[buffer_ptr++].triangle = temp_temp_triangles[temp_temp_len].triangle;

                if (buffer_ptr == buffer_len){

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


                      EDGE_ID mm = 0;
                      while (mm < buffer_len){

                           if (coH2_lows_buffer[mm].vertex == -1){
                                //If it has empty cob, then it is undead cycle
                                      
                                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[coH2_lows_buffer[mm].triangle.key1];
                                self->g_H2_pers_pairs[self->g_H2_pers_pairs_len++] = -1;

                                mm++;
                                continue;
                           }

                           // Is this is a trivial pair?
                           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)){

                                mm++;
                                continue;
                           }


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

                            this_ws->triangle = coH2_lows_buffer[mm].triangle;

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

                            this_ws->flag_first = 1;
                            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->k1_ptr = 0;
                            this_ws->k2_ptr = 0;
                            this_ws->last = 1;

                            this_ws->keys1[0].last = 1;
                            this_ws->keys1[0].flag_empty = 0;
                            this_ws->keys1[0].k1 = this_ws->pivot.key1;

                            this_ws->keys1[0].keys2[0].k2 = this_ws->pivot.key2;
                            this_ws->keys1[0].keys2[0].o_abc  = coH2_lows_buffer[mm].triangle;
                            this_ws->keys1[0].keys2[0].a_ptr  = coH2_lows_buffer[mm].a_ptr;
                            this_ws->keys1[0].keys2[0].b_ptr  = coH2_lows_buffer[mm].b_ptr;
                            this_ws->keys1[0].keys2[0].c_ptr  = coH2_lows_buffer[mm].c_ptr;
                            this_ws->keys1[0].keys2[0].vertex = coH2_lows_buffer[mm].vertex;
                            this_ws->keys1[0].keys2[0].flag_next = 1;

                            this_ws->v_triangles.last = 0;

                            self->g_ws_counter++;

                            if (self->g_ws_counter == self->g_cohom_ws_size){
                                  reduce_ws_coH2(self);
                            }

                            mm++;
                            
                      }
                      
                      
                      buffer_ptr = 0;

                }



          }

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

          //      coboundary_H2_ws* this_ws = self->g_V_ws_H2 + self->g_ws_counter;

          //      this_ws->triangle = temp_temp_triangles[temp_temp_len].triangle;

          //      this_ws->pivot = temp_temp_triangles[temp_temp_len].low;

          //      this_ws->flag_first = 1;
          //      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->k1_ptr = 0;
          //      this_ws->k2_ptr = 0;
          //      this_ws->last = 1;

          //      this_ws->keys1[0].last = 1;
          //      this_ws->keys1[0].flag_empty = 0;
          //      this_ws->keys1[0].k1 = this_ws->pivot.key1;

          //      this_ws->keys1[0].keys2[0].k2 = this_ws->pivot.key2;
          //      this_ws->keys1[0].keys2[0].o_abc   = temp_temp_triangles[temp_temp_len].triangle;
          //      this_ws->keys1[0].keys2[0].a_ptr  = temp_temp_triangles[temp_temp_len].a_ptr;
          //      this_ws->keys1[0].keys2[0].b_ptr  = temp_temp_triangles[temp_temp_len].b_ptr;
          //      this_ws->keys1[0].keys2[0].c_ptr  = temp_temp_triangles[temp_temp_len].c_ptr;
          //      this_ws->keys1[0].keys2[0].vertex = temp_temp_triangles[temp_temp_len].vertex;
          //      this_ws->keys1[0].keys2[0].flag_next = 1;

          //      this_ws->v_triangles.last = 0;

          //      self->g_ws_counter++;

          //      if (self->g_ws_counter == self->g_cohom_ws_size){
          //            reduce_ws_coH2(self);
          //      }


          //}



    }


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

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

    }


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

          if (coH2_lows_buffer[mm].vertex == -1){
               mm++;
               continue;
          }
          
          // Is this is a trivial pair?
          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)){
               
               mm++;
               continue;
          }
          
          
          coboundary_H2_ws* this_ws = self->g_V_ws_H2 + self->g_ws_counter;
          
          this_ws->triangle = coH2_lows_buffer[mm].triangle;
          
          this_ws->pivot = coH2_lows_buffer[mm].low;
          
          this_ws->flag_first = 1;
          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->k1_ptr = 0;
          this_ws->k2_ptr = 0;
          this_ws->last = 1;
          
          this_ws->keys1[0].last = 1;
          this_ws->keys1[0].flag_empty = 0;
          this_ws->keys1[0].k1 = this_ws->pivot.key1;
          
          this_ws->keys1[0].keys2[0].k2 = this_ws->pivot.key2;
          this_ws->keys1[0].keys2[0].o_abc  = coH2_lows_buffer[mm].triangle;
          this_ws->keys1[0].keys2[0].a_ptr  = coH2_lows_buffer[mm].a_ptr;
          this_ws->keys1[0].keys2[0].b_ptr  = coH2_lows_buffer[mm].b_ptr;
          this_ws->keys1[0].keys2[0].c_ptr  = coH2_lows_buffer[mm].c_ptr;
          this_ws->keys1[0].keys2[0].vertex = coH2_lows_buffer[mm].vertex;
          this_ws->keys1[0].keys2[0].flag_next = 1;
          
          this_ws->v_triangles.last = 0;
          
          self->g_ws_counter++;
          
          if (self->g_ws_counter == self->g_cohom_ws_size){
                reduce_ws_coH2(self);
          }
          
          mm++;
          
    }


    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);

     printf("\nsparse V coH2 length %d", self->g_V_sparse_ptr);

    //////////////////////////////////////////////////
    // FREE coH2 Workspace
    //////////////////////////////////////////////////
    for (int bb = 0; bb < self->g_cohom_ws_size; bb++){
         
          for (EDGE_ID mm = 0; mm < self->g_V_ws_H2[bb].max_len; mm++){
                
                free(self->g_V_ws_H2[bb].keys1[mm].keys2);
                
          }

          free(self->g_V_ws_H2[bb].keys1);
          free(self->g_V_ws_H2[bb].v_triangles.o_abc);

    }

    free(self->g_V_ws_H2);

    // FREE temp_temp_triangles
    free(temp_temp_triangles);


    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);

#ifdef SAVEPD

    // 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);

#endif


#ifdef PRINT

    // TEXT FILE

    printf("\nPers pairs in dim 2");
    if (self->g_filetype == 1){

        for (EDGE_ID it = 0; it < self->g_H2_pers_pairs_len; it+=2){
              
              printf("\n%0.12lf, %0.12lf", 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){
              
              printf("\n%0.12lf, %0.12lf", self->g_H2_pers_pairs[it], self->g_H2_pers_pairs[it+1]);
              
        }

    }


#endif


#ifdef SAVEV
    // TEXT FILE
    fp2 = fopen(self->g_coH2_V_file, "w");

        for (EDGE_ID it = 1; it < self->g_V_sparse_max; it++){
              
              fprintf(fp2, "%d\n", self->g_V_sparse_H2[it]);

    }

    fclose(fp2);
#endif
    
    // FREE V_sparse
    free(self->g_V_sparse_H2);

    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("\nTime to process input : %lf" , self->g_timer_process_input);
    printf("\nTime to create neigh: %lf" , self->g_timer_neigh);
    printf("\nTime to compute H0: %lf"   , self->g_timer_H0);
    printf("\nTime to compute coH1: %lf" , self->g_timer_coH1);
    printf("\nTime to compute coH2: %lf" , 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("\nTotal time taken: %lf",\
                                    self->g_timer_process_input\
                                    + 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("\nQuitting after coH2");
    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);
//
//     }
//
//
//
//
//
//
//



}

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){
  
    VERT_ID 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; 
} 




// 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;

#ifdef COMBIDX
                        EDGE_ID o_ac = COMB_IDX(a, c);
                        if (o_ac != self->g_n_valid_edges){
#else

                        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;
#endif

                            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;

#ifdef COMBIDX
                EDGE_ID o_bc = COMB_IDX(b, c);
                if (o_bc != self->g_n_valid_edges){

#else
                VERT_ID idx = search_Neighbors(self, b, c, 0, self->g_Neigh_len[b]-1);
                if (idx < self->g_n_vert) {
#endif

                    // 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;

#ifdef COMBIDX
                EDGE_ID o_ac = COMB_IDX(a, c);
                if (o_ac != self->g_n_valid_edges){

#else
                VERT_ID idx = search_Neighbors(self, a, c, 0, self->g_Neigh_len[a]-1);
                if (idx < self->g_n_vert) {
#endif
                    // 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];



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

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

          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;
                          //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;

                          }


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


                    }
                    

                }
                else {
                    break;
                }

                
          }
          

          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]);


      }

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


     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){
            
            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;

#ifdef COMBIDX
                      EDGE_ID o_bc = COMB_IDX(b, c);
                      if (o_bc != self->g_n_valid_edges){

#else
                      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;
#endif


                          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;
#ifdef COMBIDX
                      EDGE_ID o_ac = COMB_IDX(a, c);
                      if (o_ac != self->g_n_valid_edges){
#else
                      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;
#endif


                          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;

#ifdef COMBIDX
                      EDGE_ID o_bc = COMB_IDX(b, c);
                      if (o_bc != self->g_n_valid_edges){
#else
                      VERT_ID idx = search_Neighbors(self, b, c, 0, self->g_Neigh_len[b]-1);
                      if (idx < self->g_n_vert) {
#endif
                            
                          //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;

#ifdef COMBIDX
                      EDGE_ID o_ac = COMB_IDX(a, c);
                      if (o_ac != self->g_n_valid_edges){
#else
                      VERT_ID idx = search_Neighbors(self, a, c, 0, self->g_Neigh_len[a]-1);
                      if (idx < self->g_n_vert) {
#endif
                            
                          //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){

    //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++;
          }

    }


}



// 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); 
    } 
} 


#ifdef COMBIDX

      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;
      
                  if ((d == a) || (d == b)){
                        V_info->c_ptr++;
                        continue;
                  }
      
      
                  if (COMB_IDX(a, d) > V_info->triangle.key1){
                        V_info->c_ptr++;
                        continue;
                  }
      
      
      
                  if (COMB_IDX(b, d) > 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;
      
                      if ((d == b) || (d == c)){
                          V_info->a_ptr++;
                          continue;
                      }
      
      
                      o_bd = COMB_IDX(b, d);
                      if (o_bd > ep){
                          V_info->a_ptr++;
                          continue;
                      }
      
                      o_cd = COMB_IDX(c, d);
                      if (o_cd > ep){
                          V_info->a_ptr++;
                          continue;
                      }
      
                      V_info->low.key1 = ep;
      
                      //o_bc = COMB_IDX(b, c);
                      V_info->low.key2 = COMB_IDX(b, c);
      
      
      
                      V_info->vertex = 1;
                      return;
      
      
      
      
                }
                else if (flag == 2){
                      
                      d = self->g_Neighbors_e[b][V_info->b_ptr].neighbor;
      
      
                      if ((d == a) || (d == c)){
                          V_info->b_ptr++;
                          continue;
                      }
      
      
                      o_ad = COMB_IDX(a, d);
                      if (o_ad > ep){
                          V_info->b_ptr++;
                          continue;
                      }
      
                      o_cd = COMB_IDX(c, d);
                      if (o_cd > ep){
                          V_info->b_ptr++;
                          continue;
                      }
      
      
                      V_info->low.key1 = ep;
      
                      //o_ac = COMB_IDX(a, c);
                      V_info->low.key2 = COMB_IDX(a, c);
      
                      V_info->vertex = 2;
                      return;
      
      
                }
                else if (flag == 3){
                      
                      d = self->g_Neighbors_e[c][V_info->c_ptr].neighbor;
      
      
      
                      if ((d == a) || (d == b)){
                          V_info->c_ptr++;
                          continue;
                      }
      
      
                      o_ad = COMB_IDX(a, d);
                      if (o_ad > ep){
                          V_info->c_ptr++;
                          continue;
                      }
      
                      o_bd = COMB_IDX(b, d);
                      if (o_bd > ep){
                          V_info->c_ptr++;
                          continue;
                      }
      
                      V_info->low.key1 = ep;
      
                      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;
            
            
      }







#else

      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;
      
            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;
            
            
      }


#endif


void update_V_coH1(filtration* self, int ws_counter){

    EDGE_ID red_col = 0;

    coboundary_H1_ws* this_ws = self->g_V_ws_H1 + ws_counter;

    self->g_V_sparse_beg_ptr = self->g_V_sparse_ptr;

    if (this_ws->v_edges.last){

        if ((this_ws->v_edges.last + self->g_V_sparse_ptr) + 1 > self->g_V_sparse_max){

                    self->g_V_sparse_max = self->g_V_sparse_ptr + this_ws->v_edges.last + 10000;
                    self->g_V_sparse_H1 = (EDGE_ID*)realloc(self->g_V_sparse_H1\
                                                        , self->g_V_sparse_max*sizeof(EDGE_ID));

        }

#ifdef VREDUCE1
        if (this_ws->v_edges.last > 1){
            sorter8_tim_sort(this_ws->v_edges.o_ab, this_ws->v_edges.last);          
        }
#endif 

        EDGE_ID mm = 0;


        while (1){
              
#ifdef VREDUCE1
              if (this_ws->v_edges.o_ab[mm] == this_ws->v_edges.o_ab[mm+1]){
                    mm += 2;
                    if (mm == this_ws->v_edges.last){
                        break;
                    }
                    continue;
              }

              //if (this_ws->edge == self->g_debug_edge){
              //    printf("%d, ", this_ws->v_edges.o_ab[mm]);
              //}
#endif 

              self->g_V_sparse_H1[self->g_V_sparse_ptr++] = this_ws->v_edges.o_ab[mm++];

#ifdef VREDUCE1
              if (mm == this_ws->v_edges.last - 1){
                  self->g_V_sparse_H1[self->g_V_sparse_ptr++] = this_ws->v_edges.o_ab[mm];
                  break;
              }
#endif 


              if (mm == this_ws->v_edges.last){
                  break;
              }
              
        }



        //if (this_ws->edge == self->g_debug_edge){

        //    printf("\nAfter adding to V sparse ");
        //    for (EDGE_ID bb = self->g_V_sparse_beg_ptr; bb < self->g_V_sparse_ptr; bb++){

        //        printf("%d, ", self->g_V_sparse_H1[bb]);
        //    }
        //  getchar();
        //}
        // All have been added
        this_ws->v_edges.last = 0;


        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++;


        }

    }



#ifdef COH1DEBUG
        if (this_ws->edge == self->g_debug_edge){

            printf("\n%d, %d, %d, %d: "\
                                            , this_ws->pivot.key1\
                                            , this_ws->pivot.key2\
                                            , this_ws->edge\
                                            , self->g_V_sparse_ptr - self->g_V_sparse_beg_ptr\
                                            );
            for (EDGE_ID mm = self->g_V_sparse_beg_ptr; mm < self->g_V_sparse_ptr; mm++){
                printf("%d, ", self->g_V_sparse_H1[mm]);
            }
            getchar();
              
        }
#endif

#ifdef VDEBUG
        if (self->g_V_sparse_ptr - self->g_V_sparse_beg_ptr > 0){

            printf("\n%d, %d, %d, %d"\
                                            , this_ws->pivot.key1\
                                            , this_ws->pivot.key2\
                                            , this_ws->edge\
                                            , self->g_V_sparse_ptr - self->g_V_sparse_beg_ptr\
                                            );
            //getchar();

        }
#endif

    // ADDING THE LOW

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

    }
          


    EDGE_ID old_ptr = self->g_H1_cohom_pivots_len[this_ws->pivot.key1];
    EDGE_ID new_ptr = self->g_H1_cohom_pivots_len[this_ws->pivot.key1];

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

                self->g_H1_cohom_pivots[this_ws->pivot.key1][new_ptr--] =\
                                                       self->g_H1_cohom_pivots[this_ws->pivot.key1][old_ptr];
                continue;

          }
          break;

    }

    self->g_H1_cohom_pivots[this_ws->pivot.key1][new_ptr].key2 = this_ws->pivot.key2;
    self->g_H1_cohom_pivots[this_ws->pivot.key1][new_ptr].col_idx = red_col;
                            
    self->g_H1_cohom_pivots[this_ws->pivot.key1][new_ptr].bndry = this_ws->edge;

    self->g_H1_cohom_pivots_len[this_ws->pivot.key1]++;

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

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

#ifdef DEBUGPIVOTS
           printf("\nBirth, death (%lf, %lf)", birth, death);
           printf("\n%d at pair (%d, %d)", this_ws->edge\
                                                , this_ws->pivot.key1\
                                                , this_ws->pivot.key2);
           getchar();
          
#endif

          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 deallocator(filtration* self){

      free(self->filename);

      // 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_R_sparse_H0);
      free(self->g_R_col_indices_H0);
      free(self->g_edges_with_pivots_H0);

#ifdef SAVEPD
      free(self->g_H0_pers_file);
#endif

      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);
#ifdef SAVEPD
            free(self->g_H1_pers_file);
#endif

#ifdef SAVEV
            free(self->g_coH1_V_file);
#endif
            free(self->g_H1_pers_pairs);

            free(self->g_V_col_indices);
            
            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);
#ifdef SAVEPD
                  free(self->g_H2_pers_file);
#endif

#ifdef SAVEV
                  free(self->g_coH2_V_file);
#endif
                  free(self->g_H2_pers_pairs);
                  
            }

            
      }



      free(self);
      

}





void insert_in_implicit_v(filtration* self, int ws_counter, coboundary_H1* phi, int flag_next){


      if (phi->low.key1 == self->g_n_valid_edges){
          return;
      }

      coboundary_H1_ws* this_ws = self->g_V_ws_H1 + ws_counter;
      

      if (phi->low.key1 == this_ws->keys1[this_ws->k1_ptr].k1){


            if (this_ws->keys1[this_ws->k1_ptr].last ==\
                                            this_ws->keys1[this_ws->k1_ptr].max_len){

                  self->g_V_ws_H1[ws_counter].keys1[self->g_V_ws_H1[ws_counter].k1_ptr].max_len += 10;
                  self->g_V_ws_H1[ws_counter].keys1[self->g_V_ws_H1[ws_counter].k1_ptr].keys2 = \
                                                        (implicit_keys2*)realloc\
                                                             (self->g_V_ws_H1[ws_counter].keys1[self->g_V_ws_H1[ws_counter].k1_ptr].keys2\
                                                            , self->g_V_ws_H1[ws_counter].keys1[self->g_V_ws_H1[ws_counter].k1_ptr].max_len\
                                                                  *sizeof(implicit_keys2));
                  
            }


            EDGE_ID mm = this_ws->keys1[this_ws->k1_ptr].last;

            int compare;


            while (1){


                //int compare = compare_implicit(this_ws->keys1[this_ws->k1_ptr].keys2[mm-1], *phi);


                if (this_ws->keys1[this_ws->k1_ptr].keys2[mm-1].k2 < phi->low.key2) compare = 0;
                else if (this_ws->keys1[this_ws->k1_ptr].keys2[mm-1].k2 > phi->low.key2) compare = 1;
                else{

                      if (this_ws->keys1[this_ws->k1_ptr].keys2[mm-1].o_ab < phi->o_ab) compare = 0;
                      else compare = 1;

                }

                  
                if (compare){

                      this_ws->keys1[this_ws->k1_ptr].keys2[mm] =\
                                                        this_ws->keys1[this_ws->k1_ptr].keys2[mm-1];
                }
                else{
                    
                      this_ws->keys1[this_ws->k1_ptr].keys2[mm].k2 = phi->low.key2; 
                  
                      this_ws->keys1[this_ws->k1_ptr].keys2[mm].k2 = phi->low.key2; 
                      this_ws->keys1[this_ws->k1_ptr].keys2[mm].o_ab = phi->o_ab; 
                      this_ws->keys1[this_ws->k1_ptr].keys2[mm].a_ptr = phi->a_ptr; 
                      this_ws->keys1[this_ws->k1_ptr].keys2[mm].b_ptr = phi->b_ptr; 
                      this_ws->keys1[this_ws->k1_ptr].keys2[mm].flag_next = flag_next; 

                      this_ws->keys1[this_ws->k1_ptr].last++;

                      return;

                }

                mm--;

                //// ERROR CHECKING, REMOVE LATER
                //if (!mm){
                //    printf("\nk2_ptr %d", v_implicit->k2_ptr);
                //    printf("\nADDING %d:(%d, %d) to ", phi->o_ab, phi->low.key1, phi->low.key2);
                //    print_v_implicit(self);
                //    exit(0);
                //    
                //}

                if (mm == this_ws->k2_ptr){

                      this_ws->keys1[this_ws->k1_ptr].keys2[mm].k2 = phi->low.key2; 
                      this_ws->keys1[this_ws->k1_ptr].keys2[mm].o_ab = phi->o_ab; 
                      this_ws->keys1[this_ws->k1_ptr].keys2[mm].a_ptr = phi->a_ptr; 
                      this_ws->keys1[this_ws->k1_ptr].keys2[mm].b_ptr = phi->b_ptr; 
                      this_ws->keys1[this_ws->k1_ptr].keys2[mm].flag_next = flag_next; 

                      this_ws->keys1[this_ws->k1_ptr].last++;


                      return;
                    

                }

            }
            
            
      }

      for (EDGE_ID mm = 0; mm < this_ws->last; mm++){

            
            if (this_ws->keys1[mm].k1 == phi->low.key1){
                
                  
                  //check_space_implicit_keys2(&(v_implicit->keys1[mm]));

                  if (this_ws->keys1[mm].last ==\
                                                  this_ws->keys1[mm].max_len){

                        self->g_V_ws_H1[ws_counter].keys1[mm].max_len += 10;
                        self->g_V_ws_H1[ws_counter].keys1[mm].keys2 = (implicit_keys2*)realloc\
                                                                 (self->g_V_ws_H1[ws_counter].keys1[mm].keys2\
                                                                , self->g_V_ws_H1[ws_counter].keys1[mm].max_len*sizeof(implicit_keys2));
                        
                  }

                  this_ws->keys1[mm].flag_empty = 0;

                  this_ws->keys1[mm].keys2[this_ws->keys1[mm].last].k2 = phi->low.key2;
                  this_ws->keys1[mm].keys2[this_ws->keys1[mm].last].o_ab = phi->o_ab;
                  this_ws->keys1[mm].keys2[this_ws->keys1[mm].last].a_ptr = phi->a_ptr;
                  this_ws->keys1[mm].keys2[this_ws->keys1[mm].last].b_ptr = phi->b_ptr;
                  this_ws->keys1[mm].keys2[this_ws->keys1[mm].last].flag_next = flag_next;

                  this_ws->keys1[mm].last++;

                  return;

                  
            }
            
            
      }

      //if (self->g_new_debug){
      //    printf("\nBefore inserting c4");
      //    print_v_implicit(self);
      //    getchar();

      //}
      //check_space_implicit_keys1(v_implicit);

      if (self->g_V_ws_H1[ws_counter].last == self->g_V_ws_H1[ws_counter].max_len){
            
            EDGE_ID mm = self->g_V_ws_H1[ws_counter].max_len;

            self->g_V_ws_H1[ws_counter].max_len += 10;
            self->g_V_ws_H1[ws_counter].keys1 = (implicit_keys1*)realloc(self->g_V_ws_H1[ws_counter].keys1\
                                                                  , self->g_V_ws_H1[ws_counter].max_len*sizeof(implicit_keys1));

            while (mm < self->g_V_ws_H1[ws_counter].max_len){
                  
                  this_ws->keys1[mm].flag_empty = 1;
                  this_ws->keys1[mm].max_len = 10;
                  this_ws->keys1[mm].last = 0;
                  self->g_V_ws_H1[ws_counter].keys1[mm].keys2 = (implicit_keys2*)malloc(10*sizeof(implicit_keys2));

                  mm++;
                
                  
            }


      }




      this_ws->keys1[this_ws->last].flag_empty = 0;
      this_ws->keys1[this_ws->last].k1 = phi->low.key1;
      this_ws->keys1[this_ws->last].keys2[0].k2 = phi->low.key2;
      this_ws->keys1[this_ws->last].keys2[0].o_ab = phi->o_ab;
      this_ws->keys1[this_ws->last].keys2[0].a_ptr = phi->a_ptr;
      this_ws->keys1[this_ws->last].keys2[0].b_ptr = phi->b_ptr;
      this_ws->keys1[this_ws->last].keys2[0].flag_next = flag_next;
      this_ws->keys1[this_ws->last].last = 1;

      this_ws->last++;


      return;



      
        
}



void print_v_implicit(filtration* self, int ws_counter){
      

    if (self->g_V_ws_H1[ws_counter].edge == self->g_debug_edge){


          EDGE_ID k1_ptr = 0;

          if (k1_ptr == self->g_V_ws_H1[ws_counter].last){
            printf("\nv implicit is empty");
            return;
          }

          EDGE_ID k2_ptr = 0;


          while (k1_ptr < self->g_V_ws_H1[ws_counter].last){

              if (self->g_V_ws_H1[ws_counter].keys1[k1_ptr].flag_empty){
                //printf("empty %d, %d", k1_ptr, self->g_V_ws_H1[ws_counter].keys1[k1_ptr].k1);
                k1_ptr++;
                continue;
              }


              printf("\n");
              printf("idx %d, last %d:: ", k1_ptr, self->g_V_ws_H1[ws_counter].keys1[k1_ptr].last);

              while (k2_ptr < self->g_V_ws_H1[ws_counter].keys1[k1_ptr].last){
                  printf("%d:(%d, %d):%d,  ", self->g_V_ws_H1[ws_counter].keys1[k1_ptr].keys2[k2_ptr].o_ab\
                                         , self->g_V_ws_H1[ws_counter].keys1[k1_ptr].k1\
                                         , self->g_V_ws_H1[ws_counter].keys1[k1_ptr].keys2[k2_ptr].k2\
                                         , self->g_V_ws_H1[ws_counter].keys1[k1_ptr].keys2[k2_ptr].flag_next\
                                         );
                  k2_ptr++;
              }
              k2_ptr = 0;
              k1_ptr++;

                
          }

    }
      
}


void coH2_print_v_implicit(filtration* self, int ws_counter){
      


          printf("\nk1ptr is %d, k2ptr is %d", self->g_V_ws_H2[ws_counter].k1_ptr\
                                             , self->g_V_ws_H2[ws_counter].k2_ptr);

          EDGE_ID k1_ptr = 0;

          if (k1_ptr == self->g_V_ws_H2[ws_counter].last){
            printf("\nv implicit is empty");
            return;
          }

          EDGE_ID k2_ptr = 0;


          while (k1_ptr < self->g_V_ws_H2[ws_counter].last){


              //if (self->g_V_ws_H2[ws_counter].keys1[k1_ptr].flag_empty){
              //  printf("empty", k1_ptr, self->g_V_ws_H2[ws_counter].keys1[k1_ptr].k1);
              //  k1_ptr++;
              //  continue;
              //}


              //printf("\nentries in %d are %d", k1_ptr, self->g_v_implicit.keys1[k1_ptr].last);
              
              printf("\n");
              printf("idx %d, last %d:: ", k1_ptr, self->g_V_ws_H2[ws_counter].keys1[k1_ptr].last);

              while (k2_ptr < self->g_V_ws_H2[ws_counter].keys1[k1_ptr].last){
                  printf("(%d, %d):%d,  "\
                                         , self->g_V_ws_H2[ws_counter].keys1[k1_ptr].keys2[k2_ptr].o_abc.key1\
                                         , self->g_V_ws_H2[ws_counter].keys1[k1_ptr].keys2[k2_ptr].o_abc.key2\
                                         , self->g_V_ws_H2[ws_counter].keys1[k1_ptr].keys2[k2_ptr].flag_next\
                                         );
                  k2_ptr++;
              }
              k2_ptr = 0;
              k1_ptr++;

                
          }

      
}


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


      pthread_mutex_lock(&(self->g_thread_lock));

      int tid = ++self->g_thread_id;


      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 (int ws_counter = self->g_jobs[tid - 1]; ws_counter < self->g_jobs[tid]; ws_counter++){


              coboundary_H1_ws* this_ws = self->g_V_ws_H1 + ws_counter;


              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_append_to_complex){
                  continue;
              }

              // If being processed for the first time...
              if (this_ws->flag_first){

                  this_ws->flag_first = 0;

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


                        this_ws->reduce_w_bndry = this_ws->pivot.key1;
                        this_ws->V_col_idx = 0;

                        this_ws->flag_red_w_trivial = 1;


                  }
                  else{

                        // If this low is not a pivot
                        if (!self->g_H1_cohom_pivots_len[this_ws->pivot.key1]){
#ifdef COH1DEBUG
                              if (this_ws->edge == self->g_debug_edge ){
                                  printf("\n(%d, %d) pivot of %d is not a pivot 1"\
                                                          , this_ws->pivot.key1\
                                                          , this_ws->pivot.key2\
                                                          , this_ws->edge\
                                                          );
                              } 
#endif

                              this_ws->flag_append_to_complex = 1;
                              continue;

                        }
                        else{

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

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

#ifdef COH1DEBUG
                                    if (this_ws->edge == self->g_debug_edge ){
                                        printf("\n(%d, %d) pivot of %d is not a pivot 2"\
                                                                , this_ws->pivot.key1\
                                                                , this_ws->pivot.key2\
                                                                , this_ws->edge\
                                                                );
                                    } 
#endif
                                    this_ws->flag_append_to_complex = 1;
                                    continue;


                              }
                              else{

                                  this_ws->flag_red_w_complex = 1;
                                  this_ws->reduce_w_bndry = self->g_H1_cohom_pivots[this_ws->pivot.key1][idx].bndry;
                                  this_ws->V_col_idx = self->g_H1_cohom_pivots[this_ws->pivot.key1][idx].col_idx;

                              }
                        
                        }

                  }

              }

              if ((!this_ws->flag_red_w_trivial) && (!this_ws->flag_red_w_complex)){
                  this_ws->flag_append_to_complex = 1;
                  continue;
              }


              // We know that parallel will end only when there are no more red. to be with trivial and complex
              this_ws->flag_red_w_complex = 0;
              this_ws->flag_red_w_trivial = 0;
              this_ws->flag_append_to_complex = 1;
                    

              while(1){

                    EDGE_ID check_len = this_ws->v_edges.last + 1;

                    if (this_ws->V_col_idx){

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

                    }

                    if (check_len > this_ws->v_edges.max_len){
                        this_ws->v_edges.max_len = check_len + 100;
                        self->g_V_ws_H1[ws_counter].v_edges.o_ab =\
                                                (EDGE_ID*)realloc(self->g_V_ws_H1[ws_counter].v_edges.o_ab\
                                                                , this_ws->v_edges.max_len*sizeof(EDGE_ID));

                    }

                    this_ws->v_edges.o_ab[this_ws->v_edges.last++] = this_ws->reduce_w_bndry;

                    coboundary_H1 ttemp;

                    ttemp.o_ab = this_ws->reduce_w_bndry;


                    find_H1_cohom_greater(self, &(ttemp), &(this_ws->pivot));

                    insert_in_implicit_v(self, ws_counter, &(ttemp), 1);


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

                        // 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[this_ws->V_col_idx];
                        EDGE_ID end = self->g_V_col_indices[this_ws->V_col_idx+1];


                            for (EDGE_ID mm = start; mm < end; mm++){
                                  
                                this_ws->v_edges.o_ab[this_ws->v_edges.last++] = self->g_V_sparse_H1[mm];

                                ttemp.o_ab = self->g_V_sparse_H1[mm];

                                // Find the first low greater than or equal pivot
                                find_H1_cohom_greater(self, &(ttemp), &(this_ws->pivot));

                                insert_in_implicit_v(self, ws_counter,  &(ttemp), 1);

                            }

                    }

                    reduce_hash_table_coH1(self, ws_counter);

                    if (!this_ws->flag_non_empty){
                        break;
                    }

                    // Check with trivial pair
                    if ((self->g_coH1_all_lows[this_ws->pivot.key1].low.key1 == this_ws->pivot.key1)\
                        && (self->g_coH1_all_lows[this_ws->pivot.key1].low.key2 == this_ws->pivot.key2)){


                            this_ws->reduce_w_bndry = this_ws->pivot.key1;
                            this_ws->V_col_idx = 0;
                            continue;

                    }

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


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




                    if (idx == self->g_n_valid_edges){
                      break;
                    }

                    this_ws->reduce_w_bndry = self->g_H1_cohom_pivots[this_ws->pivot.key1][idx].bndry;
                    this_ws->V_col_idx = self->g_H1_cohom_pivots[this_ws->pivot.key1][idx].col_idx;

              }

          }

      }
        
}


void reduce_with_self_coH1(filtration* self){


      // Now we have to reduce

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

            coboundary_H1_ws* this_ws = self->g_V_ws_H1 + ws_counter;

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



            int 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_H1_ws* m_ws = self->g_V_ws_H1 + m;

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


                int compare;

                if (m_ws->pivot.key1 > this_ws->pivot.key1) compare = 1;
                else if (m_ws->pivot.key1 < this_ws->pivot.key1) compare = 0;
                else{
                  if (m_ws->pivot.key2 > this_ws->pivot.key2) compare = 1;
                  else if (m_ws->pivot.key2 < this_ws->pivot.key2) compare = 0;
                  else compare = -1;
                }

                // If pivot of m is higher than pivot of ws_counter
                // then we don't care
                if (compare == 1){
                      m++;
                      continue;
                }

                // 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;
                }

                // At this point they have same low
                if (m_ws->flag_red_w_complex || m_ws->flag_red_w_trivial){
                      this_ws->flag_append_to_complex = 0;
                      //m++;
                      break;
                      //continue;
                }

                //printf("\nin serial reducing %d with %d", this_ws->edge, m_ws->edge);
                //getchar();

                // Merge m and this_ws
                //




                
                // Merge v_edges
                if (this_ws->v_edges.last + m_ws->v_edges.last > this_ws->v_edges.max_len - 1){
                      
                      this_ws->v_edges.max_len += m_ws->v_edges.last + 100;

                      self->g_V_ws_H1[ws_counter].v_edges.o_ab = (EDGE_ID*)realloc(\
                                                 self->g_V_ws_H1[ws_counter].v_edges.o_ab\
                                                 , this_ws->v_edges.max_len*sizeof(EDGE_ID));

                }

                // Add the original edge
                
                  
                //if (this_ws->edge == self->g_debug_edge){
                //    printf("\nAdding to v edge in serial %d", m_ws->edge);
                //}

                this_ws->v_edges.o_ab[this_ws->v_edges.last++] = m_ws->edge;
                
                for (EDGE_ID bb = 0; bb < m_ws->v_edges.last; bb++){

                      //if (this_ws->edge == self->g_debug_edge){
                      //    printf(", %d", m_ws->v_edges.o_ab[bb]);
                      //}

                      this_ws->v_edges.o_ab[this_ws->v_edges.last++] =\
                                                    m_ws->v_edges.o_ab[bb];
                      
                }

                // Merge hash tables
                coboundary_H1 ttemp;

                for (EDGE_ID bb = 0; bb < m_ws->last; bb++){

                      EDGE_ID m_start = 0;

                      if (bb == m_ws->k1_ptr){
                          m_start = m_ws->k2_ptr;
                      }
                      
                      ttemp.low.key1 = m_ws->keys1[bb].k1;

                      for (EDGE_ID mm = m_start; mm < m_ws->keys1[bb].last; mm++){
                            

                            ttemp.low.key2 = m_ws->keys1[bb].keys2[mm].k2;
                            ttemp.o_ab = m_ws->keys1[bb].keys2[mm].o_ab;
                            ttemp.a_ptr = m_ws->keys1[bb].keys2[mm].a_ptr;
                            ttemp.b_ptr = m_ws->keys1[bb].keys2[mm].b_ptr;

                            insert_in_implicit_v(self, ws_counter, &(ttemp)\
                                                  , m_ws->keys1[bb].keys2[mm].flag_next);
                            
                            
                      }
                      
                }



                // Now reduce


                reduce_hash_table_coH1(self, ws_counter);


                if (!this_ws->flag_non_empty){
                  break;
                }

                // Check with trivial pair
                if ((self->g_coH1_all_lows[this_ws->pivot.key1].low.key1 == this_ws->pivot.key1)\
                    && (self->g_coH1_all_lows[this_ws->pivot.key1].low.key2 == this_ws->pivot.key2)){

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

                        this_ws->reduce_w_bndry = this_ws->pivot.key1;
                        this_ws->V_col_idx = 0;
                        break;

                }


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

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

                    if (idx != self->g_n_valid_edges){

                        this_ws->flag_red_w_trivial = 0;
                        this_ws->flag_red_w_complex = 1;
                        this_ws->flag_append_to_complex = 0;
                          
                        this_ws->reduce_w_bndry = self->g_H1_cohom_pivots[this_ws->pivot.key1][idx].bndry;
                        this_ws->V_col_idx = self->g_H1_cohom_pivots[this_ws->pivot.key1][idx].col_idx;

                        break;

                    }

                      
                 }


                 // Reset m after single reduction
                 m = 0;

                
            }

            
      }
      
      
    
}



void reduce_hash_table_coH1(filtration* self, int ws_counter){
      
      
      // Now we have to reduce
      int coeff = 1;
      
      coboundary_H1_ws* this_ws = self->g_V_ws_H1 + ws_counter;

      coboundary_H1 ttemp;
      
      EDGE_ID* k1_ptr = &(this_ws->k1_ptr);
      EDGE_ID* k2_ptr = &(this_ws->k2_ptr);
      
      while (1){
            
            
            if (this_ws->keys1[*k1_ptr].last == 1){
                this_ws->pivot.key1 = this_ws->keys1[*k1_ptr].k1;
                this_ws->pivot.key2 = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].k2;
                break;
            }
      
      
      
            if (this_ws->keys1[*k1_ptr].keys2[*k2_ptr].k2 ==\
                          this_ws->keys1[*k1_ptr].keys2[*k2_ptr+1].k2){
      
                  coeff = 1 - coeff;
      
                  if (this_ws->keys1[*k1_ptr].keys2[*k2_ptr].o_ab ==\
                            this_ws->keys1[*k1_ptr].keys2[*k2_ptr+1].o_ab){
      
                        if (this_ws->keys1[*k1_ptr].keys2[*k2_ptr].flag_next==\
                                                this_ws->keys1[*k1_ptr].keys2[*k2_ptr+1].flag_next){
      
                            this_ws->keys1[*k1_ptr].keys2[*k2_ptr].flag_next = 0;
                            this_ws->keys1[*k1_ptr].keys2[*k2_ptr+1].flag_next = 0;
      
                        }
                        
      
                  }
                  
              
            }
            else{
                  
                 if (coeff){
                    
                      this_ws->pivot.key1 = this_ws->keys1[*k1_ptr].k1;
                      this_ws->pivot.key2 = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].k2;
                      break;
      
                 }
                 else{
      
                      coeff = 1;
                 }
            }
      
      
      
            if (this_ws->keys1[*k1_ptr].keys2[*k2_ptr].flag_next){
      
                ttemp.o_ab = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].o_ab;
                ttemp.a_ptr = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].a_ptr;
                ttemp.b_ptr = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].b_ptr;
      
                ttemp.low.key1 = this_ws->keys1[*k1_ptr].k1;
                ttemp.low.key2 = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].k2;
      
                find_H1_cohom_next(self, &(ttemp));

                this_ws->keys1[*k1_ptr].keys2[*k2_ptr].flag_next = 0;
      
                insert_in_implicit_v(self, ws_counter, &(ttemp), 1);
      
      
                // It is possible that last key1 and last key2 changed. Make sure last is consistent
                
                
            }
      
      
            *k2_ptr = *k2_ptr + 1;
      
            if (*k2_ptr == this_ws->keys1[*k1_ptr].last-1){

                if (coeff){
                          this_ws->pivot.key1 = this_ws->keys1[*k1_ptr].k1;
                          this_ws->pivot.key2 = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].k2;
                          break;
                }
      
                if (this_ws->keys1[*k1_ptr].keys2[*k2_ptr].flag_next){
                      
                      ttemp.o_ab  = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].o_ab;
                      ttemp.a_ptr = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].a_ptr;
                      ttemp.b_ptr = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].b_ptr;
      
                      ttemp.low.key1 = this_ws->keys1[*k1_ptr].k1;
                      ttemp.low.key2 = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].k2;
      
                      find_H1_cohom_next(self, &(ttemp));
      
                      this_ws->keys1[*k1_ptr].keys2[*k2_ptr].flag_next = 0;

                      insert_in_implicit_v(self, ws_counter, &(ttemp), 1);
      
                      
                }
      
      
                if (*k2_ptr == this_ws->keys1[*k1_ptr].last-2){
      
                          *k2_ptr = *k2_ptr + 1;
                          this_ws->pivot.key1 = this_ws->keys1[*k1_ptr].k1;
                          this_ws->pivot.key2 = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].k2;
                          break;
                }
                else{
      
                          // Mark this key1 as empty
                          this_ws->keys1[*k1_ptr].flag_empty = 1;
      
                          EDGE_ID current_ptr = 0;
                          EDGE_ID minn = self->g_n_valid_edges;
      
                          for (EDGE_ID mm = 0; mm < this_ws->last; mm++){
      
                              if (this_ws->keys1[mm].flag_empty){
                                  continue;
                              }
      
                              implicit_keys1 ttemp = this_ws->keys1[current_ptr];
      
                              this_ws->keys1[current_ptr] = this_ws->keys1[mm];
                              
                              this_ws->keys1[mm] = ttemp;
      
                              if (this_ws->keys1[current_ptr].k1 < minn){
                                   minn = this_ws->keys1[current_ptr].k1;
                                   *k1_ptr = current_ptr;
                              }
      
                              current_ptr++;
                                
                                
                          }
      
                          this_ws->last = current_ptr;
      
                          if (minn == self->g_n_valid_edges){
                              this_ws->flag_non_empty = 0;
                              break;
                          }
      
                          coeff = 1;
                          *k2_ptr = 0;
      
                          sorter7_tim_sort(this_ws->keys1[*k1_ptr].keys2\
                                            , this_ws->keys1[*k1_ptr].last);          
      
                      
                }
      
                  
            }
      
      
      }
      
}




void reduce_ws_coH1(filtration* self){



      // PARALLEL
      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));
      }


      // SERIAL
      reduce_with_self_coH1(self);


      // CLEARANCE
      int count_valid = 0;

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

            
            if (!self->g_V_ws_H1[ws_counter].flag_non_empty){
                // Add the undead H1
                
                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++] = \
                                                     self->g_edge_parameter[self->g_V_ws_H1[ws_counter].edge];

                self->g_H1_pers_pairs[self->g_H1_pers_pairs_len++] = -1;
              
                continue;
            }

            if (self->g_V_ws_H1[ws_counter].flag_append_to_complex){
                  update_V_coH1(self, ws_counter);
                  continue;
            }


            // Swap V
            coboundary_H1_ws temp = self->g_V_ws_H1[count_valid];
            self->g_V_ws_H1[count_valid] = self->g_V_ws_H1[ws_counter];
            self->g_V_ws_H1[ws_counter] = temp;

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

            // Run through parallel at least once
            self->g_V_ws_H1[count_valid].flag_append_to_complex = 0;
            
            count_valid++;

      }

      self->g_ws_counter = count_valid;
      
      
}


void reduce_with_self_coH2(filtration* self){


      // Now we have to reduce

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

            coboundary_H2_ws* this_ws = self->g_V_ws_H2 + ws_counter;

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

            int 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 + m;

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

                int compare;

                if (m_ws->pivot.key1 > this_ws->pivot.key1) compare = 1;
                else if (m_ws->pivot.key1 < this_ws->pivot.key1) compare = 0;
                else{
                  if (m_ws->pivot.key2 > this_ws->pivot.key2) compare = 1;
                  else if (m_ws->pivot.key2 < this_ws->pivot.key2) compare = 0;
                  else compare = -1;
                }

                // If pivot of m is higher than pivot of ws_counter
                // then we don't care
                if (compare == 1){
                      m++;
                      continue;
                }

                // 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;
                }

                // At this point they have same low
                if (m_ws->flag_red_w_complex || m_ws->flag_red_w_trivial){
                      this_ws->flag_append_to_complex = 0;
                      //m++;
                      break;
                      //continue;
                }

                //printf("\nin serial reducing %d with %d", this_ws->edge, m_ws->edge);
                //getchar();

                // Merge m and this_ws
                //

                
                // Merge v_triangles
                if (this_ws->v_triangles.last + m_ws->v_triangles.last > this_ws->v_triangles.max_len - 1){
                      
                      this_ws->v_triangles.max_len += m_ws->v_triangles.last + 100;

                      self->g_V_ws_H2[ws_counter].v_triangles.o_abc = (simplex*)realloc(\
                                                 self->g_V_ws_H2[ws_counter].v_triangles.o_abc\
                                                 , this_ws->v_triangles.max_len*sizeof(simplex));

                }

                // Add the original edge
                

                this_ws->v_triangles.o_abc[this_ws->v_triangles.last++] = m_ws->triangle;
                
                for (EDGE_ID bb = 0; bb < m_ws->v_triangles.last; bb++){

                      this_ws->v_triangles.o_abc[this_ws->v_triangles.last++] =\
                                                    m_ws->v_triangles.o_abc[bb];
                      
                }

                // Merge hash tables
                coboundary_H2 ttemp;

                for (EDGE_ID bb = 0; bb < m_ws->last; bb++){

                      EDGE_ID m_start = 0;

                      if (bb == m_ws->k1_ptr){
                          m_start = m_ws->k2_ptr;
                      }
                      
                      ttemp.low.key1 = m_ws->keys1[bb].k1;

                      for (EDGE_ID mm = m_start; mm < m_ws->keys1[bb].last; mm++){
                            

                            ttemp.low.key2 = m_ws->keys1[bb].keys2[mm].k2;
                            ttemp.triangle = m_ws->keys1[bb].keys2[mm].o_abc;
                            ttemp.a_ptr = m_ws->keys1[bb].keys2[mm].a_ptr;
                            ttemp.b_ptr = m_ws->keys1[bb].keys2[mm].b_ptr;
                            ttemp.c_ptr = m_ws->keys1[bb].keys2[mm].c_ptr;
                            ttemp.vertex = m_ws->keys1[bb].keys2[mm].vertex;

                            coH2_insert_in_implicit_v(self, ws_counter, &(ttemp)\
                                                  , m_ws->keys1[bb].keys2[mm].flag_next);
                            
                            
                      }
                      
                }



                // Now reduce

                reduce_hash_table_coH2(self, ws_counter);

                if (!this_ws->flag_non_empty){
                  break;
                }

                coboundary_H2 temptemp;

                // CHECK FOR TRIVIAL PAIR
                // Get low for maximum triangle <ab, d> in this_pivot
                temptemp.triangle.key1 = this_ws->pivot.key1;
                temptemp.triangle.key2 = self->g_edges_list[this_ws->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_ws->pivot.key1)\
                    && (temptemp.low.key2 == this_ws->pivot.key2)){

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

                        this_ws->reduce_w_bndry = temptemp.triangle;
                        this_ws->V_col_idx = 0;
                        break;

                }


                 // If this low is not a pivot
                 if (self->g_H2_cohom_pivots_len[this_ws->pivot.key1]){

                    EDGE_ID idx = search_H2_cohom_pivots(self->g_H2_cohom_pivots[this_ws->pivot.key1]\
                                        , 0 \
                                        , self->g_H2_cohom_pivots_len[this_ws->pivot.key1] - 1\
                                        , this_ws->pivot.key2 \
                                        , self->g_n_valid_edges);



                    if (idx != self->g_n_valid_edges){

                        this_ws->flag_red_w_trivial = 0;
                        this_ws->flag_red_w_complex = 1;
                        this_ws->flag_append_to_complex = 0;
                          
                        this_ws->reduce_w_bndry = self->g_H2_cohom_pivots[this_ws->pivot.key1][idx].bndry;
                        this_ws->V_col_idx = self->g_H2_cohom_pivots[this_ws->pivot.key1][idx].col_idx;

                        break;

                    }

                      
                 }


                 // Reset m after single reduction
                 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;


      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 (int ws_counter = self->g_jobs[tid - 1]; ws_counter < self->g_jobs[tid]; ws_counter++){


              coboundary_H2_ws* this_ws = self->g_V_ws_H2 + ws_counter;

              coboundary_H2 temp;

              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
                  //printf("\nEmpty. Skipping.");
                  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_append_to_complex){
                  //printf("\nAppend to complex. Nothing to do.");
                  continue;
              }

              if (this_ws->flag_first){

                  this_ws->flag_first = 0;

                  // CHECK WITH TRIVIAL
                  temp.triangle.key1 = this_ws->pivot.key1;
                  temp.triangle.key2 = self->g_edges_list[this_ws->pivot.key2][1];

                  find_H2_cohom_low(self, &temp);

                  if ((temp.low.key1 == this_ws->pivot.key1)\
                      && (temp.low.key2 == this_ws->pivot.key2)){

                        
                      this_ws->flag_red_w_trivial = 1;

                      this_ws->reduce_w_bndry = temp.triangle;
                      this_ws->V_col_idx = 0;


                  }
                  else{


                      if (!self->g_H2_cohom_pivots_len[this_ws->pivot.key1]){
                          
                          //printf("\npivot not in complex c1. append");
                          this_ws->flag_red_w_complex = 0;
                          this_ws->flag_red_w_trivial = 0;
                          this_ws->flag_append_to_complex = 1;
                          continue;
                      }
                      else{

                          EDGE_ID idx = search_H2_cohom_pivots(self->g_H2_cohom_pivots[this_ws->pivot.key1]\
                                        , 0 \
                                        , self->g_H2_cohom_pivots_len[this_ws->pivot.key1] - 1\
                                        , this_ws->pivot.key2 \
                                        , self->g_n_valid_edges);
                          
                          // If this low is not a pivot
                          if (idx == self->g_n_valid_edges){

                                //printf("\npivot not in complex c2. append");
                                this_ws->flag_red_w_complex = 0;
                                this_ws->flag_red_w_trivial = 0;
                                this_ws->flag_append_to_complex = 1;
                                continue;


                          }
                          else{

                                this_ws->flag_red_w_complex = 1;
                                this_ws->reduce_w_bndry = self->g_H2_cohom_pivots[this_ws->pivot.key1][idx].bndry;
                                this_ws->V_col_idx = self->g_H2_cohom_pivots[this_ws->pivot.key1][idx].col_idx;

                          }

                      }

                  }

                    
              }



              if ((!this_ws->flag_red_w_trivial) && (!this_ws->flag_red_w_complex)){
                  //printf("\nno red with trivial and no red with complex");
                  this_ws->flag_append_to_complex = 1;
                  continue;
              }

              //printf("\nReducing...");

              // Presume that this will be flagged to be added to complex
              this_ws->flag_red_w_complex = 0;
              this_ws->flag_red_w_trivial = 0;
              this_ws->flag_append_to_complex = 1;
                    
              //int flag = 0;

              while(1){

                    EDGE_ID check_len = this_ws->v_triangles.last + 1;

                    if (this_ws->V_col_idx){

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

                    }

                    if (check_len > this_ws->v_triangles.max_len){
                        this_ws->v_triangles.max_len = check_len + 100;
                        self->g_V_ws_H2[ws_counter].v_triangles.o_abc = \
                                        (simplex*)realloc(self->g_V_ws_H2[ws_counter].v_triangles.o_abc\
                                                                , this_ws->v_triangles.max_len*sizeof(simplex));

                    }

                    this_ws->v_triangles.o_abc[this_ws->v_triangles.last++] = this_ws->reduce_w_bndry;

                    temp.triangle = this_ws->reduce_w_bndry;


                    find_H2_cohom_greater(self, &(temp), &(this_ws->pivot));

                    coH2_insert_in_implicit_v(self, ws_counter, &(temp), 1);

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

                        // 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[this_ws->V_col_idx];
                        EDGE_ID end = self->g_V_col_indices[this_ws->V_col_idx+1];


                            for (EDGE_ID mm = start; mm < end; mm++){
                                  
                                this_ws->v_triangles.o_abc[this_ws->v_triangles.last++] = self->g_V_sparse_H2[mm];

                                temp.triangle = self->g_V_sparse_H2[mm];

                                // Find the first low greater than or equal pivot
                                find_H2_cohom_greater(self, &(temp), &(this_ws->pivot));

                                coH2_insert_in_implicit_v(self, ws_counter,  &(temp), 1);

                            }

                    }


                    //simplex test_low = this_ws->pivot;


                    reduce_hash_table_coH2(self, ws_counter);

                    if (!this_ws->flag_non_empty){
                        break;
                    }


                    // Check with trivial pair
                    temp.triangle.key1 = this_ws->pivot.key1;
                    temp.triangle.key2 = self->g_edges_list[this_ws->pivot.key2][1];

                    find_H2_cohom_low(self, &temp);

                    if ((temp.low.key1 == this_ws->pivot.key1)\
                        && (temp.low.key2 == this_ws->pivot.key2)){


                            this_ws->reduce_w_bndry = temp.triangle;
                            this_ws->V_col_idx = 0;
                            continue;
                          
                    }

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


                    EDGE_ID idx = search_H2_cohom_pivots(self->g_H2_cohom_pivots[this_ws->pivot.key1]\
                                        , 0 \
                                        , self->g_H2_cohom_pivots_len[this_ws->pivot.key1] - 1\
                                        , this_ws->pivot.key2 \
                                        , self->g_n_valid_edges);


                    if (idx == self->g_n_valid_edges){
                      break;
                    }

                    this_ws->reduce_w_bndry = self->g_H2_cohom_pivots[this_ws->pivot.key1][idx].bndry;
                    this_ws->V_col_idx = self->g_H2_cohom_pivots[this_ws->pivot.key1][idx].col_idx;

              }

          }

      }
        
}


void update_V_coH2(filtration* self, int ws_counter){


    EDGE_ID red_col = 0;

    coboundary_H2_ws* this_ws = self->g_V_ws_H2 + ws_counter;

    self->g_V_sparse_beg_ptr = self->g_V_sparse_ptr;

    if (this_ws->v_triangles.last){

        if ((this_ws->v_triangles.last + self->g_V_sparse_ptr) + 1 > self->g_V_sparse_max){

                    self->g_V_sparse_max = self->g_V_sparse_ptr + this_ws->v_triangles.last + 100000;
                    self->g_V_sparse_H2 = (simplex*)realloc(self->g_V_sparse_H2\
                                                        , self->g_V_sparse_max*sizeof(simplex));

        }

//#ifdef VREDUCE2
        // //TRYING EDIT
        if (this_ws->v_triangles.last > 1){
            sorter4_tim_sort(this_ws->v_triangles.o_abc, this_ws->v_triangles.last);          
        }
//#endif 

        EDGE_ID mm = 0;


        while (1){
            
//#ifdef VREDUCE2
            if ((this_ws->v_triangles.o_abc[mm].key1 == this_ws->v_triangles.o_abc[mm+1].key1) &&
                (this_ws->v_triangles.o_abc[mm].key2 == this_ws->v_triangles.o_abc[mm+1].key2))
            {
                  mm += 2;
                  if (mm == this_ws->v_triangles.last){
                      break;
                  }
                  continue;
            }

//#endif 
            self->g_V_sparse_H2[self->g_V_sparse_ptr++] = this_ws->v_triangles.o_abc[mm++];

//#ifdef VREDUCE2
            if (mm == this_ws->v_triangles.last - 1){
                self->g_V_sparse_H2[self->g_V_sparse_ptr++] = this_ws->v_triangles.o_abc[mm];
                break;
            }
//#endif 

            if (mm == this_ws->v_triangles.last){
                break;
            }
            
        }

        // All have been added
        this_ws->v_triangles.last = 0;


        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++;

        }

    }

    add_H2_pivot(self, this_ws->triangle, this_ws->pivot, red_col);


}


void add_H2_pivot (filtration* self, simplex triangle, simplex pivot, EDGE_ID red_col){
      
      
    if (self->g_H2_cohom_pivots_len[pivot.key1]\
                            == self->g_H2_cohom_pivots_max_len[pivot.key1]){
          
          self->g_H2_cohom_pivots_max_len[pivot.key1] += 5;
          self->g_H2_cohom_pivots[pivot.key1] = (H2_cohom_pivots*)realloc( \
                          self->g_H2_cohom_pivots[pivot.key1] \
                          , self->g_H2_cohom_pivots_max_len[pivot.key1]*sizeof(H2_cohom_pivots));

    }




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

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

                self->g_H2_cohom_pivots[pivot.key1][new_ptr--] =\
                                                       self->g_H2_cohom_pivots[pivot.key1][old_ptr];
                continue;

          }
          break;

    }


    self->g_H2_cohom_pivots[pivot.key1][new_ptr].key2 = pivot.key2;
    self->g_H2_cohom_pivots[pivot.key1][new_ptr].col_idx = red_col;
                            
    self->g_H2_cohom_pivots[pivot.key1][new_ptr].bndry = triangle;

    self->g_H2_cohom_pivots_len[pivot.key1]++;

    // PERS PAIRS
    // Add non-zero barcodes
        
    PAR birth = self->g_edge_parameter[triangle.key1];
    PAR death = self->g_edge_parameter[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, %d) at pair (%d, %d)", triangle.key1\
                                                    , triangle.key2\
                                                    , pivot.key1\
                                                    , pivot.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 reduce_ws_coH2(filtration* self){



      // PARALLEL
      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));
      }


      // SERIAL
      reduce_with_self_coH2(self);


      // CLEARANCE
      int count_valid = 0;

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

            
            if (!self->g_V_ws_H2[ws_counter].flag_non_empty){
                // Add the undead H2

                //printf("\nAdding undead");
                
                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].triangle.key1];

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

                //printf("\nEmpty. Not adding to complex.");
              
                continue;
            }

            if (self->g_V_ws_H2[ws_counter].flag_append_to_complex){
                  //printf("\nAdding to complex.");
                  update_V_coH2(self, ws_counter);
                  continue;
            }

            //printf("\nSwapping...");

            // Swap V
            coboundary_H2_ws 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;

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

            // Run through parallel at least once
            self->g_V_ws_H2[count_valid].flag_append_to_complex = 0;
            
            count_valid++;

      }

      self->g_ws_counter = count_valid;
      
      
}


void reduce_hash_table_coH2(filtration* self, int ws_counter){
      
      
      // Now we have to reduce
      int coeff = 1;
      
      coboundary_H2_ws* this_ws = self->g_V_ws_H2 + ws_counter;

      coboundary_H2 ttemp;
      
      EDGE_ID* k1_ptr = &(this_ws->k1_ptr);
      EDGE_ID* k2_ptr = &(this_ws->k2_ptr);
      
      while (1){
            
            
            if (this_ws->keys1[*k1_ptr].last == 1){
                this_ws->pivot.key1 = this_ws->keys1[*k1_ptr].k1;
                this_ws->pivot.key2 = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].k2;
                break;
            }
      
      
      
            if (this_ws->keys1[*k1_ptr].keys2[*k2_ptr].k2 ==\
                          this_ws->keys1[*k1_ptr].keys2[*k2_ptr+1].k2){
      
                  coeff = 1 - coeff;
      
                  if ((this_ws->keys1[*k1_ptr].keys2[*k2_ptr].o_abc.key1 ==\
                            this_ws->keys1[*k1_ptr].keys2[*k2_ptr+1].o_abc.key1) &&
                          (this_ws->keys1[*k1_ptr].keys2[*k2_ptr].o_abc.key2 ==\
                            this_ws->keys1[*k1_ptr].keys2[*k2_ptr+1].o_abc.key2))
                  {
      
                        if (this_ws->keys1[*k1_ptr].keys2[*k2_ptr].flag_next ==\
                                                this_ws->keys1[*k1_ptr].keys2[*k2_ptr+1].flag_next){
      
                            this_ws->keys1[*k1_ptr].keys2[*k2_ptr].flag_next = 0;
                            this_ws->keys1[*k1_ptr].keys2[*k2_ptr+1].flag_next = 0;
      
                        }
                        
      
                  }
                  
              
            }
            else{
                  
                 if (coeff){
                    
                      this_ws->pivot.key1 = this_ws->keys1[*k1_ptr].k1;
                      this_ws->pivot.key2 = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].k2;
                      break;
      
                 }
                 else{
      
                      coeff = 1;
                 }
            }
      
      
            if (this_ws->keys1[*k1_ptr].keys2[*k2_ptr].flag_next){
      
                ttemp.triangle = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].o_abc;
                ttemp.a_ptr = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].a_ptr;
                ttemp.b_ptr = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].b_ptr;
                ttemp.c_ptr = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].c_ptr;
                ttemp.vertex = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].vertex;
      
                ttemp.low.key1 = this_ws->keys1[*k1_ptr].k1;
                ttemp.low.key2 = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].k2;
      
                find_H2_cohom_next(self, &(ttemp));

      
                this_ws->keys1[*k1_ptr].keys2[*k2_ptr].flag_next = 0;

                coH2_insert_in_implicit_v(self, ws_counter, &(ttemp), 1);

                
            }
      
      
            *k2_ptr = *k2_ptr + 1;
      
            if (*k2_ptr == this_ws->keys1[*k1_ptr].last-1){
      
                if (coeff){
                          this_ws->pivot.key1 = this_ws->keys1[*k1_ptr].k1;
                          this_ws->pivot.key2 = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].k2;
                          break;
                }

                if (this_ws->keys1[*k1_ptr].keys2[*k2_ptr].flag_next){
                      
                      ttemp.triangle  = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].o_abc;
                      ttemp.a_ptr = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].a_ptr;
                      ttemp.b_ptr = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].b_ptr;
                      ttemp.c_ptr = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].c_ptr;
                      ttemp.vertex = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].vertex;
      
                      ttemp.low.key1 = this_ws->keys1[*k1_ptr].k1;
                      ttemp.low.key2 = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].k2;
      

                      find_H2_cohom_next(self, &(ttemp));
      
                      this_ws->keys1[*k1_ptr].keys2[*k2_ptr].flag_next = 0;

                      coH2_insert_in_implicit_v(self, ws_counter, &(ttemp), 1);
      
                      
                }
      
      
                if (*k2_ptr == this_ws->keys1[*k1_ptr].last-2){
                      
                          *k2_ptr = *k2_ptr + 1;
                          this_ws->pivot.key1 = this_ws->keys1[*k1_ptr].k1;
                          this_ws->pivot.key2 = this_ws->keys1[*k1_ptr].keys2[*k2_ptr].k2;
                          break;
                }
      
                else{
      
                          // Mark this key1 as empty
                          this_ws->keys1[*k1_ptr].flag_empty = 1;
      
                          EDGE_ID current_ptr = 0;
                          EDGE_ID minn = self->g_n_valid_edges;
      
                          for (EDGE_ID mm = 0; mm < this_ws->last; mm++){
      
                              if (this_ws->keys1[mm].flag_empty){
                                  continue;
                              }
      
                              coH2_implicit_keys1 ttemp = this_ws->keys1[current_ptr];
      
                              this_ws->keys1[current_ptr] = this_ws->keys1[mm];
                              
                              this_ws->keys1[mm] = ttemp;
      
                              if (this_ws->keys1[current_ptr].k1 < minn){
                                   minn = this_ws->keys1[current_ptr].k1;
                                   *k1_ptr = current_ptr;
                              }
      
                              current_ptr++;
                                
                                
                          }
      
                          this_ws->last = current_ptr;
      
                          if (minn == self->g_n_valid_edges){
                              this_ws->flag_non_empty = 0;
                              break;
                          }
      
                          // Otherwise reset coefficient and begin reduction
                          coeff = 1;
                          *k2_ptr = 0;
      
                          sorter9_tim_sort(this_ws->keys1[*k1_ptr].keys2\
                                            , this_ws->keys1[*k1_ptr].last);          
      
                }
      
                  
            }
      
      
      }
      
}



void coH2_insert_in_implicit_v(filtration* self, int ws_counter, coboundary_H2* phi, int flag_next){


      if (phi->low.key1 == self->g_n_valid_edges){
          return;
      }

      coboundary_H2_ws* this_ws = self->g_V_ws_H2 + ws_counter;


      if (phi->low.key1 == this_ws->keys1[this_ws->k1_ptr].k1){


            if (this_ws->keys1[this_ws->k1_ptr].last ==\
                                            this_ws->keys1[this_ws->k1_ptr].max_len){

                  self->g_V_ws_H2[ws_counter].keys1[self->g_V_ws_H2[ws_counter].k1_ptr].max_len += 10;
                  self->g_V_ws_H2[ws_counter].keys1[self->g_V_ws_H2[ws_counter].k1_ptr].keys2 = \
                                                        (coH2_implicit_keys2*)realloc\
                                                             (self->g_V_ws_H2[ws_counter].keys1[self->g_V_ws_H2[ws_counter].k1_ptr].keys2\
                                                            , self->g_V_ws_H2[ws_counter].keys1[self->g_V_ws_H2[ws_counter].k1_ptr].max_len\
                                                                  *sizeof(coH2_implicit_keys2));
                  
            }


            EDGE_ID mm = this_ws->keys1[this_ws->k1_ptr].last;

            coH2_implicit_keys2* this_key2 = &(this_ws->keys1[this_ws->k1_ptr].keys2[mm-1]);

            int compare;

            while (1){


                if (this_key2->k2 < phi->low.key2) compare = 0;
                else if (this_key2->k2 > phi->low.key2) compare = 1;
                else{

                      if (this_key2->o_abc.key1 < phi->triangle.key1) compare = 0;
                      else if (this_key2->o_abc.key1 > phi->triangle.key1) compare = 1;
                      else{

                          if (this_key2->o_abc.key2 < phi->triangle.key2) compare = 0;
                          else compare = 1;

                      }

                }



                if (compare){

                      this_ws->keys1[this_ws->k1_ptr].keys2[mm] =\
                                                        this_ws->keys1[this_ws->k1_ptr].keys2[mm-1];
                }
                else{
                    
                  
                      coH2_implicit_keys1* this_key1 = &(this_ws->keys1[this_ws->k1_ptr]);
                      coH2_implicit_keys2* this_key2 = &(this_key1->keys2[mm]);

                      this_key2->k2 = phi->low.key2; 
                      this_key2->o_abc = phi->triangle; 
                      this_key2->a_ptr = phi->a_ptr; 
                      this_key2->b_ptr = phi->b_ptr; 
                      this_key2->c_ptr = phi->c_ptr; 
                      this_key2->vertex = phi->vertex; 
                      this_key2->flag_next = flag_next; 

                      this_ws->keys1[this_ws->k1_ptr].last++;


                      return;

                }

                this_key2--;
                mm--;

                if (mm == this_ws->k2_ptr){

                      coH2_implicit_keys1* this_key1 = &(this_ws->keys1[this_ws->k1_ptr]);
                      coH2_implicit_keys2* this_key2 = &(this_key1->keys2[mm]);

                      this_key2->k2 = phi->low.key2; 
                      this_key2->o_abc = phi->triangle; 
                      this_key2->a_ptr = phi->a_ptr; 
                      this_key2->b_ptr = phi->b_ptr; 
                      this_key2->c_ptr = phi->c_ptr; 
                      this_key2->vertex = phi->vertex; 
                      this_key2->flag_next = flag_next; 

                      this_ws->keys1[this_ws->k1_ptr].last++;


                      return;

                }

            }
            
      }

      for (EDGE_ID mm = 0; mm < self->g_V_ws_H2[ws_counter].last; mm++){

            
            if (self->g_V_ws_H2[ws_counter].keys1[mm].k1 == phi->low.key1){
                
                  
                  if (self->g_V_ws_H2[ws_counter].keys1[mm].last ==\
                                                  self->g_V_ws_H2[ws_counter].keys1[mm].max_len){

                        self->g_V_ws_H2[ws_counter].keys1[mm].max_len += 10;
                        self->g_V_ws_H2[ws_counter].keys1[mm].keys2 = (coH2_implicit_keys2*)realloc\
                                                                 (self->g_V_ws_H2[ws_counter].keys1[mm].keys2\
                                                                , self->g_V_ws_H2[ws_counter].keys1[mm].max_len*sizeof(coH2_implicit_keys2));
                        
                  }

                  coH2_implicit_keys1* this_key1 = &(this_ws->keys1[mm]);
                  coH2_implicit_keys2* this_key2 = &(this_key1->keys2[this_key1->last]);

                  this_ws->keys1[mm].flag_empty = 0;
                  

                  this_key2->k2 = phi->low.key2;
                  this_key2->o_abc = phi->triangle;
                  this_key2->a_ptr = phi->a_ptr;
                  this_key2->b_ptr = phi->b_ptr;
                  this_key2->c_ptr = phi->c_ptr;
                  this_key2->vertex = phi->vertex;
                  this_key2->flag_next = flag_next;
                
                  this_key1->last++;

                  return;

                  
            }
            
            
      }


      if (self->g_V_ws_H2[ws_counter].last == self->g_V_ws_H2[ws_counter].max_len){
            
            EDGE_ID mm = self->g_V_ws_H2[ws_counter].max_len;

            self->g_V_ws_H2[ws_counter].max_len += 10;
            self->g_V_ws_H2[ws_counter].keys1 = (coH2_implicit_keys1*)realloc(self->g_V_ws_H2[ws_counter].keys1\
                                                                  , self->g_V_ws_H2[ws_counter].max_len*sizeof(coH2_implicit_keys1));

            while (mm < self->g_V_ws_H2[ws_counter].max_len){
                  
                  this_ws->keys1[mm].flag_empty = 1;
                  this_ws->keys1[mm].max_len = 10;
                  this_ws->keys1[mm].last = 0;
                  self->g_V_ws_H2[ws_counter].keys1[mm].keys2 = (coH2_implicit_keys2*)malloc(10*sizeof(coH2_implicit_keys2));

                  mm++;
                
                  
            }


      }


      coH2_implicit_keys1* this_key1 = &(this_ws->keys1[this_ws->last]);
      coH2_implicit_keys2* this_key2 = &(this_key1->keys2[0]);

      this_key1->flag_empty = 0;
      this_key1->k1 = phi->low.key1;
      this_key2->k2 = phi->low.key2;

      this_key2->o_abc = phi->triangle;
      this_key2->a_ptr = phi->a_ptr;
      this_key2->b_ptr = phi->b_ptr;
      this_key2->c_ptr = phi->c_ptr;
      this_key2->vertex = phi->vertex;
      this_key2->flag_next = flag_next;

      this_key1->last = 1;
      this_ws->last++;


      return;


        
}





void compute_num_simplices(filtration* self){

    
      self->g_n_all_simp  = (BIGINT)(self->g_n_vert) + (BIGINT)(self->g_n_valid_edges);
      printf("\n");

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


        printf("\redge%d", o_ab);
            
         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->g_Neighbors[a][a_ptr].order;

                    if (o_ac > o_ab){
                        a_ptr++;
                        b_ptr++;
                        continue;
                    }

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

                    if (o_bc > o_ab){
                        a_ptr++;
                        b_ptr++;
                        continue;
                    }

                    // This is a valid triangle
                    self->g_n_all_simp++;
                    


                    for (VERT_ID mm = 0; mm < self->g_Neigh_len[c]; mm++){

                          if (self->g_Neighbors[c][mm].neighbor < c){
                            continue;
                          }

                          VERT_ID d = self->g_Neighbors[c][mm].neighbor;
                          
                          VERT_ID idx = search_Neighbors(self, a, d, 0, self->g_Neigh_len[a]-1);
                          if (idx == self->g_n_vert) continue;
                          EDGE_ID o_ad = self->g_Neighbors[a][idx].order;
                          if (o_ad > o_ab) continue;

                          idx = search_Neighbors(self, b, d, 0, self->g_Neigh_len[b]-1);
                          if (idx == self->g_n_vert) continue;
                          EDGE_ID o_bd = self->g_Neighbors[b][idx].order;
                          if (o_bd > o_ab) continue;

                          idx = search_Neighbors(self, c, d, 0, self->g_Neigh_len[c]-1);
                          if (idx == self->g_n_vert) continue;
                          EDGE_ID o_cd = self->g_Neighbors[c][idx].order;
                          if (o_cd > o_ab) continue;

                          self->g_n_all_simp++;

                          //printf("\n %d, %d, %d, %d", a, b, c, d);
                          
                          
                    }
                    a_ptr++;
                    b_ptr++;


              }
            
         }
             
      }

      printf("\nNumber of simplices %llu\n", self->g_n_all_simp);

      
}



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);
}










