Esempio n. 1
0
def direction_mapper(circuit_graph, coupling_graph):
    """Change the direction of CNOT gates to conform to CouplingGraph.

    circuit_graph = input DAGCircuit
    coupling_graph = corresponding CouplingGraph

    Adds "h" to the circuit basis.

    Returns a DAGCircuit object containing a circuit equivalent to
    circuit_graph but with CNOT gate directions matching the edges
    of coupling_graph. Raises an exception if the circuit_graph
    does not conform to the coupling_graph.
    """
    if "cx" not in circuit_graph.basis:
        return circuit_graph
    if circuit_graph.basis["cx"] != (2, 0, 0):
        raise MapperError("cx gate has unexpected signature %s" %
                          circuit_graph.basis["cx"])

    qr_fcx = QuantumRegister(2, "fcx")
    flipped_cx_circuit = DAGCircuit()
    flipped_cx_circuit.add_qreg(qr_fcx)
    flipped_cx_circuit.add_basis_element("CX", 2)
    flipped_cx_circuit.add_basis_element("U", 1, 0, 3)
    flipped_cx_circuit.add_basis_element("cx", 2)
    flipped_cx_circuit.add_basis_element("u2", 1, 0, 2)
    flipped_cx_circuit.add_basis_element("h", 1)
    flipped_cx_circuit.add_gate_data("cx", cx_data)
    flipped_cx_circuit.add_gate_data("u2", u2_data)
    flipped_cx_circuit.add_gate_data("h", h_data)
    flipped_cx_circuit.apply_operation_back(HGate(qr_fcx[0]))
    flipped_cx_circuit.apply_operation_back(HGate(qr_fcx[1]))
    flipped_cx_circuit.apply_operation_back(CnotGate(qr_fcx[1], qr_fcx[0]))
    flipped_cx_circuit.apply_operation_back(HGate(qr_fcx[0]))
    flipped_cx_circuit.apply_operation_back(HGate(qr_fcx[1]))

    q_tmp = QuantumRegister(coupling_graph.size(), 'q')
    cg_edges = [((q_tmp, i), (q_tmp, j)) for i, j in coupling_graph.get_edges()]

    for cx_node in circuit_graph.get_named_nodes("cx"):
        nd = circuit_graph.multi_graph.node[cx_node]
        cxedge = tuple(nd["qargs"])
        if cxedge in cg_edges:
            logger.debug("cx %s[%d], %s[%d] -- OK",
                         cxedge[0][0], cxedge[0][1],
                         cxedge[1][0], cxedge[1][1])
            continue
        elif (cxedge[1], cxedge[0]) in cg_edges:
            circuit_graph.substitute_circuit_one(cx_node,
                                                 flipped_cx_circuit,
                                                 wires=[qr_fcx[0], qr_fcx[1]])
            logger.debug("cx %s[%d], %s[%d] -FLIP",
                         cxedge[0][0], cxedge[0][1],
                         cxedge[1][0], cxedge[1][1])
        else:
            raise MapperError("circuit incompatible with CouplingGraph: "
                              "cx on %s" % pprint.pformat(cxedge))
    return circuit_graph
Esempio n. 2
0
    def yzy_to_zyz(xi, theta1, theta2, eps=1e-9):  # pylint: disable=invalid-name
        """Express a Y.Z.Y single qubit gate as a Z.Y.Z gate.

        Solve the equation

        .. math::

        Ry(theta1).Rz(xi).Ry(theta2) = Rz(phi).Ry(theta).Rz(lambda)

        for theta, phi, and lambda.

        Return a solution theta, phi, and lambda.
        """
        quaternion_yzy = quaternion_from_euler([theta1, xi, theta2], 'yzy')
        euler = quaternion_yzy.to_zyz()
        quaternion_zyz = quaternion_from_euler(euler, 'zyz')
        # output order different than rotation order
        out_angles = (euler[1], euler[0], euler[2])
        abs_inner = abs(quaternion_zyz.data.dot(quaternion_yzy.data))
        if not np.allclose(abs_inner, 1, eps):
            raise MapperError(
                'YZY and ZYZ angles do not give same rotation matrix.')
        out_angles = tuple(0 if np.abs(angle) < _CHOP_THRESHOLD else angle
                           for angle in out_angles)
        return out_angles
Esempio n. 3
0
def yzy_to_zyz(xi, theta1, theta2, eps=1e-9):
    """Express a Y.Z.Y single qubit gate as a Z.Y.Z gate.

    Solve the equation

    .. math::

    Ry(theta1).Rz(xi).Ry(theta2) = Rz(phi).Ry(theta).Rz(lambda)

    for theta, phi, and lambda.

    Return a solution theta, phi, and lambda.
    """
    Q = quaternion_from_euler([theta1, xi, theta2], 'yzy')
    euler = Q.to_zyz()
    P = quaternion_from_euler(euler, 'zyz')
    # output order different than rotation order
    out_angles = (euler[1], euler[0], euler[2])
    abs_inner = abs(P.data.dot(Q.data))
    if not np.allclose(abs_inner, 1, eps):
        logger.debug("xi=%s", xi)
        logger.debug("theta1=%s", theta1)
        logger.debug("theta2=%s", theta2)
        logger.debug("solutions=%s", out_angles)
        logger.debug("abs_inner=%s", abs_inner)
        raise MapperError('YZY and ZYZ angles do not give same rotation matrix.')
    return out_angles
