示例#1
0
文件: optimizer.py 项目: danlkv/qtree
def circ2buckets(qubit_count, circuit, pdict={}, max_depth=None):
    """
    Takes a circuit in the form of list of lists, builds
    corresponding buckets. Buckets contain Tensors
    defining quantum gates. Each bucket
    corresponds to a variable. Each bucket can hold tensors
    acting on it's variable and variables with higher index.

    Parameters
    ----------
    qubit_count : int
            number of qubits in the circuit
    circuit : list of lists
            quantum circuit as returned by
            :py:meth:`operators.read_circuit_file`
    pdict : dict
            Dictionary with placeholders if any parameteric gates
            were unresolved

    max_depth : int
            Maximal depth of the circuit which should be used
    Returns
    -------
    buckets : list of lists
            list of lists (buckets)
    data_dict : dict
            Dictionary with all tensor data
    bra_variables : list
            variables of the output qubits
    ket_variables: list
            variables of the input qubits
    """
    # import pdb
    # pdb.set_trace()

    if max_depth is None:
        max_depth = len(circuit)

    data_dict = {}

    # Let's build buckets for bucket elimination algorithm.
    # The circuit is built from left to right, as it operates
    # on the ket ( |0> ) from the left. We thus first place
    # the bra ( <x| ) and then put gates in the reverse order

    # Fill the variable `frame`
    layer_variables = [
        Var(qubit, name=f'o_{qubit}') for qubit in range(qubit_count)
    ]
    current_var_idx = qubit_count

    # Save variables of the bra
    bra_variables = [var for var in layer_variables]

    # Initialize buckets
    for qubit in range(qubit_count):
        buckets = [[] for qubit in range(qubit_count)]

    # Place safeguard measurement circuits before and after
    # the circuit
    measurement_circ = [[ops.M(qubit) for qubit in range(qubit_count)]]

    combined_circ = functools.reduce(
        lambda x, y: itertools.chain(x, y),
        [measurement_circ, reversed(circuit[:max_depth])])

    # Start building the graph in reverse order
    for layer in combined_circ:
        for op in layer:
            # CUSTOM
            # Swap variables on swap gate
            if isinstance(op, ops.SWAP):
                q1, q2 = op.qubits
                _v1 = layer_variables[q1]
                layer_variables[q1] = layer_variables[q2]
                layer_variables[q2] = _v1
                continue

            # build the indices of the gate. If gate
            # changes the basis of a qubit, a new variable
            # has to be introduced and current_var_idx is increased.
            # The order of indices
            # is always (a_new, a, b_new, b, ...), as
            # this is how gate tensors are chosen to be stored
            variables = []
            current_var_idx_copy = current_var_idx
            min_var_idx = current_var_idx
            for qubit in op.qubits:
                if qubit in op.changed_qubits:
                    variables.extend(
                        [layer_variables[qubit],
                         Var(current_var_idx_copy)])
                    current_var_idx_copy += 1
                else:
                    variables.extend([layer_variables[qubit]])
                min_var_idx = min(min_var_idx, int(layer_variables[qubit]))

            # fill placeholders in parameters if any
            for par, value in op.parameters.items():
                if isinstance(value, ops.placeholder):
                    op._parameters[par] = pdict[value]

            data_key = (op.name, hash((op.name, tuple(op.parameters.items()))))
            # Build a tensor
            t = Tensor(op.name, variables, data_key=data_key)

            # Insert tensor data into data dict
            data_dict[data_key] = op.gen_tensor()

            # Append tensor to buckets
            # first_qubit_var = layer_variables[op.qubits[0]]
            buckets[min_var_idx].append(t)

            # Create new buckets and update current variable frame
            for qubit in op.changed_qubits:
                layer_variables[qubit] = Var(current_var_idx)
                buckets.append([])
                current_var_idx += 1

    # Finally go over the qubits, append measurement gates
    # and collect ket variables
    ket_variables = []

    op = ops.M(0)  # create a single measurement gate object
    data_key = (op.name, hash((op.name, tuple(op.parameters.items()))))
    data_dict.update({data_key: op.gen_tensor()})

    for qubit in range(qubit_count):
        var = layer_variables[qubit]
        new_var = Var(current_var_idx, name=f'i_{qubit}', size=2)
        ket_variables.append(new_var)
        # update buckets and variable `frame`
        buckets[int(var)].append(
            Tensor(op.name, indices=[var, new_var], data_key=data_key))
        buckets.append([])
        layer_variables[qubit] = new_var
        current_var_idx += 1

    return buckets, data_dict, bra_variables, ket_variables
