Exemple #1
0
def order_matrix(m: GenericMatrix, input_order: list, matrix_input_order: list,
                 output_order: list,
                 matrix_output_order: list) -> GenericMatrix:
    """Transforms the matrix so the order of the I/O is change accordingly to the args. This is only a permutation on
    the lines and the columns.

    Args:
        m (GenericMatrix): initial matrix
        input_order (list): order of the inputs as they are expected
        matrix_input_order (list): order of the inputs on the initial matrix
        output_order (list): order of the outputs as they are expected
        matrix_output_order (list): order of the outputs on the initial matrix

    Returns:
        GenericMatrix: matrix of the same size but with.
    """
    if len(input_order) != len(matrix_input_order):
        raise ValueError('input_order and matrix_input_order length differ')
    if len(output_order) != len(matrix_output_order):
        raise ValueError('output_order and matrix_output_order length differ')
    _height, _width = m.shape
    _input_base_size = max(_width.bit_length() - 1, 0)
    _output_base_size = max(_height.bit_length() - 1, 0)
    _result = deepcopy(UsedFragment(m))

    def _permutation_dictionary(pre_permutation_list, post_permutation_list):
        length = len(pre_permutation_list)
        permutation_dic = {}
        for i in np.arange(length):
            for j in np.arange(length):
                # as before, since we are in little endian representation, we need to switch the order for the wires
                if pre_permutation_list[length - 1 -
                                        i] == post_permutation_list[length -
                                                                    1 - j]:
                    permutation_dic[i] = j
                    break
        return permutation_dic

    _input_dict = _permutation_dictionary(input_order, matrix_input_order)
    _output_dict = _permutation_dictionary(output_order, matrix_output_order)

    def _transformation(_i: EnhancedInt, _permutation: dict):
        length = len(_permutation)
        image = EnhancedInt(0)
        for _k in np.arange(length):
            image[_k] = _i[_permutation[_k]]
        return image

    _matrix = deepcopy(UsedFragment(m))
    for _i in np.arange(2**_output_base_size):
        _enhanced_i = EnhancedInt(_i)
        _new_i = _transformation(_enhanced_i, _output_dict)
        for _j in np.arange(2**_input_base_size):
            _enhanced_j = EnhancedInt(_j)
            # the following operations with the _input_base_size and the _new_output_base_size are needed because
            # _transformation treats the numbers in little endian and the wires order is given in big endian
            _new_j = _transformation(_enhanced_j, _input_dict)
            _result[_new_i, _new_j] = _matrix[_i, _j]

    return _result
