def optimize_1q_gates(circuit): """Simplify runs of single qubit gates in the QX basis. Return a new circuit that has been optimized. """ qx_basis = ["u1", "u2", "u3", "cx", "id"] dag_unroller = DagUnroller(circuit, DAGBackend(qx_basis)) unrolled = dag_unroller.expand_gates() runs = unrolled.collect_runs(["u1", "u2", "u3", "id"]) for run in runs: qname = 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] assert nd["condition"] is None, "internal error" assert len(nd["qargs"]) == 1, "internal error" assert nd["qargs"][0] == qname, "internal error" left_name = nd["name"] assert left_name in ["u1", "u2", "u3", "id"], "internal error" if left_name == "u1": left_parameters = (N(0), N(0), nd["params"][0]) elif left_name == "u2": left_parameters = (sympy.pi / 2, nd["params"][0], nd["params"][1]) elif left_name == "u3": left_parameters = tuple(nd["params"]) 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_params = [] if right_name == "u1": new_params = [right_parameters[2]] if right_name == "u2": new_params = [right_parameters[1], right_parameters[2]] if right_name == "u3": new_params = list(right_parameters) nx.set_node_attributes(unrolled.multi_graph, name='name', values={run[0]: right_name}) # params is a list of sympy symbols nx.set_node_attributes(unrolled.multi_graph, name='params', values={run[0]: new_params}) # 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, 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