示例#2
0
def circ2graph(qubit_count,
               circuit,
               pdict={},
               max_depth=None,
               omit_terminals=True):
    """
    Constructs a graph from a circuit in the form of a
    list of lists.

    Parameters
    ----------
    qubit_count : int
            number of qubits in the circuit
    circuit : list of lists
            quantum circuit as returned by
            :py:meth:`operators.read_circuit_file`
    pdict : dict
            Dictionary with placeholders if any parameteric gates
            were unresolved
    max_depth : int, default None
            Maximal depth of the circuit which should be used
    omit_terminals : bool, default True
            If terminal nodes should be excluded from the final
            graph.

    Returns
    -------
    graph : networkx.MultiGraph
            Graph which corresponds to the circuit
    data_dict : dict
            Dictionary with all tensor data
    """
    import functools
    import qtree.operators as ops

    if max_depth is None:
        max_depth = len(circuit)

    data_dict = {}

    # Let's build the graph.
    # The circuit is built from left to right, as it operates
    # on the ket ( |0> ) from the left. We thus first place
    # the bra ( <x| ) and then put gates in the reverse order

    # Fill the variable `frame`
    layer_variables = list(range(qubit_count))
    current_var_idx = qubit_count

    # Initialize the graph
    graph = nx.MultiGraph()

    # Populate nodes and save variables of the bra
    bra_variables = []
    for var in layer_variables:
        graph.add_node(var, name=f'o_{var}', size=2)
        bra_variables.append(Var(var, name=f"o_{var}"))

    # Place safeguard measurement circuits before and after
    # the circuit
    measurement_circ = [[ops.M(qubit) for qubit in range(qubit_count)]]

    combined_circ = functools.reduce(
        lambda x, y: itertools.chain(x, y),
        [measurement_circ, reversed(circuit[:max_depth])])

    # Start building the graph in reverse order
    for layer in combined_circ:
        for op in layer:
            # build the indices of the gate. If gate
            # changes the basis of a qubit, a new variable
            # has to be introduced and current_var_idx is increased.
            # The order of indices
            # is always (a_new, a, b_new, b, ...), as
            # this is how gate tensors are chosen to be stored
            variables = []
            current_var_idx_copy = current_var_idx
            for qubit in op.qubits:
                if qubit in op.changed_qubits:
                    variables.extend(
                        [layer_variables[qubit], current_var_idx_copy])
                    graph.add_node(current_var_idx_copy,
                                   name='v_{}'.format(current_var_idx_copy),
                                   size=2)
                    current_var_idx_copy += 1
                else:
                    variables.extend([layer_variables[qubit]])

            # Form a tensor and add a clique to the graph
            # fill placeholders in gate's parameters if any
            for par, value in op.parameters.items():
                if isinstance(value, ops.placeholder):
                    op._parameters[par] = pdict[value]

            data_key = hash((op.name, tuple(op.parameters.items())))
            tensor = {
                'name': op.name,
                'indices': tuple(variables),
                'data_key': data_key
            }

            # Insert tensor data into data dict
            data_dict[data_key] = op.gen_tensor()

            if len(variables) > 1:
                edges = itertools.combinations(variables, 2)
            else:
                edges = [(variables[0], variables[0])]

            graph.add_edges_from(edges, tensor=tensor)

            # Update current variable frame
            for qubit in op.changed_qubits:
                layer_variables[qubit] = current_var_idx
                current_var_idx += 1

    # Finally go over the qubits, append measurement gates
    # and collect ket variables
    ket_variables = []

    op = ops.M(0)  # create a single measurement gate object

    for qubit in range(qubit_count):
        var = layer_variables[qubit]
        new_var = current_var_idx

        ket_variables.append(Var(new_var, name=f'i_{qubit}', size=2))
        # update graph and variable `frame`
        graph.add_node(new_var, name=f'i_{qubit}', size=2)
        data_key = hash((op.name, tuple(op.parameters.items())))
        tensor = {
            'name': op.name,
            'indices': (var, new_var),
            'data_key': data_key
        }

        graph.add_edge(var, new_var, tensor=tensor)
        layer_variables[qubit] = new_var
        current_var_idx += 1

    if omit_terminals:
        graph.remove_nodes_from(tuple(map(int, bra_variables + ket_variables)))

    v = graph.number_of_nodes()
    e = graph.number_of_edges()

    log.info(f"Generated graph with {v} nodes and {e} edges")
    # log.info(f"last index contains from {layer_variables}")

    return graph, data_dict, bra_variables, ket_variables