def match_planar_3D(lattice_size,stabilizer_type,anyon_positions,time_space_weights,boundary_weight = 1.11 ,special_weights=[1,1,1],print_graph=False):


    ## specify matching parameters 
    max_time_separation = 15
    [wS,wT]=time_space_weights
    wB = wS if boundary_weight == 1.11 else boundary_weight

    [w1,w2,w3]=special_weights
    

    total_time=len(anyon_positions)
    nodes_list=[item for sublist in anyon_positions for item in sublist]
    n_nodes=len(nodes_list)




    if n_nodes==0:
        return []
    
    node_index=[]
    count=0

    for x in anyon_positions:
        node_index+=[[count+i for i in range(len(x))]]
        count+=len(x)

    b_node_index=[[index+n_nodes for index in t] for t in node_index]

    all_boundary_nodes=[]
    all_boundary_nodes2=[]


    ## LOOKUP TABLES

    m = 2*lattice_size +1


    weight_lookup={}
    for p in range(-1,m+1):
        weight_lookup[p]={}
        for q in range(-1,m+1):
                        
            weight_lookup[p][q]=abs(p-q)



    nodes1 = []
    nodes2 = []
    weights = []

    ## PART 1: Complete 2 copies of the graph between all real nodes
 
    for i in range(n_nodes -1):
        (pt,p0,p1)=nodes_list[i]

        for j in range(i+1,n_nodes):
            (qt,q0,q1)=nodes_list[j]

            wt=(qt-pt)
            if wt>=max_time_separation: break
            

            wx = weight_lookup[q0][p0]
            wy = weight_lookup[q1][p1]

            weight = (wS*(wx+wy)+wt*wT)

            if (wt,wx,wy) in [(1,2,0),(1,0,2)]:
                weight *= w1
            elif (wt,wx,wy) == (1,0,0):
                weight *= w2
            elif (wt,wx,wy) in [(0,2,0),(0,0,2)]:
                weight *=w3
            #weight = weight_lookup[q0][p0]+weight_lookup[q1][p1]+wt*wT
            
            

            nodes1 +=[i,i+n_nodes]
            nodes2 +=[j,j+n_nodes]
            weights+=[weight,weight]

    ## PART 2: Link each node to its equivalent in the second graph
    boundary_nodes_list=[]

    for i in range(n_nodes):
        
        (pt,p0,p1)=nodes_list[i]

        if stabilizer_type =="star": 
            (bt,b0,b1)=(pt,p0,-1 if p1<lattice_size else m)
        elif stabilizer_type=="plaquette":
            (bt,b0,b1)=(pt,-1 if p0<lattice_size else m,p1)
        else: 
            print "stabilizer_type must be either *star* or *plaquette*"
            sys.exit(0)
            
        boundary_nodes_list+=[(bt,b0,b1)]

        weight = weight_lookup[p0][b0]+weight_lookup[p1][b1]

        nodes1+=[i]
        nodes2+=[i+n_nodes]
        weights+=[int(weight*wB/wS)]
   


            


    if print_graph==True:
        print zip(nodes1,nodes2,weights)
#    print 't0 = ', t0
 #   print 't1 = ', time.time()-tt1
  #  print 't2 = ', t22
   # print 't3 = ', t3
    n_edges=len(nodes1)
    
################ MATCHING ###################
    #tt4 = time.time()    
#    matching=pm.getMatching(2*n_nodes,graphArray)
    matching = pm.getMatching_fast(2*n_nodes,nodes1,nodes2,weights)
   # print 'matching ', time.time()-tt4
#############################################
    
    if print_graph==True:
        print "Matching"
        print matching

    matching_pairs=[[i,matching[i]] for i in range(2*n_nodes) if matching[i]>i]
    
    
    all_positions=nodes_list+boundary_nodes_list
     

    points=[] if len(matching_pairs)==0 else [[all_positions[i] for i in x] for x in matching_pairs]

  
    return points
