def UDecompositionFullConnectivity(party_map, q, num_qubit):
    '''
    Naive methods for converting a party map with full connectivity
    '''
    debug_flag = 0

    operation_CNOT = []
    for colunm in range(num_qubit):
        '''set diagonal entry to 1'''
        if party_map[colunm][colunm] == 0:
            for raw in list(range(colunm + 1, num_qubit)):
                if party_map[raw][colunm] == 1:
                    if debug_flag == 1: print('control', raw, 'target', colunm)
                    PerformRawAddinPartyMap(party_map, raw, colunm)
                    if debug_flag == 1: print(party_map)
                    add_CNOT = OperationCNOT(q[raw], q[colunm])
                    operation_CNOT.append(add_CNOT)
                    break

        for raw in list(range(colunm)) + list(range(colunm + 1, num_qubit)):
            if party_map[raw][colunm] == 1:
                if debug_flag == 1: print('control', colunm, 'target', raw)
                PerformRawAddinPartyMap(party_map, colunm, raw)
                if debug_flag == 1: print(party_map)
                add_CNOT = OperationCNOT(q[colunm], q[raw])
                operation_CNOT.append(add_CNOT)

    return operation_CNOT
def RemoteCNOTinArchitectureGraph(path, party_map, q):
    '''
    implement remote CNOT in party map via path of nodes in architecture graph, i.e., [v_c, ..., v_t]
    '''
    operation_CNOT = []
    dis = len(path) - 1
    v_c = path[0]
    v_t = path[-1]
    if dis == 2:
        add_CNOT = OperationCNOT(q[v_c], q[path[1]])
        operation_CNOT.append(add_CNOT)
        PerformRawAddinPartyMap(party_map, v_c, path[1])
        add_CNOT = OperationCNOT(q[path[1]], q[v_t])
        operation_CNOT.append(add_CNOT)
        PerformRawAddinPartyMap(party_map, path[1], v_t)
        add_CNOT = OperationCNOT(q[v_c], q[path[1]])
        operation_CNOT.append(add_CNOT)
        PerformRawAddinPartyMap(party_map, v_c, path[1])
        add_CNOT = OperationCNOT(q[path[1]], q[v_t])
        operation_CNOT.append(add_CNOT)
        PerformRawAddinPartyMap(party_map, path[1], v_t)
    else:
        if dis == 3:
            add_CNOT = OperationCNOT(q[v_c], q[path[1]])
            operation_CNOT.append(add_CNOT)
            PerformRawAddinPartyMap(party_map, v_c, path[1])
            add_CNOT = OperationCNOT(q[path[2]], q[v_t])
            operation_CNOT.append(add_CNOT)
            PerformRawAddinPartyMap(party_map, path[2], v_t)
            add_CNOT = OperationCNOT(q[path[1]], q[path[2]])
            operation_CNOT.append(add_CNOT)
            PerformRawAddinPartyMap(party_map, path[1], path[2])
            add_CNOT = OperationCNOT(q[v_c], q[path[1]])
            operation_CNOT.append(add_CNOT)
            PerformRawAddinPartyMap(party_map, v_c, path[1])
            add_CNOT = OperationCNOT(q[path[1]], q[path[2]])
            operation_CNOT.append(add_CNOT)
            PerformRawAddinPartyMap(party_map, path[1], path[2])
        else:
            '''See Section 2B , arXiv:1904.01972'''
            '''first part'''
            for i in range(dis):
                R = []
                add_CNOT = OperationCNOT(q[path[-1 - i - i]], q[path[-1 - i]])
                R.append(add_CNOT)
            R_prime = copy.deepcopy(R_operation)
            R_prime.pop()
            R_prime.reverse()
            operation_CNOT.extend(R + R_prime)
            R_star = copy.deepcopy(R + R_prime)
            R_star.pop()
            R_star.pop(0)
            operation_CNOT.extend(R_star)

    return operation_CNOT