Esempio n. 4
0
    def run(self, dag):
        """Run one pass of the lookahead mapper on the provided DAG.

        Args:
            dag (DAGCircuit): the directed acyclic graph to be mapped
        Returns:
            DAGCircuit: A dag mapped to be compatible with the coupling_map in
              the property_set.
        Raises:
            MapperError: If the provided DAG has more qubits than are available
              in the coupling map.

        """

        # Preserve fix for https://github.com/Qiskit/qiskit-terra/issues/674
        removed_measures = remove_last_measurements(dag)

        coupling_map = self._coupling_map
        ordered_virtual_gates = list(dag.serial_layers())

        if len(dag.get_qubits()) > len(coupling_map.physical_qubits):
            raise MapperError(
                'DAG contains more qubits than are present in the coupling map.'
            )

        dag_qubits = dag.get_qubits()
        coupling_qubits = coupling_map.physical_qubits

        starting_layout = [
            dag_qubits[i] if i < len(dag_qubits) else None
            for i in range(len(coupling_qubits))
        ]

        mapped_gates = []
        layout = Layout(starting_layout)
        gates_remaining = ordered_virtual_gates.copy()

        while gates_remaining:
            best_step = _search_forward_n_swaps(layout, gates_remaining,
                                                coupling_map)

            layout = best_step['layout']
            gates_mapped = best_step['gates_mapped']
            gates_remaining = best_step['gates_remaining']

            mapped_gates.extend(gates_mapped)

        # Preserve input DAG's name, regs, wire_map, etc. but replace the graph.
        mapped_dag = _copy_circuit_metadata(dag, coupling_map)

        for gate in mapped_gates:
            mapped_dag.apply_operation_back(**gate)

        return_last_measurements(mapped_dag, removed_measures, layout)

        return mapped_dag