def match_planar_3D(lattice_size,
                    stabilizer_type,
                    anyon_positions,
                    time_space_weights=[1, 1],
                    boundary_weight=-1,
                    print_graph=False):
    """ Finds a matching to fix the errors in a 3D planar code given the positions of '-1' stabilizer outcomes  
    
    Parameters:
    -----------
    lattice_size -- The dimension of the code
    stabilizer_type -- defines the stabilizer basis, can take the value "star" or "plaquette"
    anyon_positions -- A list of the locations of all '-1' value stabilizers in the 3D parity lattice. [[x0,y0,t0],[x1,y1,t1],...]
    time_space_weights -- The multiplicative weighting that should be assigned to graph edges in the [space,time] dimensions. Default: [1,1]
    boundary_weight -- multiplicative weight to be assigned to edges matching to the boundary. if no boundary_weight specified, set boundary_weight = space_weight 
    print_graph -- Set to True to print the constructed graph. Default: False.

    Returns:
    --------
    A list containing all the input anyon positions grouped into pairs. [[[x0,y0,t0],[x1,y1,t1]],[[x2,y2,t2],... 

    """

    max_time_separation = 15  # This determines the maximum time separation of edges that are added to the graph
    [wS, wT] = time_space_weights
    wB = wS if boundary_weight == -1 else boundary_weight  #if boundary weight not specifiedm, let wB=wS

    total_time = len(anyon_positions)
    nodes_list = [item for sublist in anyon_positions for item in sublist]
    n_nodes = len(nodes_list)

    # exclude edge case where no anyons exist.
    if n_nodes == 0:
        return []

    node_index = []
    count = 0

    for x in anyon_positions:
        node_index += [[count + i for i in range(len(x))]]
        count += len(x)

    b_node_index = [[index + n_nodes for index in t] for t in node_index]

    all_boundary_nodes = []
    all_boundary_nodes2 = []

    ## LOOKUP TABLES

    m = 2 * lattice_size + 1

    weight_lookup = {}
    for p in range(-1, m + 1):
        weight_lookup[p] = {}
        for q in range(-1, m + 1):
            weight_lookup[p][q] = wS * abs(p - q)

    ## CONSTRUCT GRAPH
    ## create a graph containing all possible matchings between pairs of anyons (given constraints)
    ## This is represented as three lists:

    nodes1 = []
    nodes2 = []
    weights = []

    ## PART 1: Complete graph between all real nodes

    for i in range(n_nodes - 1):
        (pt, p0, p1) = nodes_list[i]

        for j in range(i + 1, n_nodes):
            (qt, q0, q1) = nodes_list[j]

            wt = (qt - pt)
            if wt >= max_time_separation: break

            weight = weight_lookup[q0][p0] + weight_lookup[q1][p1] + wt * wT

            nodes1 += [i]
            nodes2 += [j]
            weights += [weight]

    ## PART 2: Generate list of boundary nodes linked to each real node

    boundary_nodes_list = []

    for i in range(n_nodes):

        (pt, p0, p1) = nodes_list[i]

        if stabilizer_type == "star":
            (bt, b0, b1) = (pt, p0, -1 if p1 < lattice_size else m)
        elif stabilizer_type == "plaquette":
            (bt, b0, b1) = (pt, -1 if p0 < lattice_size else m, p1)
        else:
            print "stabilizer_type must be either *star* or *plaquette*"
            sys.exit(0)

        weight = weight_lookup[p0][b0] + weight_lookup[p1][b1]

        nodes1 += [i]
        nodes2 += [i + n_nodes]
        weights += [int(weight * wB / wS)]

        boundary_nodes_list += [(bt, b0, b1)]