def FillSteinerTreeRootToLeaf(q, steiner_tree, party_map, terminal_nodes,
                              shortest_path_steiner_tree):
    '''
    Use CNOT operation to set all nodes in steiner tree 1 from root to leaf
    Refresh party map
    Return:
        list of CNOT operations
    '''

    operation_CNOT = []
    num_leaf = len(terminal_nodes) - 1
    root_node = terminal_nodes[0]

    for i in range(num_leaf):
        leaf_node = terminal_nodes[i + 1]
        path = shortest_path_steiner_tree[leaf_node][root_node]
        l = len(path)
        for current_pos in range(l - 2, -1, -1):
            current_node = path[current_pos]
            son_node = path[current_pos + 1]
            if steiner_tree.node[current_node]['party_map_value'] == 0:
                add_CNOT = OperationCNOT(q[son_node], q[current_node])
                operation_CNOT.append(add_CNOT)
                party_map[current_node][:] = np.logical_xor(
                    party_map[current_node][:], party_map[son_node][:])
                steiner_tree.node[current_node]['party_map_value'] = 1

    return operation_CNOT
def QiskitGateToOperation(Gate):
    '''
    convert a Qiskit Gate object to OperationU
    only support CNOT
    '''
    if Gate.name == 'cx':
        qargs = Gate.qargs
        return OperationCNOT(qargs[0], qargs[1])

    return None
def EmptySteinerTree(q, steiner_tree, party_map, terminal_nodes):

    operation_CNOT = []
    DFT_edges = list(nx.dfs_edges(steiner_tree, source=terminal_nodes[0]))
    DFT_edges.reverse()

    for edge in DFT_edges:
        current_node = edge[0]
        son_node = edge[1]
        add_CNOT = OperationCNOT(q[current_node], q[son_node])
        operation_CNOT.append(add_CNOT)
        party_map[son_node][:] = np.logical_xor(party_map[current_node][:],
                                                party_map[son_node][:])
        steiner_tree.node[son_node]['party_map_value'] = 1

    return operation_CNOT
def UDecompositionFullConnectivityPATEL(party_map, q, num_qubit, m=None):
    '''
    Optimal methods for converting a party map with full connectivity
    Using partition
    See "OPTIMAL SYNTHESIS OF LINEAR REVERSIBLE CIRCUITS"
    '''
    operation_CNOT = []
    '''set value of m'''
    if m == None:
        cuurent_m = int(np.log2(num_qubit))
        while int(num_qubit / cuurent_m) != (num_qubit / cuurent_m):
            cuurent_m -= 1
        m = cuurent_m
    '''transform to upper triangle'''
    '''traverse all sections'''
    for start_col in range(0, num_qubit, m):
        list_all_col = list(range(start_col, start_col + m))
        '''delete duplicate sub-rows'''
        for raw in list_all_col:
            module = party_map[raw][list_all_col]
            if np.sum(module) > 1:
                for raw2 in range(list_all_col[-1] + 1, num_qubit):
                    module2 = party_map[raw2][list_all_col]
                    if np.array_equal(module, module2) == True:
                        PerformRawAddinPartyMap(party_map, raw, raw2)
                        add_CNOT = OperationCNOT(q[raw], q[raw2])
                        operation_CNOT.append(add_CNOT)
        '''set values of all entries in each column of current subsection'''
        for current_col_sec in list_all_col:
            '''set diagonal entry to 1'''
            if party_map[current_col_sec][current_col_sec] != 1:
                for i in range(current_col_sec + 1, num_qubit):
                    if party_map[i][current_col_sec] == 1:
                        PerformRawAddinPartyMap(party_map, i, current_col_sec)
                        add_CNOT = OperationCNOT(q[i], q[current_col_sec])
                        operation_CNOT.append(add_CNOT)
                        break
            '''set other entries in this column'''
            for current_raw in range(current_col_sec + 1, num_qubit):
                if party_map[current_raw][current_col_sec] == 1:
                    PerformRawAddinPartyMap(party_map, current_col_sec,
                                            current_raw)
                    add_CNOT = OperationCNOT(q[current_col_sec],
                                             q[current_raw])
                    operation_CNOT.append(add_CNOT)
    '''transform to upper identity'''
    '''traverse all sections'''
    for start_col in range(num_qubit - 1, -1, -1 * m):
        list_all_col = list(range(start_col, start_col - m, -1))
        '''delete duplicate sub-rows'''
        for raw in list_all_col:
            module = party_map[raw][list_all_col]
            if np.sum(module) > 1:
                for raw2 in range(list_all_col[-1] - 1, -1, -1):
                    module2 = party_map[raw2][list_all_col]
                    if np.array_equal(module, module2) == True:
                        PerformRawAddinPartyMap(party_map, raw, raw2)
                        add_CNOT = OperationCNOT(q[raw], q[raw2])
                        operation_CNOT.append(add_CNOT)
        '''set values of all entries in each column of current subsection'''
        for current_col_sec in list_all_col:
            '''set diagonal entry to 1'''
            if party_map[current_col_sec][current_col_sec] != 1:
                for i in range(current_col_sec - 1, -1, -1):
                    if party_map[i][current_col_sec] == 1:
                        PerformRawAddinPartyMap(party_map, i, current_col_sec)
                        add_CNOT = OperationCNOT(q[i], q[current_col_sec])
                        operation_CNOT.append(add_CNOT)
                        break
            '''set other entries in this column'''
            for current_raw in range(current_col_sec - 1, -1, -1):
                if party_map[current_raw][current_col_sec] == 1:
                    PerformRawAddinPartyMap(party_map, current_col_sec,
                                            current_raw)
                    add_CNOT = OperationCNOT(q[current_col_sec],
                                             q[current_raw])
                    operation_CNOT.append(add_CNOT)

    return operation_CNOT