Esempio n. 5
0
    def run(self, dag):
        """Return a new circuit that has been optimized."""
        runs = dag.collect_runs(["u1", "u2", "u3", "id"])
        for run in runs:
            right_name = "u1"
            right_parameters = (0, 0, 0)  # (theta, phi, lambda)

            for current_node in run:
                left_name = current_node.name
                if (current_node.condition is not None
                        or len(current_node.qargs) != 1
                        or left_name not in ["u1", "u2", "u3", "id"]):
                    raise MapperError("internal error")
                if left_name == "u1":
                    left_parameters = (0, 0, current_node.op.params[0])
                elif left_name == "u2":
                    left_parameters = (np.pi / 2, current_node.op.params[0],
                                       current_node.op.params[1])
                elif left_name == "u3":
                    left_parameters = tuple(current_node.op.params)
                else:
                    left_name = "u1"  # replace id with u1
                    left_parameters = (0, 0, 0)
                # If there are any sympy objects coming from the gate convert
                # to numpy.
                left_parameters = tuple([float(x) for x in left_parameters])
                # Compose gates
                name_tuple = (left_name, right_name)
                if name_tuple == ("u1", "u1"):
                    # u1(lambda1) * u1(lambda2) = u1(lambda1 + lambda2)
                    right_parameters = (0, 0, right_parameters[2] +
                                        left_parameters[2])
                elif name_tuple == ("u1", "u2"):
                    # u1(lambda1) * u2(phi2, lambda2) = u2(phi2 + lambda1, lambda2)
                    right_parameters = (np.pi / 2, right_parameters[1] +
                                        left_parameters[2],
                                        right_parameters[2])
                elif name_tuple == ("u2", "u1"):
                    # u2(phi1, lambda1) * u1(lambda2) = u2(phi1, lambda1 + lambda2)
                    right_name = "u2"
                    right_parameters = (np.pi / 2, left_parameters[1],
                                        right_parameters[2] +
                                        left_parameters[2])
                elif name_tuple == ("u1", "u3"):
                    # u1(lambda1) * u3(theta2, phi2, lambda2) =
                    #     u3(theta2, phi2 + lambda1, lambda2)
                    right_parameters = (right_parameters[0],
                                        right_parameters[1] +
                                        left_parameters[2],
                                        right_parameters[2])
                elif name_tuple == ("u3", "u1"):
                    # u3(theta1, phi1, lambda1) * u1(lambda2) =
                    #     u3(theta1, phi1, lambda1 + lambda2)
                    right_name = "u3"
                    right_parameters = (left_parameters[0], left_parameters[1],
                                        right_parameters[2] +
                                        left_parameters[2])
                elif name_tuple == ("u2", "u2"):
                    # Using Ry(pi/2).Rz(2*lambda).Ry(pi/2) =
                    #    Rz(pi/2).Ry(pi-2*lambda).Rz(pi/2),
                    # u2(phi1, lambda1) * u2(phi2, lambda2) =
                    #    u3(pi - lambda1 - phi2, phi1 + pi/2, lambda2 + pi/2)
                    right_name = "u3"
                    right_parameters = (np.pi - left_parameters[2] -
                                        right_parameters[1],
                                        left_parameters[1] + np.pi / 2,
                                        right_parameters[2] + np.pi / 2)
                elif name_tuple[1] == "nop":
                    right_name = left_name
                    right_parameters = left_parameters
                else:
                    # For composing u3's or u2's with u3's, use
                    # u2(phi, lambda) = u3(pi/2, phi, lambda)
                    # together with the qiskit.mapper.compose_u3 method.
                    right_name = "u3"
                    # Evaluate the symbolic expressions for efficiency
                    right_parameters = Optimize1qGates.compose_u3(
                        left_parameters[0], left_parameters[1],
                        left_parameters[2], right_parameters[0],
                        right_parameters[1], right_parameters[2])
                    # Why evalf()? This program:
                    #   OPENQASM 2.0;
                    #   include "qelib1.inc";
                    #   qreg q[2];
                    #   creg c[2];
                    #   u3(0.518016983430947*pi,1.37051598592907*pi,1.36816383603222*pi) q[0];
                    #   u3(1.69867232277986*pi,0.371448347747471*pi,0.461117217930936*pi) q[0];
                    #   u3(0.294319836336836*pi,0.450325871124225*pi,1.46804720442555*pi) q[0];
                    #   measure q -> c;
                    # took >630 seconds (did not complete) to optimize without
                    # calling evalf() at all, 19 seconds to optimize calling
                    # evalf() AFTER compose_u3, and 1 second to optimize
                    # calling evalf() BEFORE compose_u3.
                # 1. Here down, when we simplify, we add f(theta) to lambda to
                # correct the global phase when f(theta) is 2*pi. This isn't
                # necessary but the other steps preserve the global phase, so
                # we continue in that manner.
                # 2. The final step will remove Z rotations by 2*pi.
                # 3. Note that is_zero is true only if the expression is exactly
                # zero. If the input expressions have already been evaluated
                # then these final simplifications will not occur.
                # TODO After we refactor, we should have separate passes for
                # exact and approximate rewriting.

                # Y rotation is 0 mod 2*pi, so the gate is a u1
                if np.mod(right_parameters[0], (2 * np.pi)) == 0 \
                        and right_name != "u1":
                    right_name = "u1"
                    right_parameters = (0, 0, right_parameters[1] +
                                        right_parameters[2] +
                                        right_parameters[0])
                # Y rotation is pi/2 or -pi/2 mod 2*pi, so the gate is a u2
                if right_name == "u3":
                    # theta = pi/2 + 2*k*pi
                    if np.mod((right_parameters[0] - np.pi / 2),
                              (2 * np.pi)) == 0:
                        right_name = "u2"
                        right_parameters = (np.pi / 2, right_parameters[1],
                                            right_parameters[2] +
                                            (right_parameters[0] - np.pi / 2))
                    # theta = -pi/2 + 2*k*pi
                    if np.mod((right_parameters[0] + np.pi / 2),
                              (2 * np.pi)) == 0:
                        right_name = "u2"
                        right_parameters = (np.pi / 2,
                                            right_parameters[1] + np.pi,
                                            right_parameters[2] - np.pi +
                                            (right_parameters[0] + np.pi / 2))
                # u1 and lambda is 0 mod 2*pi so gate is nop (up to a global phase)
                if right_name == "u1" and np.mod(right_parameters[2],
                                                 (2 * np.pi)) == 0:
                    right_name = "nop"

            # Replace the the first node in the run with a dummy DAG which contains a dummy
            # qubit. The name is irrelevant, because substitute_node_with_dag will take care of
            # putting it in the right place.
            run_qarg = (QuantumRegister(1, 'q'), 0)
            new_op = Gate(name="", num_qubits=1, params=[])
            if right_name == "u1":
                new_op = U1Gate(right_parameters[2])
            if right_name == "u2":
                new_op = U2Gate(right_parameters[1], right_parameters[2])
            if right_name == "u3":
                new_op = U3Gate(*right_parameters)

            if right_name != 'nop':
                new_dag = DAGCircuit()
                new_dag.add_qreg(run_qarg[0])
                new_dag.apply_operation_back(new_op, [run_qarg], [])
                dag.substitute_node_with_dag(run[0], new_dag)

            # Delete the other nodes in the run
            for current_node in run[1:]:
                dag.remove_op_node(current_node)
            if right_name == "nop":
                dag.remove_op_node(run[0])

        return dag