## PART 3: Complete graph between all boundary nodes

    for i in range(n_nodes - 1):
        (pt, p0, p1) = boundary_nodes_list[i]

        for j in range(i + 1, n_nodes):
            (qt, q0, q1) = boundary_nodes_list[j]
            wt = (qt - pt)
            if wt >= 5: break

            nodes1 += [n_nodes + i]
            nodes2 += [n_nodes + j]
            weights += [0]

    if print_graph == True:
        print zip(nodes1, nodes2, weights)

    n_edges = len(nodes1)

    ## MAKE MATCHING.
    ## Call the blossom5 perfect matching algorithm to return a matching.
    ## The form of the returned variable <matching> is a list of pairs of node numbers.

    matching = pm.getMatching_fast(2 * n_nodes, nodes1, nodes2, weights)

    if print_graph == True:
        print "Matching"
        print matching

    ## REFORMAT MATCHING PAIRS
    ## Take <matching> and turn it into a list of paired anyon positions.

    matching_pairs = [[i, matching[i]] for i in range(2 * n_nodes)
                      if matching[i] > i]

    all_positions = nodes_list + boundary_nodes_list

    points = [] if len(matching_pairs) == 0 else [
        [all_positions[i] for i in x] for x in matching_pairs
    ]

    return points
def match_planar_3D(lattice_size,stabilizer_type,anyon_positions,time_space_weights,boundary_weight = 1,print_graph=False):

    max_time_separation = 15

    t00 = time.time()

    total_time=len(anyon_positions)
    nodes_list=[item for sublist in anyon_positions for item in sublist]
    n_nodes=len(nodes_list)

    [wS,wT]=time_space_weights
    wB = wS if boundary_weight == 1 else boundary_weight

    if n_nodes==0:
        return []
    
    node_index=[]
    count=0

    for x in anyon_positions:
        node_index+=[[count+i for i in range(len(x))]]
        count+=len(x)

    b_node_index=[[index+n_nodes for index in t] for t in node_index]

    all_boundary_nodes=[]
    


    boundary_node=None
    

    ## LOOKUP TABLES

    m = 2*lattice_size +1


    weight_lookup={}
    for p in range(-1,m+1):
        weight_lookup[p]={}
        for q in range(-1,m+1):
            weight_lookup[p][q]=wS*abs(p-q)

    
#------------------------------------------------------------

    nodes1 = []
    nodes2 = []
    weights = []

    ## PART 1: Complete graph between all real nodes
 
    for i in range(n_nodes -1):
        (pt,p0,p1)=nodes_list[i]

        for j in range(i+1,n_nodes):
            (qt,q0,q1)=nodes_list[j]
            wt=(qt-pt)
            if wt>=max_time_separation: break
                
            weight = weight_lookup[q0][p0]+weight_lookup[q1][p1]+wt*wT

            nodes1 +=[i]
            nodes2 +=[j]
            weights+=[weight]


#-------------------------------------

    ## PART 2: Create boundary node for each 

    for t in range(total_time):

        n_list_t=anyon_positions[t]
        n_nodes_t=len(n_list_t)
        n_index_t=node_index[t]
        b_index_t=b_node_index[t]
        
        n_nodes_t_minus1 = n_nodes_t - 1

        # for every node, create a boundary node and joining edge



        boundary_node_positions_t=[]
        
        if stabilizer_type == "star": 
            for i in range(n_nodes_t):
                (pt,p0,p1)=n_list_t[i]
                (bt,b0,b1)=(t,p0,-1 if p1<lattice_size else m)
                boundary_node_positions_t+=[[bt,b0,b1]]                
                #weight=sW*(abs(p0-b0)+abs(p1-b1))
                weight = weight_lookup[p0][b0]+weight_lookup[p1][b1]

                nodes1+=[n_index_t[i]]
                nodes2+=[b_index_t[i]]
                weights+=[int(weight*wB/wS)]

                if i==0:
                    if isinstance(boundary_node,int):
                        boundary_node_update=b_index_t[0]
                        nodes1+=[boundary_node_update]
                        nodes2+=[boundary_node]
                        weights+=[0]
                        boundary_node=copy.copy(boundary_node_update)

                    else:
                        boundary_node=copy.copy(b_index_t[0])


        elif stabilizer_type=="plaquette":
            for i in range(n_nodes_t):
                (pt,p0,p1)=n_list_t[i]
                (bt,b0,b1)=(t,-1 if p0<lattice_size else m,p1)
                boundary_node_positions_t+=[[bt,b0,b1]]                
                weight = weight_lookup[p0][b0]+weight_lookup[p1][b1]
 #               graphArray+=[[n_index_t[i],b_index_t[i],weight]]
                nodes1+=[n_index_t[i]]
                nodes2+=[b_index_t[i]]
                weights+=[int(weight*wB/wS)]

                if i==0:
                    if isinstance(boundary_node,int):
                        boundary_node_update=b_index_t[0]
