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
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
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
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
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
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
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
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
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