Esempio n. 6
0
def swap_mapper(circuit_graph, coupling_graph,
                initial_layout=None,
                basis="cx,u1,u2,u3,id", trials=20, seed=None):
    """Map a DAGCircuit onto a CouplingGraph using swap gates.

    Args:
        circuit_graph (DAGCircuit): input DAG circuit
        coupling_graph (CouplingGraph): coupling graph to map onto
        initial_layout (dict): dict from qubits of circuit_graph to qubits
            of coupling_graph (optional)
        basis (str): basis string specifying basis of output DAGCircuit
        trials (int): number of trials.
        seed (int): initial seed.

    Returns:
        DAGCircuit: object containing a circuit equivalent to
        circuit_graph that respects couplings in coupling_graph, and
        a layout dict mapping qubits of circuit_graph into qubits
        of coupling_graph. The layout may differ from the initial_layout
        if the first layer of gates cannot be executed on the
        initial_layout. Finally, returned is the final layer qubit
        permutation that is needed to add measurements back in.

    Raises:
        MapperError: if there was any error during the mapping or with the
            parameters.
    """
    if circuit_graph.width() > coupling_graph.size():
        raise MapperError("Not enough qubits in CouplingGraph")

    # Schedule the input circuit
    layerlist = list(circuit_graph.layers())
    logger.debug("schedule:")
    for i, v in enumerate(layerlist):
        logger.debug("    %d: %s", i, v["partition"])

    if initial_layout is not None:
        # Check the input layout
        circ_qubits = circuit_graph.get_qubits()
        coup_qubits = coupling_graph.get_qubits()
        qubit_subset = []
        for k, v in initial_layout.items():
            qubit_subset.append(v)
            if k not in circ_qubits:
                raise MapperError("initial_layout qubit %s[%d] not in input "
                                  "DAGCircuit" % (k[0], k[1]))
            if v not in coup_qubits:
                raise MapperError("initial_layout qubit %s[%d] not in input "
                                  "CouplingGraph" % (v[0], v[1]))
    else:
        # Supply a default layout
        qubit_subset = coupling_graph.get_qubits()
        qubit_subset = qubit_subset[0:circuit_graph.width()]
        initial_layout = {a: b for a, b in
                          zip(circuit_graph.get_qubits(), qubit_subset)}

    # Find swap circuit to preceed to each layer of input circuit
    layout = initial_layout.copy()
    layout_max_index = max(map(lambda x: x[1]+1, layout.values()))

    # Construct an empty DAGCircuit with one qreg "q"
    # and the same set of cregs as the input circuit
    dagcircuit_output = DAGCircuit()
    dagcircuit_output.name = circuit_graph.name
    dagcircuit_output.add_qreg("q", layout_max_index)
    for name, size in circuit_graph.cregs.items():
        dagcircuit_output.add_creg(name, size)

    # Make a trivial wire mapping between the subcircuits
    # returned by swap_mapper_layer_update and the circuit
    # we are building
    identity_wire_map = {}
    for j in range(layout_max_index):
        identity_wire_map[("q", j)] = ("q", j)
    for name, size in circuit_graph.cregs.items():
        for j in range(size):
            identity_wire_map[(name, j)] = (name, j)

    first_layer = True  # True until first layer is output
    logger.debug("initial_layout = %s", layout)

    # Iterate over layers
    for i, layer in enumerate(layerlist):

        # Attempt to find a permutation for this layer
        success_flag, best_circ, best_d, best_layout, trivial_flag \
            = layer_permutation(layer["partition"], layout,
                                qubit_subset, coupling_graph, trials, seed)
        logger.debug("swap_mapper: layer %d", i)
        logger.debug("swap_mapper: success_flag=%s,best_d=%s,trivial_flag=%s",
                     success_flag, str(best_d), trivial_flag)

        # If this fails, try one gate at a time in this layer
        if not success_flag:
            logger.debug("swap_mapper: failed, layer %d, "
                         "retrying sequentially", i)
            serial_layerlist = list(layer["graph"].serial_layers())

            # Go through each gate in the layer
            for j, serial_layer in enumerate(serial_layerlist):

                success_flag, best_circ, best_d, best_layout, trivial_flag \
                    = layer_permutation(serial_layer["partition"],
                                        layout, qubit_subset, coupling_graph,
                                        trials, seed)
                logger.debug("swap_mapper: layer %d, sublayer %d", i, j)
                logger.debug("swap_mapper: success_flag=%s,best_d=%s,"
                             "trivial_flag=%s",
                             success_flag, str(best_d), trivial_flag)

                # Give up if we fail again
                if not success_flag:
                    raise MapperError("swap_mapper failed: " +
                                      "layer %d, sublayer %d" % (i, j) +
                                      ", \"%s\"" %
                                      serial_layer["graph"].qasm(
                                          no_decls=True,
                                          aliases=layout))

                # If this layer is only single-qubit gates,
                # and we have yet to see multi-qubit gates,
                # continue to the next inner iteration
                if trivial_flag and first_layer:
                    logger.debug("swap_mapper: skip to next sublayer")
                    continue

                # Update the record of qubit positions for each inner iteration
                layout = best_layout
                # Update the QASM
                dagcircuit_output.compose_back(
                    swap_mapper_layer_update(j,
                                             first_layer,
                                             best_layout,
                                             best_d,
                                             best_circ,
                                             serial_layerlist),
                    identity_wire_map)
                # Update initial layout
                if first_layer:
                    initial_layout = layout
                    first_layer = False

        else:
            # Update the record of qubit positions for each iteration
            layout = best_layout

            # Update the QASM
            dagcircuit_output.compose_back(
                swap_mapper_layer_update(i,
                                         first_layer,
                                         best_layout,
                                         best_d,
                                         best_circ,
                                         layerlist),
                identity_wire_map)
            # Update initial layout
            if first_layer:
                initial_layout = layout
                first_layer = False

    # This is the final layout that we need to correctly replace
    # any measurements that needed to be removed before the swap
    last_layout = layout

    # If first_layer is still set, the circuit only has single-qubit gates
    # so we can use the initial layout to output the entire circuit
    if first_layer:
        layout = initial_layout
        for i, layer in enumerate(layerlist):
            dagcircuit_output.compose_back(layer["graph"], layout)

    # Parse openqasm_output into DAGCircuit object
    dag_unrolled = DagUnroller(dagcircuit_output,
                               DAGBackend(basis.split(",")))
    dagcircuit_output = dag_unrolled.expand_gates()
    return dagcircuit_output, initial_layout, last_layout