#                        graphArray+=[[boundary_node_update,boundary_node,0]]
                        nodes1+=[boundary_node_update]
                        nodes2+=[boundary_node]
                        weights+=[0]

                        boundary_node=copy.copy(boundary_node_update)

                    else:
                        boundary_node=copy.copy(b_index_t[0])

        

        else:
            print "stabilizer_type must be either **star** or **plaquette**"
            return 0
            

    
        ## save one boundary node to connect to other layers

        all_boundary_nodes+=boundary_node_positions_t
        #boundary_nodes_positions_t=
        #[[pt,p0,-1 if p1<lattice_size else 2*lattice_size+1] for [pt,p0,p1] in node_list_t]
        #if stabilizer_type=="star" else [[pt,-1 if p0<lattice_size else 2*lattice_size,p1]]

    
        tt2 = time.time()

        # fully connect boundary nodes with weight 0
        for i in range(n_nodes_t-1):
#            for j in range(n_nodes_t-i-1):
            for j in range(i+1,n_nodes_t):
                
#                graphArray+=[[b_index_t[i],b_index_t[i+j+1],0]]
                nodes1+=[b_index_t[i]]
                #nodes2+=[b_index_t[i+j+1]]
                nodes2+=[b_index_t[j]]
                weights+=[0]
#        t22 += time.time()-tt2


    if print_graph==True:
        print zip(nodes1,nodes2,weights)
#    print 't0 = ', t0
 #   print 't1 = ', time.time()-tt1
  #  print 't2 = ', t22
   # print 't3 = ', t3
    n_edges=len(nodes1)
    
################ MATCHING ###################
    #tt4 = time.time()    
#    matching=pm.getMatching(2*n_nodes,graphArray)
    matching = pm.getMatching_fast(2*n_nodes,nodes1,nodes2,weights)
   # print 'matching ', time.time()-tt4
#############################################
    
    if print_graph==True:
        print "Matching"
        print matching

    matching_pairs=[[i,matching[i]] for i in range(2*n_nodes) if matching[i]>i]
    
    
    all_positions=nodes_list+all_boundary_nodes
     

    points=[] if len(matching_pairs)==0 else [[all_positions[i] for i in x] for x in matching_pairs]

  
    return points