Exemple #2
0
def test_tensor_product():
    m1 = UsedFragment(np.identity(2))
    m2 = UsedFragment([[0, 1], [1, 0]])
    expected_result = UsedFragment(
        np.matrix([[0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]))
    result = m1.tensor_product(m2)
    assert not (result - expected_result).any()
Exemple #3
0
def test_fallback_tensor_power():
    h = UsedFragment([[1, 1], [1, -1]])
    expected_result = UsedFragment([[1, 1, 1, 1], [1, -1, 1, -1],
                                    [1, 1, -1, -1], [1, -1, -1, 1]])
    assert not (qf.tensor_power(h, 0) - [[1]]).any()
    assert not (qf.tensor_power(h, 1) - h).any()
    assert not (qf.tensor_power(h, 2) - expected_result).any()
Exemple #4
0
def no_node_matrix(edges: List[Edge], inputs: List[Wire],
                   outputs: List[Wire]) -> GenericMatrix:
    """no_node_matrix(edges: List[Edge], inputs: List[Wire], outputs: List[Wire]) -> GenericMatrix

    Works similarly to split and reunite but without any node

    Args:
        edges (List[Edge]): edges in the diagram considered
        inputs (List[Wire]): inputs in the diagram considered
        outputs (List[Wire]): outputs in the diagram considered

    Returns:
        GenericMatrix: matrix corresponding to the given diagram
    """
    if 2 * len(edges) != len(inputs) + len(outputs):
        raise ValueError(
            "len(edges) != len(inputs) + len(outputs) : len(edges) == %d, len(inputs) == %d and "
            "len(outputs) == %d" % (len(edges), len(inputs), len(outputs)))
    if len(edges) == 0:
        return UsedFragment(1)
    if len(edges) == 1:
        if len(inputs) == 0:
            return UsedFragment([[1], [0], [0], [1]])
        if len(inputs) == 1:
            return UsedFragment([[1, 0], [0, 1]])
        if len(inputs) == 2:
            return UsedFragment([[1, 0, 0, 1]])
        else:
            raise RuntimeError("Unhandled case of no node matrix")
    else:
        half = len(edges) // 2
        first_half_edges = edges[:half]
        second_half_edges = edges[half:]

        first_half_inputs, first_half_outputs = filter_inputs_outputs_by_edges(
            first_half_edges, inputs, outputs)
        second_half_inputs, second_half_outputs = filter_inputs_outputs_by_edges(
            second_half_edges, inputs, outputs)

        first_half_matrix = no_node_matrix(first_half_edges, first_half_inputs,
                                           first_half_outputs)
        second_half_matrix = no_node_matrix(second_half_edges,
                                            second_half_inputs,
                                            second_half_outputs)

        input_connections = wires_to_connection_point_edge_sorted(
            inputs, first_half_edges, second_half_edges, False)
        output_connections = wires_to_connection_point_edge_sorted(
            outputs, first_half_edges, second_half_edges, True)

        return divide_conquer.fusion_matrices(first_half_matrix,
                                              second_half_matrix,
                                              input_connections,
                                              output_connections, [])
Exemple #5
0
def twisted_multiplication(m1: GenericMatrix, m2: GenericMatrix,
                           covering: int) -> GenericMatrix:
    """Core of this module, this section is half way between the serialization of the two matrices (one being put after
    the other) and the parallelisation of the matrices (one being put next to the other). This means that you can have
    wires going from a matrix to another, wires directly inputting to the second matrix and wire directly outputting
    from the first matrix (see conception document for more information)

    Args:
        m1 (GenericMatrix): first matrix
        m2 (GenericMatrix): second matrix
        covering (int): number of wires linking the two matrices

    Returns:
        GenericMatrix: matrix resulting of this operation
    """
    list_m1 = []  # type: List[UsedFragment]
    list_m2 = []  # type: List[UsedFragment]
    height_m1, width_m1 = EnhancedInt(m1.shape[0]), EnhancedInt(m1.shape[1])
    height_m2, width_m2 = EnhancedInt(m2.shape[0]), EnhancedInt(m2.shape[1])
    if height_m1.count(1) != 1 or width_m1.count(1) != 1:
        raise ValueError(
            'Matrices shape should be perfect powers of 2, but (%d,%d) received for m1'
            % (height_m1, width_m1))
    if height_m2.count(1) != 1 or width_m2.count(1) != 1:
        raise ValueError(
            'Matrices shape should be perfect powers of 2, but (%d,%d) received for m2'
            % (height_m1, width_m1))
    for i in np.arange(height_m1 >> covering):
        list_m1.append(m1[i * 2**covering:(i + 1) * 2**covering, :])
    for i in np.arange(width_m2 >> covering):
        list_m2.append(m2[:, i::width_m2 >> covering])

    list_m12 = []  # type: List[List[UsedFragment]]
    for i in np.arange(height_m1 >> covering):
        list_m12_i = []  # type: List[UsedFragment]
        for j in np.arange(width_m2 >> covering):
            list_m12_i.append(list_m2[j].dot(list_m1[i]))
        list_m12.append(list_m12_i)

    result = UsedFragment(
        np.zeros(((height_m1 * height_m2) >> covering,
                  (width_m2 * width_m1) >> covering)))
    for i in np.arange(height_m1 >> covering):
        for j in np.arange(width_m2 >> covering):
            result[i * height_m2:(i + 1) * height_m2,
                   j::width_m2 >> covering] = list_m12[i][j]
    return result
Exemple #6
0
def tensor_product(a: GenericMatrix, b: GenericMatrix) -> GenericMatrix:
    """tensor_product(a: GenericMatrix, b: GenericMatrix) -> GenericMatrix

    Computes the tensor product of matrix *a* and *b*
    
    Args:
        a (GenericMatrix): First argument, have to be a matrix (numpy matrix or 2-D array)
        b (GenericMatrix): Second argument, have to be a matrix (numpy matrix or 2-D array)
    Returns:
        GenericMatrix: Tensor product of a and b
    """
    ma, na = a.shape
    mb, nb = b.shape
    mr, nr = ma * mb, na * nb
    result = UsedFragment(np.zeros((mr, nr), dtype=complex))
    for i in np.arange(mr):
        for j in np.arange(nr):
            result[i, j] = a[i // mb, j // nb] * b[i % mb, j % nb]
    return result
Exemple #7
0
def tensor_power(a: GenericMatrix, power: int) -> GenericMatrix:
    """tensor_power(a: GenericMatrix, power: int) -> GenericMatrix

    Computes the *a**power*, in the tensor sense

    Args:
        a (GenericMatrix): First argument, has to be a matrix (numpy matrix or 2-D array)
        power (int): The power to elevate a to, must be positive (or equal to 0)
    Returns:
        GenericMatrix: 'power'
    """
    if power < 0:
        raise ValueError('Tensor power not defined for a negative power')
    if power == 0:
        return UsedFragment(np.identity(1))
    else:
        try:
            return a.tensor_product(a.tensor_power(power - 1))
        except AttributeError:
            return tensor_product(a, tensor_power(a, power - 1))
Exemple #8
0
def output_to_input(m: GenericMatrix,
                    output_index: int,
                    input_index: int = -1) -> GenericMatrix:
    """Transforms *m* to have the output connected to *output_index* becoming an input connected to to *input_index*.

    Args:
        m (GenericMatrix): matrix to transform
        output_index (int): index of the output being taken off
        input_index (int): index of the output being added

    Returns:
        GenericMatrix: *m* with its number of lines divided by 2, its number of columns multiplied by 2 and the
            coefficients moved accordingly (see conception document for more information)
    """
    height, width = m.shape
    input_base_size = width.bit_length() - 1
    new_input_base_size = input_base_size + 1
    output_base_size = height.bit_length() - 1
    new_output_base_size = output_base_size - 1
    if input_index not in list(np.arange(new_input_base_size)) + list(
            np.arange(-new_input_base_size, 0)):
        raise ValueError(
            "Index out of bound, m input base size : %d and index1 given : %d"
            % (input_base_size, input_index))
    if output_index not in np.arange(output_base_size):
        raise ValueError(
            "Index out of bound, m output base size : %d and index2 given : %d"
            % (output_base_size, output_index))
    try:
        _result = UsedFragment(np.zeros(
            (2**new_output_base_size, 2**new_input_base_size)),
                               z=m.z)
    except AttributeError:
        _result = UsedFragment(
            np.zeros((2**new_output_base_size, 2**new_input_base_size)))

    def _transformation(_i: EnhancedInt, _j: EnhancedInt, in_index: int,
                        out_index: int):
        """

        Args:
            _i: represent the output to transform
            _j: represent the input to transform
            in_index: index of the destination
            out_index: index of the output to be taken

        Returns:

        """

        _j.insert(in_index, _i[out_index])
        del _i[out_index]
        return _i, _j

    _matrix = UsedFragment(m)
    for _i in np.arange(2**output_base_size):
        for _j in np.arange(2**input_base_size):
            _enhanced_i = EnhancedInt(_i)
            _enhanced_j = EnhancedInt(_j)
            if input_index < 0:
                _input_index = new_input_base_size + input_index
            else:
                _input_index = input_index
            # the following operations with the input_base_size and the new_output_base_size are needed because
            # _transformation treats the numbers in little endian and the wires order is given in big endian
            _position = _transformation(_enhanced_i, _enhanced_j,
                                        new_input_base_size - 1 - _input_index,
                                        output_base_size - 1 - output_index)
            _result[_position] = _matrix[_i, _j]
    return _result
Exemple #9
0
def split_and_reunite(graph: Graph) -> GenericMatrix:
    """Recursive function taking in a graph and returning the corresponding matrix.

    To do so, split the graph in two, passes the two halves to it's next iteration and reunite the two matrix obtained
    using the :ref:`fusion_matrices <fusion_matrices>` method from :ref:`divide_conquer`.

    The main part of this function is converting the graph format to the matrix format. tmp

    Args:
        graph (Graph): diagram considered

    Returns:
        GenericMatrix: matrix corresponding to the given diagram
    """
    if len(graph.nodes) == 0:
        return no_node_matrix(graph.edges, graph.inputs, graph.outputs)
    elif len(graph.nodes) == 1 and not no_node_edges_detection(graph.edges):
        try:
            return UsedFragment.node_to_matrix(graph.nodes[0],
                                               len(graph.inputs),
                                               len(graph.outputs))
        except AttributeError:
            return fallback_node_to_matrix(graph.nodes[0], len(graph.inputs),
                                           len(graph.outputs))
    else:
        graph1, graph2 = connected_graphs_split(graph)
        if not graph2:
            # we rewrite graph1 and graph2 so they contain two parts of the current graph1
            if no_node_edges_detection(graph.edges):
                # probably dead code since if a graph has such an edge (containing no node, only I/O), and has nodes,
                # the connected_graphs_split function would return two distinct graphs
                #
                # degenerate cases, when a graph contains only wires
                # in this case, graph1 will contain the I/O connected to another I/O and graph2 will contain the rest
                graph2 = Graph(nodes=graph.nodes)

                graph2 += filter_edges_inputs_outputs_by_nodes(
                    graph2.nodes, graph)
                graph1 = graph - graph2
            else:
                if graph.inputs:
                    graph1 = Graph(inputs=[graph.inputs[0]])
                    graph1.augment(graph)
                elif graph.nodes:
                    graph1 = Graph(nodes=[graph.nodes[0]])
                else:
                    raise RuntimeError(
                        'A graph with no node shouldn\'t enter in this branch')

                graph1 += graph1.neighbouring_i_o(graph)
                graph2 = graph - graph1

                in_between_edges = between_graphs_edges(graph1, graph2, graph)

                graph1.edges += in_between_edges

                in_between_wires = []
                for edge in in_between_edges:
                    in_between_wires.append(Wire(edge.name))

                graph1.outputs += in_between_wires
                graph2.inputs += in_between_wires

            first_half_matrix = split_and_reunite(graph1)
            second_half_matrix = split_and_reunite(graph2)

            inter_matrix_link = matrix_linker(graph1, graph2)
        else:
            first_half_matrix = split_and_reunite(graph1)
            second_half_matrix = split_and_reunite(graph2)
            inter_matrix_link = []

        input_connections = wires_to_connection_point_node_sorted(
            graph.inputs, graph.edges, graph1.nodes, graph2.nodes, False)
        output_connections = wires_to_connection_point_node_sorted(
            graph.outputs, graph.edges, graph1.nodes, graph2.nodes, True)

        return divide_conquer.fusion_matrices(first_half_matrix,
                                              second_half_matrix,
                                              input_connections,
                                              output_connections,
                                              inter_matrix_link)