def EliminateOneEntryInColumn(party_map, steiner_tree_total, sub_steiner_trees,
                              sub_trees_root_node, terminal_nodes, q):
    '''
    See chapter 4.1, arXiv:1904.01972
    '''
    flag_debug = 0

    operation_CNOT = []

    for i in range(len(sub_steiner_trees)):
        '''if root node of a sub Steiner tree is not the biggest'''
        current_tree = sub_steiner_trees[i]
        current_root = sub_trees_root_node[i]
        leaf_nodes = FindAllLeafNodesInTree(current_tree)
        if current_root < max(leaf_nodes):
            #print('testing')
            add_operations = EliminateOneEntryInColumn(party_map,
                                                       steiner_tree_total,
                                                       [steiner_tree_total],
                                                       [max(terminal_nodes)],
                                                       terminal_nodes, q)
            operation_CNOT.extend(add_operations)
            return operation_CNOT
        '''else'''

    for i in range(len(sub_steiner_trees)):
        tree = sub_steiner_trees[i]
        root_node = sub_trees_root_node[i]
        R_operation = []
        shortest_length_tree = dict(
            nx.shortest_path_length(tree,
                                    source=None,
                                    target=None,
                                    weight=None,
                                    method='dijkstra'))
        BFS_edges = list(nx.bfs_edges(tree, source=root_node))
        BFS_edges.reverse()
        if flag_debug == 1: print('BFS edges are', BFS_edges)
        for edge in BFS_edges:
            if shortest_length_tree[edge[0]][root_node] < shortest_length_tree[
                    edge[1]][root_node]:
                control_q = q[edge[0]]
                target_q = q[edge[1]]
            else:
                control_q = q[edge[1]]
                target_q = q[edge[0]]
            R_operation.append(OperationCNOT(control_q, target_q))
        '''calculate R_prime'''
        R_prime = copy.deepcopy(R_operation)
        '''delete redundancy of R_prime'''
        for operation in copy.copy(R_prime):
            if operation.control_qubit[1] == root_node:
                R_prime.remove(operation)

        #R_prime.pop()
        R_prime.reverse()
        '''calculate R_star'''
        add_R_star = []
        leaf_nodes = FindAllLeafNodesInTree(tree)
        R_star = copy.deepcopy(R_operation) + copy.deepcopy(R_prime)
        for operation in copy.copy(R_star):
            if (operation.target_qubit[1]
                    in terminal_nodes) and (operation.target_qubit[1]
                                            in leaf_nodes):
                R_star.remove(operation)
            '''这个部分不正确,但是可以用来预估增加的CNOT数目'''
            if (operation.target_qubit[1] in terminal_nodes) and (
                    not operation.target_qubit[1] in leaf_nodes):
                add_R_star.append(
                    OperationCNOT(operation.target_qubit,
                                  operation.control_qubit))
        add_R_star_copy = copy.deepcopy(add_R_star)
        add_R_star_copy.reverse()
        if add_R_star != []: R_star = add_R_star + R_star + add_R_star_copy
        '''total operation'''
        R_total = R_operation + R_prime + R_star
        operation_CNOT.extend(R_total)
        if flag_debug == 1: print('R_operation')
        PerformOperationCNOTinPartyMap(party_map, R_operation)
        if flag_debug == 1: print('R_prime')
        PerformOperationCNOTinPartyMap(party_map, R_prime)
        if flag_debug == 1: print('R_star')
        PerformOperationCNOTinPartyMap(party_map, R_star)
        #PerformOperationCNOTinPartyMap(party_map, R_total)

    return operation_CNOT