def match_planar_3D(lattice_size,stabilizer_type,anyon_positions,time_space_weights=[1,1],boundary_weight = -1 ,print_graph=False):

    """ Finds a matching to fix the errors in a 3D planar code given the positions of '-1' stabilizer outcomes  
    
    Parameters:
    -----------
    lattice_size -- The dimension of the code
    stabilizer_type -- defines the stabilizer basis, can take the value "star" or "plaquette"
    anyon_positions -- A list of the locations of all '-1' value stabilizers in the 3D parity lattice. [[x0,y0,t0],[x1,y1,t1],...]
    time_space_weights -- The multiplicative weighting that should be assigned to graph edges in the [space,time] dimensions. Default: [1,1]
    boundary_weight -- multiplicative weight to be assigned to edges matching to the boundary. if no boundary_weight specified, set boundary_weight = space_weight 
    print_graph -- Set to True to print the constructed graph. Default: False.

    Returns:
    --------
    A list containing all the input anyon positions grouped into pairs. [[[x0,y0,t0],[x1,y1,t1]],[[x2,y2,t2],... 

    """

    max_time_separation = 15  # This determines the maximum time separation of edges that are added to the graph
    [wS,wT]=time_space_weights
    wB = wS if boundary_weight == -1 else boundary_weight #if boundary weight not specifiedm, let wB=wS

    total_time=len(anyon_positions)
    nodes_list=[item for sublist in anyon_positions for item in sublist]
    n_nodes=len(nodes_list)

    # exclude edge case where no anyons exist.
    if n_nodes==0:
        return []
    
    node_index=[]
    count=0

    for x in anyon_positions:
        node_index+=[[count+i for i in range(len(x))]]
        count+=len(x)

    b_node_index=[[index+n_nodes for index in t] for t in node_index]

    all_boundary_nodes=[]
    all_boundary_nodes2=[]


    ## LOOKUP TABLES

    m = 2*lattice_size +1


    weight_lookup={}
    for p in range(-1,m+1):
        weight_lookup[p]={}
        for q in range(-1,m+1):
            weight_lookup[p][q]=wS*abs(p-q)

    
    ## CONSTRUCT GRAPH
    ## create a graph containing all possible matchings between pairs of anyons (given constraints)
    ## This is represented as three lists: 

    nodes1 = []
    nodes2 = []
    weights = []

    ## PART 1: Complete graph between all real nodes
 
    for i in range(n_nodes -1):
        (pt,p0,p1)=nodes_list[i]

        for j in range(i+1,n_nodes):
            (qt,q0,q1)=nodes_list[j]

            wt=(qt-pt)
            if wt>=max_time_separation: break
                
            weight = weight_lookup[q0][p0]+weight_lookup[q1][p1]+wt*wT

            nodes1 +=[i]
            nodes2 +=[j]
            weights+=[weight]


    ## PART 2: Generate list of boundary nodes linked to each real node
    
    boundary_nodes_list = []

    for i in range(n_nodes):
        
        (pt,p0,p1)=nodes_list[i]

        if stabilizer_type =="star": 
            (bt,b0,b1)=(pt,p0,-1 if p1<lattice_size else m)
        elif stabilizer_type=="plaquette":
            (bt,b0,b1)=(pt,-1 if p0<lattice_size else m,p1)
        else: 
            print "stabilizer_type must be either *star* or *plaquette*"
            sys.exit(0)
        
        weight = weight_lookup[p0][b0]+weight_lookup[p1][b1]

        nodes1+=[i]
        nodes2+=[i+n_nodes]
        weights+=[int(weight*wB/wS)]
        
        boundary_nodes_list+=[(bt,b0,b1)]

    
    
 ## PART 3: Complete graph between all boundary nodes
 
    for i in range(n_nodes -1):
        (pt,p0,p1)=boundary_nodes_list[i]

        for j in range(i+1,n_nodes):
            (qt,q0,q1)=boundary_nodes_list[j]
            wt=(qt-pt)
            if wt>=5: break
                
            nodes1 +=[n_nodes+i]
            nodes2 +=[n_nodes+j]
            weights+=[0]




   


            


    if print_graph==True:
        print zip(nodes1,nodes2,weights)

    n_edges=len(nodes1)
    
    

    ## MAKE MATCHING.
    ## Call the blossom5 perfect matching algorithm to return a matching. 
    ## The form of the returned variable <matching> is a list of pairs of node numbers. 

    matching = pm.getMatching_fast(2*n_nodes,nodes1,nodes2,weights)


    
    if print_graph==True:
        print "Matching"
        print matching

    ## REFORMAT MATCHING PAIRS
    ## Take <matching> and turn it into a list of paired anyon positions. 

    matching_pairs=[[i,matching[i]] for i in range(2*n_nodes) if matching[i]>i]
    
    all_positions=nodes_list+boundary_nodes_list

    points=[] if len(matching_pairs)==0 else [[all_positions[i] for i in x] for x in matching_pairs]

  
    return points