Esempio n. 7
0
def layer_permutation(layer_partition, layout, qubit_subset, coupling, trials,
                      seed=None):
    """Find a swap circuit that implements a permutation for this layer.

    The goal is to swap qubits such that qubits in the same two-qubit gates
    are adjacent.

    Based on Sergey Bravyi's algorithm.

    The layer_partition is a list of (qu)bit lists and each qubit is a
    tuple (qreg, index).
    The layout is a dict mapping qubits in the circuit to qubits in the
    coupling graph and represents the current positions of the data.
    The qubit_subset is the subset of qubits in the coupling graph that
    we have chosen to map into.
    The coupling is a CouplingGraph.
    TRIALS is the number of attempts the randomized algorithm makes.

    Returns: success_flag, best_circ, best_d, best_layout, trivial_flag

    If success_flag is True, then best_circ contains a DAGCircuit with
    the swap circuit, best_d contains the depth of the swap circuit, and
    best_layout contains the new positions of the data qubits after the
    swap circuit has been applied. The trivial_flag is set if the layer
    has no multi-qubit gates.
    """
    if seed is not None:
        np.random.seed(seed)
    logger.debug("layer_permutation: ----- enter -----")
    logger.debug("layer_permutation: layer_partition = %s",
                 pprint.pformat(layer_partition))
    logger.debug("layer_permutation: layout = %s",
                 pprint.pformat(layout))
    logger.debug("layer_permutation: qubit_subset = %s",
                 pprint.pformat(qubit_subset))
    logger.debug("layer_permutation: trials = %s", trials)
    rev_layout = {b: a for a, b in layout.items()}
    gates = []
    for layer in layer_partition:
        if len(layer) > 2:
            raise MapperError("Layer contains >2 qubit gates")
        elif len(layer) == 2:
            gates.append(tuple(layer))

    logger.debug("layer_permutation: gates = %s", pprint.pformat(gates))

    # Find layout maximum index
    layout_max_index = max(map(lambda x: x[1]+1, layout.values()))

    # Can we already apply the gates?
    dist = sum([coupling.distance(layout[g[0]],
                                  layout[g[1]]) for g in gates])
    logger.debug("layer_permutation: dist = %s", dist)
    if dist == len(gates):
        logger.debug("layer_permutation: done already")
        logger.debug("layer_permutation: ----- exit -----")
        circ = DAGCircuit()
        circ.add_qreg('q', layout_max_index)
        circ.add_basis_element("CX", 2)
        circ.add_basis_element("cx", 2)
        circ.add_basis_element("swap", 2)
        circ.add_gate_data("cx", cx_data)
        circ.add_gate_data("swap", swap_data)
        return True, circ, 0, layout, bool(gates)

    # Begin loop over trials of randomized algorithm
    n = coupling.size()
    best_d = sys.maxsize  # initialize best depth
    best_circ = None  # initialize best swap circuit
    best_layout = None  # initialize best final layout
    for trial in range(trials):

        logger.debug("layer_permutation: trial %s", trial)
        trial_layout = layout.copy()
        rev_trial_layout = rev_layout.copy()
        # SWAP circuit constructed this trial
        trial_circ = DAGCircuit()
        trial_circ.add_qreg('q', layout_max_index)

        # Compute Sergey's randomized distance
        xi = {}
        for i in coupling.get_qubits():
            xi[i] = {}
        for i in coupling.get_qubits():
            for j in coupling.get_qubits():
                scale = 1 + np.random.normal(0, 1 / n)
                xi[i][j] = scale * coupling.distance(i, j) ** 2
                xi[j][i] = xi[i][j]

        # Loop over depths d up to a max depth of 2n+1
        d = 1
        # Circuit for this swap slice
        circ = DAGCircuit()
        circ.add_qreg('q', layout_max_index)
        circ.add_basis_element("CX", 2)
        circ.add_basis_element("cx", 2)
        circ.add_basis_element("swap", 2)
        circ.add_gate_data("cx", cx_data)
        circ.add_gate_data("swap", swap_data)

        # Identity wire-map for composing the circuits
        identity_wire_map = {('q', j): ('q', j) for j in range(layout_max_index)}

        while d < 2 * n + 1:
            # Set of available qubits
            qubit_set = set(qubit_subset)
            # While there are still qubits available
            while qubit_set:
                # Compute the objective function
                min_cost = sum([xi[trial_layout[g[0]]][trial_layout[g[1]]]
                                for g in gates])
                # Try to decrease objective function
                progress_made = False
                # Loop over edges of coupling graph
                for e in coupling.get_edges():
                    # Are the qubits available?
                    if e[0] in qubit_set and e[1] in qubit_set:
                        # Try this edge to reduce the cost
                        new_layout = trial_layout.copy()
                        new_layout[rev_trial_layout[e[0]]] = e[1]
                        new_layout[rev_trial_layout[e[1]]] = e[0]
                        rev_new_layout = rev_trial_layout.copy()
                        rev_new_layout[e[0]] = rev_trial_layout[e[1]]
                        rev_new_layout[e[1]] = rev_trial_layout[e[0]]
                        # Compute the objective function
                        new_cost = sum([xi[new_layout[g[0]]][new_layout[g[1]]]
                                        for g in gates])
                        # Record progress if we succceed
                        if new_cost < min_cost:
                            logger.debug("layer_permutation: progress! "
                                         "min_cost = %s", min_cost)
                            progress_made = True
                            min_cost = new_cost
                            opt_layout = new_layout
                            rev_opt_layout = rev_new_layout
                            opt_edge = e

                # Were there any good choices?
                if progress_made:
                    qubit_set.remove(opt_edge[0])
                    qubit_set.remove(opt_edge[1])
                    trial_layout = opt_layout
                    rev_trial_layout = rev_opt_layout
                    circ.apply_operation_back("swap", [(opt_edge[0][0],
                                                        opt_edge[0][1]),
                                                       (opt_edge[1][0],
                                                        opt_edge[1][1])])
                    logger.debug("layer_permutation: chose pair %s",
                                 pprint.pformat(opt_edge))
                else:
                    break

            # We have either run out of qubits or failed to improve
            # Compute the coupling graph distance
            dist = sum([coupling.distance(trial_layout[g[0]],
                                          trial_layout[g[1]]) for g in gates])
            logger.debug("layer_permutation: dist = %s", dist)
            # If all gates can be applied now, we are finished
            # Otherwise we need to consider a deeper swap circuit
            if dist == len(gates):
                logger.debug("layer_permutation: all can be applied now")
                trial_circ.compose_back(circ, identity_wire_map)
                break

            # Increment the depth
            d += 1
            logger.debug("layer_permutation: increment depth to %s", d)

        # Either we have succeeded at some depth d < dmax or failed
        dist = sum([coupling.distance(trial_layout[g[0]],
                                      trial_layout[g[1]]) for g in gates])
        logger.debug("layer_permutation: dist = %s", dist)
        if dist == len(gates):
            if d < best_d:
                logger.debug("layer_permutation: got circuit with depth %s", d)
                best_circ = trial_circ
                best_layout = trial_layout
            best_d = min(best_d, d)

    if best_circ is None:
        logger.debug("layer_permutation: failed!")
        logger.debug("layer_permutation: ----- exit -----")
        return False, None, None, None, False

    logger.debug("layer_permutation: done")
    logger.debug("layer_permutation: ----- exit -----")
    return True, best_circ, best_d, best_layout, False
Esempio n. 8
0
def optimize_1q_gates(circuit):
    """Simplify runs of single qubit gates in the QX basis.

    Return a new circuit that has been optimized.
    """
    from qiskit.transpiler.passes.mapping.unroller import Unroller
    qx_basis = ["u1", "u2", "u3", "cx", "id"]
    unrolled = Unroller(qx_basis).run(circuit)

    runs = unrolled.collect_runs(["u1", "u2", "u3", "id"])
    for run in runs:
        run_qarg = unrolled.multi_graph.node[run[0]]["qargs"][0]
        right_name = "u1"
        right_parameters = (N(0), N(0), N(0))  # (theta, phi, lambda)
        for current_node in run:
            nd = unrolled.multi_graph.node[current_node]
            left_name = nd["name"]
            if (nd["condition"] is not None or len(nd["qargs"]) != 1
                    or nd["qargs"][0] != run_qarg
                    or left_name not in ["u1", "u2", "u3", "id"]):
                raise MapperError("internal error")
            if left_name == "u1":
                left_parameters = (N(0), N(0), nd["op"].param[0])
            elif left_name == "u2":
                left_parameters = (sympy.pi / 2, nd["op"].param[0],
                                   nd["op"].param[1])
            elif left_name == "u3":
                left_parameters = tuple(nd["op"].param)
            else:
                left_name = "u1"  # replace id with u1
                left_parameters = (N(0), N(0), N(0))
            # Compose gates
            name_tuple = (left_name, right_name)
            if name_tuple == ("u1", "u1"):
                # u1(lambda1) * u1(lambda2) = u1(lambda1 + lambda2)
                right_parameters = (N(0), N(0),
                                    right_parameters[2] + left_parameters[2])
            elif name_tuple == ("u1", "u2"):
                # u1(lambda1) * u2(phi2, lambda2) = u2(phi2 + lambda1, lambda2)
                right_parameters = (sympy.pi / 2,
                                    right_parameters[1] + left_parameters[2],
                                    right_parameters[2])
            elif name_tuple == ("u2", "u1"):
                # u2(phi1, lambda1) * u1(lambda2) = u2(phi1, lambda1 + lambda2)
                right_name = "u2"
                right_parameters = (sympy.pi / 2, left_parameters[1],
                                    right_parameters[2] + left_parameters[2])
            elif name_tuple == ("u1", "u3"):
                # u1(lambda1) * u3(theta2, phi2, lambda2) =
                #     u3(theta2, phi2 + lambda1, lambda2)
                right_parameters = (right_parameters[0],
                                    right_parameters[1] + left_parameters[2],
                                    right_parameters[2])
            elif name_tuple == ("u3", "u1"):
                # u3(theta1, phi1, lambda1) * u1(lambda2) =
                #     u3(theta1, phi1, lambda1 + lambda2)
                right_name = "u3"
                right_parameters = (left_parameters[0], left_parameters[1],
                                    right_parameters[2] + left_parameters[2])
            elif name_tuple == ("u2", "u2"):
                # Using Ry(pi/2).Rz(2*lambda).Ry(pi/2) =
                #    Rz(pi/2).Ry(pi-2*lambda).Rz(pi/2),
                # u2(phi1, lambda1) * u2(phi2, lambda2) =
                #    u3(pi - lambda1 - phi2, phi1 + pi/2, lambda2 + pi/2)
                right_name = "u3"
                right_parameters = (sympy.pi - left_parameters[2] -
                                    right_parameters[1],
                                    left_parameters[1] + sympy.pi / 2,
                                    right_parameters[2] + sympy.pi / 2)
            elif name_tuple[1] == "nop":
                right_name = left_name
                right_parameters = left_parameters
            else:
                # For composing u3's or u2's with u3's, use
                # u2(phi, lambda) = u3(pi/2, phi, lambda)
                # together with the qiskit.mapper.compose_u3 method.
                right_name = "u3"
                # Evaluate the symbolic expressions for efficiency
                left_parameters = tuple(
                    map(lambda x: x.evalf(), list(left_parameters)))
                right_parameters = tuple(
                    map(lambda x: x.evalf(), list(right_parameters)))
                right_parameters = compose_u3(left_parameters[0],
                                              left_parameters[1],
                                              left_parameters[2],
                                              right_parameters[0],
                                              right_parameters[1],
                                              right_parameters[2])
                # Why evalf()? This program:
                #   OPENQASM 2.0;
                #   include "qelib1.inc";
                #   qreg q[2];
                #   creg c[2];
                #   u3(0.518016983430947*pi,1.37051598592907*pi,1.36816383603222*pi) q[0];
                #   u3(1.69867232277986*pi,0.371448347747471*pi,0.461117217930936*pi) q[0];
                #   u3(0.294319836336836*pi,0.450325871124225*pi,1.46804720442555*pi) q[0];
                #   measure q -> c;
                # took >630 seconds (did not complete) to optimize without
                # calling evalf() at all, 19 seconds to optimize calling
                # evalf() AFTER compose_u3, and 1 second to optimize
                # calling evalf() BEFORE compose_u3.
            # 1. Here down, when we simplify, we add f(theta) to lambda to
            # correct the global phase when f(theta) is 2*pi. This isn't
            # necessary but the other steps preserve the global phase, so
            # we continue in that manner.
            # 2. The final step will remove Z rotations by 2*pi.
            # 3. Note that is_zero is true only if the expression is exactly
            # zero. If the input expressions have already been evaluated
            # then these final simplifications will not occur.
            # TODO After we refactor, we should have separate passes for
            # exact and approximate rewriting.

            # Y rotation is 0 mod 2*pi, so the gate is a u1
            if (right_parameters[0] % (2 * sympy.pi)).is_zero \
                    and right_name != "u1":
                right_name = "u1"
                right_parameters = (0, 0, right_parameters[1] +
                                    right_parameters[2] + right_parameters[0])
            # Y rotation is pi/2 or -pi/2 mod 2*pi, so the gate is a u2
            if right_name == "u3":
                # theta = pi/2 + 2*k*pi
                if ((right_parameters[0] - sympy.pi / 2) %
                    (2 * sympy.pi)).is_zero:
                    right_name = "u2"
                    right_parameters = (sympy.pi / 2, right_parameters[1],
                                        right_parameters[2] +
                                        (right_parameters[0] - sympy.pi / 2))
                # theta = -pi/2 + 2*k*pi
                if ((right_parameters[0] + sympy.pi / 2) %
                    (2 * sympy.pi)).is_zero:
                    right_name = "u2"
                    right_parameters = (sympy.pi / 2,
                                        right_parameters[1] + sympy.pi,
                                        right_parameters[2] - sympy.pi +
                                        (right_parameters[0] + sympy.pi / 2))
            # u1 and lambda is 0 mod 2*pi so gate is nop (up to a global phase)
            if right_name == "u1" and (right_parameters[2] %
                                       (2 * sympy.pi)).is_zero:
                right_name = "nop"
            # Simplify the symbolic parameters
            right_parameters = tuple(
                map(sympy.simplify, list(right_parameters)))
        # Replace the data of the first node in the run
        new_op = Instruction("", [], [], [])
        if right_name == "u1":
            new_op = U1Gate(right_parameters[2], run_qarg)
        if right_name == "u2":
            new_op = U2Gate(right_parameters[1], right_parameters[2], run_qarg)
        if right_name == "u3":
            new_op = U3Gate(*right_parameters, run_qarg)

        nx.set_node_attributes(unrolled.multi_graph,
                               name='name',
                               values={run[0]: right_name})
        nx.set_node_attributes(unrolled.multi_graph,
                               name='op',
                               values={run[0]: new_op})
        # Delete the other nodes in the run
        for current_node in run[1:]:
            unrolled._remove_op_node(current_node)
        if right_name == "nop":
            unrolled._remove_op_node(run[0])
    return unrolled
Esempio n. 9
0
def swap_mapper(circuit_graph,
                coupling_graph,
                initial_layout=None,
                trials=20,
                seed=None):
    """Map a DAGCircuit onto a CouplingGraph using swap gates.

    Args:
        circuit_graph (DAGCircuit): input DAG circuit
        coupling_graph (CouplingGraph): coupling graph to map onto
        initial_layout (dict): dict {(str, int): (str, int)}
            from qubits of circuit_graph to qubits of coupling_graph (optional)
        trials (int): number of trials.
        seed (int): initial seed.

    Returns:
        DAGCircuit: object containing a circuit equivalent to
        circuit_graph that respects couplings in coupling_graph, and
        a layout dict mapping qubits of circuit_graph into qubits
        of coupling_graph. The layout may differ from the initial_layout
        if the first layer of gates cannot be executed on the
        initial_layout.

    Raises:
        MapperError: if there was any error during the mapping or with the
            parameters.
    """
    if circuit_graph.width() > coupling_graph.size():
        raise MapperError("Not enough qubits in CouplingGraph")

    # Schedule the input circuit
    layerlist = list(circuit_graph.layers())
    logger.debug("schedule:")
    for i, v in enumerate(layerlist):
        logger.debug("    %d: %s", i, v["partition"])

    if initial_layout is not None:
        # update initial_layout from a user given dict{(regname,idx): (regname,idx)}
        # to an expected dict{(reg,idx): (reg,idx)}
        device_register = QuantumRegister(coupling_graph.size(), 'q')
        initial_layout = {(circuit_graph.qregs[k[0]], k[1]):
                          (device_register, v[1])
                          for k, v in initial_layout.items()}
        # Check the input layout
        circ_qubits = circuit_graph.get_qubits()
        coup_qubits = [(QuantumRegister(coupling_graph.size(), 'q'), wire)
                       for wire in coupling_graph.physical_qubits]
        qubit_subset = []
        for k, v in initial_layout.items():
            qubit_subset.append(v)
            if k not in circ_qubits:
                raise MapperError("initial_layout qubit %s[%d] not in input "
                                  "DAGCircuit" % (k[0].name, k[1]))
            if v not in coup_qubits:
                raise MapperError("initial_layout qubit %s[%d] not in input "
                                  "CouplingGraph" % (v[0].name, v[1]))
    else:
        # Supply a default layout
        qubit_subset = [(QuantumRegister(coupling_graph.size(), 'q'), wire)
                        for wire in coupling_graph.physical_qubits]
        qubit_subset = qubit_subset[0:circuit_graph.width()]
        initial_layout = {
            a: b
            for a, b in zip(circuit_graph.get_qubits(), qubit_subset)
        }

    # Find swap circuit to preceed to each layer of input circuit
    layout = initial_layout.copy()

    # Construct an empty DAGCircuit with one qreg "q"
    # and the same set of cregs as the input circuit
    dagcircuit_output = DAGCircuit()
    dagcircuit_output.name = circuit_graph.name
    dagcircuit_output.add_qreg(QuantumRegister(coupling_graph.size(), "q"))
    for creg in circuit_graph.cregs.values():
        dagcircuit_output.add_creg(creg)

    # Make a trivial wire mapping between the subcircuits
    # returned by swap_mapper_layer_update and the circuit
    # we are building
    identity_wire_map = {}
    q = QuantumRegister(coupling_graph.size(), 'q')
    for j in range(coupling_graph.size()):
        identity_wire_map[(q, j)] = (q, j)
    for creg in circuit_graph.cregs.values():
        for j in range(creg.size):
            identity_wire_map[(creg, j)] = (creg, j)

    first_layer = True  # True until first layer is output
    logger.debug("initial_layout = %s", layout)

    # Iterate over layers
    for i, layer in enumerate(layerlist):

        # Attempt to find a permutation for this layer
        success_flag, best_circ, best_d, best_layout, trivial_flag \
            = layer_permutation(layer["partition"], layout,
                                qubit_subset, coupling_graph, trials, seed)
        logger.debug("swap_mapper: layer %d", i)
        logger.debug("swap_mapper: success_flag=%s,best_d=%s,trivial_flag=%s",
                     success_flag, str(best_d), trivial_flag)

        # If this fails, try one gate at a time in this layer
        if not success_flag:
            logger.debug(
                "swap_mapper: failed, layer %d, "
                "retrying sequentially", i)
            serial_layerlist = list(layer["graph"].serial_layers())

            # Go through each gate in the layer
            for j, serial_layer in enumerate(serial_layerlist):

                success_flag, best_circ, best_d, best_layout, trivial_flag \
                    = layer_permutation(serial_layer["partition"],
                                        layout, qubit_subset, coupling_graph,
                                        trials, seed)
                logger.debug("swap_mapper: layer %d, sublayer %d", i, j)
                logger.debug(
                    "swap_mapper: success_flag=%s,best_d=%s,"
                    "trivial_flag=%s", success_flag, str(best_d), trivial_flag)

                # Give up if we fail again
                if not success_flag:
                    raise MapperError("swap_mapper failed: " +
                                      "layer %d, sublayer %d" % (i, j) +
                                      ", \"%s\"" % serial_layer["graph"].qasm(
                                          no_decls=True, aliases=layout))

                # If this layer is only single-qubit gates,
                # and we have yet to see multi-qubit gates,
                # continue to the next inner iteration
                if trivial_flag and first_layer:
                    logger.debug("swap_mapper: skip to next sublayer")
                    continue

                # Update the record of qubit positions for each inner iteration
                layout = best_layout
                # Update the QASM
                dagcircuit_output.compose_back(
                    swap_mapper_layer_update(j, first_layer, best_layout,
                                             best_d, best_circ,
                                             serial_layerlist, coupling_graph),
                    identity_wire_map)
                # Update initial layout
                if first_layer:
                    initial_layout = layout
                    first_layer = False

        else:
            # Update the record of qubit positions for each iteration
            layout = best_layout

            # Update the QASM
            dagcircuit_output.compose_back(
                swap_mapper_layer_update(i, first_layer, best_layout, best_d,
                                         best_circ, layerlist, coupling_graph),
                identity_wire_map)
            # Update initial layout
            if first_layer:
                initial_layout = layout
                first_layer = False

    # If first_layer is still set, the circuit only has single-qubit gates
    # so we can use the initial layout to output the entire circuit
    if first_layer:
        layout = initial_layout
        for i, layer in enumerate(layerlist):
            dagcircuit_output.compose_back(layer["graph"], layout)

    return dagcircuit_output, initial_layout