def test_open_control_cy_unrolling(self): """test unrolling of open control gates when gate is in basis""" qc = QuantumCircuit(2) qc.cy(0, 1, ctrl_state=0) dag = circuit_to_dag(qc) unroller = Unroller(['u3', 'cy']) uqc = dag_to_circuit(unroller.run(dag)) ref_circuit = QuantumCircuit(2) ref_circuit.u3(np.pi, 0, np.pi, 0) ref_circuit.cy(0, 1) ref_circuit.u3(np.pi, 0, np.pi, 0) self.assertEqual(uqc, ref_circuit)
def decompose(self): """Call a decomposition pass on this circuit, to decompose one level (shallow decompose). Returns: QuantumCircuit: a circuit one level decomposed """ from qiskit.transpiler.passes.decompose import Decompose from qiskit.converters.circuit_to_dag import circuit_to_dag from qiskit.converters.dag_to_circuit import dag_to_circuit pass_ = Decompose() decomposed_dag = pass_.run(circuit_to_dag(self)) return dag_to_circuit(decomposed_dag)
def test_composite(self): """Test composite gate count.""" qreg = QuantumRegister(self.num_ctrl + self.num_target) qc = QuantumCircuit(qreg, name='composite') qc.append(self.grx.control(self.num_ctrl), qreg) qc.append(self.gry.control(self.num_ctrl), qreg) qc.append(self.gry, qreg[0:self.gry.num_qubits]) qc.append(self.grz.control(self.num_ctrl), qreg) dag = circuit_to_dag(qc) unroller = Unroller(['u3', 'cx']) uqc = dag_to_circuit(unroller.run(dag)) self.log.info('%s gate count: %d', uqc.name, uqc.size()) self.assertTrue(uqc.size() <= 93) # this limit could be changed
def test_rotation_gates(self): """Test controlled rotation gates""" import qiskit.extensions.standard.u1 as u1 import qiskit.extensions.standard.rx as rx import qiskit.extensions.standard.ry as ry import qiskit.extensions.standard.rz as rz num_ctrl = 2 num_target = 1 qreg = QuantumRegister(num_ctrl + num_target) theta = pi / 2 gu1 = u1.U1Gate(theta) grx = rx.RXGate(theta) gry = ry.RYGate(theta) grz = rz.RZGate(theta) ugu1 = ac._unroll_gate(gu1, ['u1', 'u3', 'cx']) ugrx = ac._unroll_gate(grx, ['u1', 'u3', 'cx']) ugry = ac._unroll_gate(gry, ['u1', 'u3', 'cx']) ugrz = ac._unroll_gate(grz, ['u1', 'u3', 'cx']) ugrz.params = grz.params cgu1 = ugu1.control(num_ctrl) cgrx = ugrx.control(num_ctrl) cgry = ugry.control(num_ctrl) cgrz = ugrz.control(num_ctrl) for gate, cgate in zip([gu1, grx, gry, grz], [cgu1, cgrx, cgry, cgrz]): with self.subTest(i=gate.name): if gate.name == 'rz': iden = Operator.from_label('I') zgen = Operator.from_label('Z') op_mat = (np.cos(0.5 * theta) * iden - 1j * np.sin(0.5 * theta) * zgen).data else: op_mat = Operator(gate).data ref_mat = Operator(cgate).data cop_mat = _compute_control_matrix(op_mat, num_ctrl) self.assertTrue( matrix_equal(cop_mat, ref_mat, ignore_phase=True)) cqc = QuantumCircuit(num_ctrl + num_target) cqc.append(cgate, cqc.qregs[0]) dag = circuit_to_dag(cqc) unroller = Unroller(['u3', 'cx']) uqc = dag_to_circuit(unroller.run(dag)) self.log.info('%s gate count: %d', cgate.name, uqc.size()) self.log.info('\n%s', str(uqc)) # these limits could be changed if gate.name == 'ry': self.assertTrue(uqc.size() <= 32) elif gate.name == 'rz': self.assertTrue(uqc.size() <= 40) else: self.assertTrue(uqc.size() <= 20) qc = QuantumCircuit(qreg, name='composite') qc.append(grx.control(num_ctrl), qreg) qc.append(gry.control(num_ctrl), qreg) qc.append(gry, qreg[0:gry.num_qubits]) qc.append(grz.control(num_ctrl), qreg) dag = circuit_to_dag(qc) unroller = Unroller(['u3', 'cx']) uqc = dag_to_circuit(unroller.run(dag)) print(uqc.size()) self.log.info('%s gate count: %d', uqc.name, uqc.size()) self.assertTrue(uqc.size() <= 93) # this limit could be changed
def layer_parser(circ, two_qubit_gate='cx', coupling_map=None): """ Tranforms general circuits into a nice form for a qotp. Args: circ (QuantumCircuit): A generic quantum circuit two_qubit_gate (str): a flag as to which 2 qubit gate to compile with, can be cx or cz coupling_map (list): some particular device topology as list of list (e.g. [[0,1],[1,2],[2,0]]) Returns: dict: A dictionary of the parsed layers with the following keys: ``singlequbit_layers`` (lsit): a list of circuits describing the single qubit gates ``cz_layers`` (list): a list of circuits describing the cz layers ``meas_layer`` (QuantumCircuit): a circuit describing the final measurement Raises: QiskitError: If a circuit element is not implemented in qotp """ # transpile to single qubits and cx # TODO: replace cx with cz when that is available circ_internal = transpile(circ, optimization_level=2, basis_gates=['u1', 'u2', 'u3', 'cx'], coupling_map=coupling_map) # quantum and classial registers qregs = circ_internal.qregs[0] cregs = circ_internal.cregs[0] # conatiners for the eventual output passed to the accred code singlequbitlayers = [ QuantumCircuit(qregs, cregs), QuantumCircuit(qregs, cregs) ] twoqubitlayers = [QuantumCircuit(qregs, cregs)] measlayer = QuantumCircuit(qregs, cregs) # some flags for simplicity current2qs = [] # loop through circuit (best to use the dag object) dag_internal = circuit_to_dag(circ_internal) for dag_layer in dag_internal.layers(): circuit_layer = dag_to_circuit(dag_layer['graph']) for circelem, qsub, csub in circuit_layer: n = circelem.name if n == "barrier": # if a barrier separates any two qubit gates # start a new layer if current2qs != []: singlequbitlayers.append(QuantumCircuit(qregs, cregs)) twoqubitlayers.append(QuantumCircuit(qregs, cregs)) current2qs = [] singlequbitlayers[-2].append(circelem, qsub, csub) elif n in ('u1', 'u2', 'u3'): # single qubit gate q = qsub[0] if q in current2qs: singlequbitlayers[-1].append(circelem, qsub, csub) else: singlequbitlayers[-2].append(circelem, qsub, csub) elif n == "cx": # cx indices q_0 = qsub[0] q_1 = qsub[1] # check if new cnot satisfies overlap criteria if q_0 in current2qs or q_1 in current2qs: singlequbitlayers.append(QuantumCircuit(qregs, cregs)) twoqubitlayers.append(QuantumCircuit(qregs, cregs)) current2qs = [] if two_qubit_gate == 'cx': # append cx twoqubitlayers[-1].cx(q_0, q_1) elif two_qubit_gate == 'cz': # append and correct to cz with h gates twoqubitlayers[-1].cz(q_0, q_1) singlequbitlayers[-1].h(qsub[1]) singlequbitlayers[-2].h(qsub[1]) else: raise QiskitError( "Two qubit gate {0}".format(two_qubit_gate) + " is not implemented in qotp") # add to current current2qs.append(q_0) current2qs.append(q_1) elif n == "measure": measlayer.append(circelem, qsub, csub) else: raise QiskitError("Circuit element {0}".format(n) + " is not implemented in qotp") if current2qs == []: del singlequbitlayers[-1] del twoqubitlayers[-1] for ind, circlayer in enumerate(singlequbitlayers): singlequbitlayers[ind] = transpile(circlayer, basis_gates=['u1', 'u2', 'u3']) parsedlayers = { 'singlequbitlayers': singlequbitlayers, 'twoqubitlayers': twoqubitlayers, 'measlayer': measlayer, 'twoqubitgate': two_qubit_gate, 'qregs': qregs, 'cregs': cregs } return parsedlayers
n_qubits = int(math.log(dim, 2)) converter.n_qubits = n_qubits n_cnots = (n_layers // 2) * (n_qubits // 2) + ((n_qubits - 1) // 2) * ( (n_layers + 1) // 2) r = RewriteTket(Circuit(n_qubits), [], []) r.set_target_unitary(unitary) skips = [] circuit, distance = approximate_scipy(unitary, n_layers, r, []) for k in range(10): new_skip = random.choice([i for i in range(n_cnots) if i not in skips]) print("maybe I will skip", new_skip) new_circ, new_d = approximate_scipy(unitary, n_layers, r, skips + [new_skip]) if new_d <= distance: # or random.random() < (1 - new_d + distance) / (5 * k + 1): print("skipping", new_skip) circuit, distance, skips = new_circ, new_d, skips + [new_skip] else: print("not skipping", new_skip) print(circuit.n_gates, circuit.n_gates_of_type(OpType.CX)) if distance < 0.01: print("returning early", distance) return circuit, r.fidelity(circuit) print(distance) return circuit, r.fidelity(circuit) circuit, fid = approximate_scipy(unitary, n_layers, r) print("final fidelity", fid) print(circuit.n_gates, circuit.n_gates_of_type(OpType.CX)) print(dag_to_circuit(tk_to_dagcircuit(circuit)))
def run(self, dag): """Run the Unroller pass on `dag`. Args: dag (DAGCircuit): input dag Raises: QiskitError: if unable to unroll given the basis due to undefined decomposition rules (such as a bad basis) or excessive recursion. Returns: DAGCircuit: output unrolled dag """ if self.basis is None: return dag # Walk through the DAG and expand each non-basis node basic_insts = ["measure", "reset", "barrier", "snapshot", "delay"] for node in dag.op_nodes(): if getattr(node.op, "_directive", False): continue if node.name in basic_insts: # TODO: this is legacy behavior.Basis_insts should be removed that these # instructions should be part of the device-reported basis. Currently, no # backend reports "measure", for example. continue if node.name in self.basis: # If already a base, ignore. if isinstance(node.op, ControlledGate) and node.op._open_ctrl: pass else: continue if isinstance(node.op, ControlFlowOp): unrolled_blocks = [] for block in node.op.blocks: dag_block = circuit_to_dag(block) unrolled_dag_block = self.run(dag_block) unrolled_circ_block = dag_to_circuit(unrolled_dag_block) unrolled_blocks.append(unrolled_circ_block) node.op = node.op.replace_blocks(unrolled_blocks) continue try: phase = node.op.definition.global_phase rule = node.op.definition.data except (TypeError, AttributeError) as err: raise QiskitError( f"Error decomposing node of instruction '{node.name}': {err}. " f"Unable to define instruction '{node.name}' in the given basis." ) from err # Isometry gates definitions can have widths smaller than that of the # original gate, in which case substitute_node will raise. Fall back # to substitute_node_with_dag if an the width of the definition is # different that the width of the node. while rule and len(rule) == 1 and len(node.qargs) == len( rule[0].qubits) == 1: if rule[0].operation.name in self.basis: dag.global_phase += phase dag.substitute_node(node, rule[0].operation, inplace=True) break try: phase += rule[0].operation.definition.global_phase rule = rule[0].operation.definition.data except (TypeError, AttributeError) as err: raise QiskitError( f"Error decomposing node of instruction '{node.name}': {err}. " f"Unable to define instruction '{rule[0].operation.name}' in the basis." ) from err else: if not rule: if rule == []: # empty node dag.remove_op_node(node) dag.global_phase += phase continue # opaque node raise QiskitError( "Cannot unroll the circuit to the given basis, %s. " "No rule to expand instruction %s." % (str(self.basis), node.op.name)) decomposition = circuit_to_dag(node.op.definition) unrolled_dag = self.run( decomposition) # recursively unroll ops dag.substitute_node_with_dag(node, unrolled_dag) return dag
def ha_mapping( quantum_circuit: QuantumCircuit, initial_mapping: ty.Dict[Qubit, int], hardware: IBMQHardwareArchitecture, swap_cost_heuristic: ty.Callable[ [ IBMQHardwareArchitecture, # Hardware information QuantumLayer, # Current front layer ty.List[DAGNode], # Topologically sorted list of nodes int, # Index of the first non-processed gate. ty.Dict[Qubit, int], # The mapping before applying the tested SWAP/Bridge ty.Dict[Qubit, int], # The initial mapping ty.Dict[Qubit, int], # The trans mapping numpy.ndarray, # The distance matrix between each qubits TwoQubitGate, # The SWAP/Bridge we want to rank ], float, ] = sabre_heuristic, get_candidates: ty.Callable[ [QuantumLayer, IBMQHardwareArchitecture, ty.Dict[Qubit, int], ty.Dict[Qubit, int], ty.Dict[Qubit, int], ty.Set[str],], ty.List[TwoQubitGate], ] = get_all_swap_bridge_candidates, get_distance_matrix: ty.Callable[ [IBMQHardwareArchitecture], numpy.ndarray ] = get_distance_matrix_swap_number_and_error, ) -> ty.Tuple[QuantumCircuit, ty.Dict[Qubit, int]]: """Map the given quantum circuit to the hardware topology provided. :param quantum_circuit: the quantum circuit to map. :param initial_mapping: the initial mapping used to start the iterative mapping algorithm. :param hardware: hardware data such as connectivity, gate time, gate errors, ... :param swap_cost_heuristic: the heuristic cost function that will estimate the cost of a given SWAP/Bridge according to the current state of the circuit. :param get_candidates: a function that takes as input the current front layer and the hardware description and that returns a list of tuples representing the SWAP/Bridge that should be considered by the heuristic. :param get_distance_matrix: a function that takes as first (and only) parameter the hardware representation and that outputs a numpy array containing the cost of performing a SWAP between each pair of qubits. :return: The final circuit along with the mapping obtained at the end of the iterative procedure. """ _adapt_quantum_circuit_and_mapping_arity(quantum_circuit, initial_mapping, hardware) # Creating the internal data structures that will be used in this function. dag_circuit = circuit_to_dag(quantum_circuit) distance_matrix = get_distance_matrix(hardware) resulting_dag_quantum_circuit = _create_empty_dagcircuit_from_existing(dag_circuit) current_mapping = initial_mapping explored_mappings: ty.Set[str] = set() # Sorting all the quantum operations in topological order once for all. # May require significant memory on large circuits... topological_nodes: ty.List[DAGNode] = list(dag_circuit.topological_op_nodes()) current_node_index = 0 # Creating the initial front layer. front_layer = QuantumLayer() current_node_index = update_layer( front_layer, topological_nodes, current_node_index ) trans_mapping = initial_mapping.copy() # Start of the iterative algorithm while not front_layer.is_empty(): execute_gate_list = QuantumLayer() for op in front_layer.ops: if hardware.can_natively_execute_operation(op, current_mapping): execute_gate_list.add_operation(op) # Delaying the remove operation because we do not want to remove from # a container we are iterating on. # front_layer.remove_operation(op) if not execute_gate_list.is_empty(): front_layer.remove_operations_from_layer(execute_gate_list) execute_gate_list.apply_back_to_dag_circuit( resulting_dag_quantum_circuit, initial_mapping, trans_mapping ) # Empty the explored mappings because at least one gate has been executed. explored_mappings.clear() else: inverse_mapping = {val: key for key, val in initial_mapping.items()} # We cannot execute any gate, that means that we should insert at least # one SWAP/Bridge to make some gates executable. # First list all the SWAPs/Bridges that may help us make some gates # executable. swap_candidates = get_candidates( front_layer, hardware, initial_mapping, current_mapping, trans_mapping, explored_mappings ) # Then rank the SWAPs/Bridge and take the best one. best_swap_qubits = None best_cost = float("inf") for potential_swap in swap_candidates: cost = swap_cost_heuristic( hardware, front_layer, topological_nodes, current_node_index, current_mapping, initial_mapping, trans_mapping, distance_matrix, potential_swap, ) if cost < best_cost: best_cost = cost best_swap_qubits = potential_swap # We now have our best SWAP/Bridge, let's perform it! current_mapping = best_swap_qubits.update_mapping(current_mapping) if isinstance(best_swap_qubits, SwapTwoQubitGate): control, target = current_mapping[best_swap_qubits.left], current_mapping[best_swap_qubits.right] swap_control, swap_target = inverse_mapping[control], inverse_mapping[target] best_swap_qubits = SwapTwoQubitGate( swap_control, swap_target ) #print("swap gates is :", best_swap_qubits.left, best_swap_qubits.right) trans_mapping[best_swap_qubits.left], trans_mapping[best_swap_qubits.right] = ( trans_mapping[best_swap_qubits.right], trans_mapping[best_swap_qubits.left], ) else: #print("brige gate is :", best_swap_qubits.left, best_swap_qubits.middle, best_swap_qubits.right) pass explored_mappings.add(mapping_to_str(current_mapping)) best_swap_qubits.apply(resulting_dag_quantum_circuit, front_layer, initial_mapping, trans_mapping) # Anyway, update the current front_layer current_node_index = update_layer( front_layer, topological_nodes, current_node_index ) # We are done here, we just need to return the results # resulting_dag_quantum_circuit.draw(scale=1, filename="qcirc.dot") resulting_circuit = dag_to_circuit(resulting_dag_quantum_circuit) return resulting_circuit, current_mapping
def ha_mapping_paper_compliant( quantum_circuit: QuantumCircuit, initial_mapping: ty.Dict[Qubit, int], hardware: IBMQHardwareArchitecture, swap_cost_and_effect_heuristic: ty.Callable[ [ IBMQHardwareArchitecture, # Hardware information QuantumLayer, # Current front layer ty.List[DAGNode], # Topologically sorted list of nodes int, # Index of the first non-processed gate. ty.Dict[Qubit, int], # The mapping before applying the tested SWAP/Bridge ty.Dict[Qubit, int], # The initial mapping ty.Dict[Qubit, int], # The trans mapping numpy.ndarray, # The distance matrix between each qubits TwoQubitGate, # The SWAP/Bridge we want to rank ], ty.Tuple[ float, # Cost of the SWAP pair float, # Effect of the SWAP pair on the other gates ], ] = sabre_heuristic_with_effect, get_candidates: ty.Callable[ [QuantumLayer, IBMQHardwareArchitecture, ty.Dict[Qubit, int], ty.Set[str],], ty.List[SwapTwoQubitGate], ] = get_all_swap_candidates, get_distance_matrix: ty.Callable[ [IBMQHardwareArchitecture], numpy.ndarray ] = get_distance_matrix_swap_number_and_error, ) -> ty.Tuple[QuantumCircuit, ty.Dict[Qubit, int]]: """Map the given quantum circuit to the hardware topology provided. This implementation uses the exact same algorithm described in the associated scientific paper. Another implementation using a different method to choose between SWAP and Bridge is available as `:py:func:`~hamap.mapping.ha_mapping`. :param quantum_circuit: the quantum circuit to map. :param initial_mapping: the initial mapping used to start the iterative mapping algorithm. :param hardware: hardware data such as connectivity, gate time, gate errors, ... :param swap_cost_and_effect_heuristic: the heuristic cost function that will estimate the cost of a given SWAP according to the current state of the circuit and the effect of the SWAP on the following quantum gates. The two floats should be returned as a tuple (cost, effect). :param get_candidates: a function that takes as input the current front layer and the hardware description and that returns a list of tuples representing the SWAP that should be considered by the heuristic. :param get_distance_matrix: a function that takes as first (and only) parameter the hardware representation and that outputs a numpy array containing the cost of performing a SWAP between each pair of qubits. :return: The final circuit along with the mapping obtained at the end of the iterative procedure. """ _adapt_quantum_circuit_and_mapping_arity(quantum_circuit, initial_mapping, hardware) # Creating the internal data structures that will be used in this function. dag_circuit = circuit_to_dag(quantum_circuit) distance_matrix = get_distance_matrix(hardware) resulting_dag_quantum_circuit = _create_empty_dagcircuit_from_existing(dag_circuit) current_mapping = initial_mapping explored_mappings: ty.Set[str] = set() # Sorting all the quantum operations in topological order once for all. # May require significant memory on large circuits... topological_nodes: ty.List[DAGNode] = list(dag_circuit.topological_op_nodes()) current_node_index = 0 # Creating the initial front layer. front_layer = QuantumLayer() current_node_index = update_layer( front_layer, topological_nodes, current_node_index ) swap_distance_matrix = get_distance_matrix_swap_number(hardware) trans_mapping = initial_mapping.copy() # Start of the iterative algorithm while not front_layer.is_empty(): execute_gate_list = QuantumLayer() for op in front_layer.ops: if hardware.can_natively_execute_operation(op, current_mapping): execute_gate_list.add_operation(op) # Delaying the remove operation because we do not want to remove from # a container we are iterating on. # front_layer.remove_operation(op) if not execute_gate_list.is_empty(): front_layer.remove_operations_from_layer(execute_gate_list) execute_gate_list.apply_back_to_dag_circuit( resulting_dag_quantum_circuit, initial_mapping, trans_mapping ) # Empty the explored mappings because at least one gate has been executed. explored_mappings.clear() else: # We cannot execute any gate, that means that we should insert at least # one SWAP/Bridge to make some gates executable. # First list all the SWAPs/Bridges that may help us make some gates # executable. inverse_mapping = {val: key for key, val in initial_mapping.items()} swap_candidates = get_candidates( front_layer, hardware, current_mapping, explored_mappings ) # Then rank the SWAPs/Bridge and take the best one. best_swap_qubits = None best_cost = float("inf") best_effect = 0.0 for potential_swap in swap_candidates: cost, swap_effect = swap_cost_and_effect_heuristic( hardware, front_layer, topological_nodes, current_node_index, current_mapping, initial_mapping, trans_mapping, distance_matrix, potential_swap, ) if cost < best_cost: best_cost = cost best_effect = swap_effect best_swap_qubits = potential_swap # We now have our best SWAP, let's check if a Bridge is not better # if ( # best_effect < 0 # and swap_distance_matrix[best_swap_qubits.left][best_swap_qubits.right] # == 2 # ): # i, j = best_swap_qubits # common_neighbours = set(hardware.neighbors(i)) & set( # hardware.neighbors(j) # ) # if len(common_neighbours) < 1: # raise RuntimeError("Less than one common neighbour") # common_neighbour = list(common_neighbours)[0] # best_gate = BridgeTwoQubitGate(i, common_neighbour, j) if best_effect < 0: inverse_trans_mapping = {val: key for key, val in trans_mapping.items()} for op in front_layer.ops: if len(op.qargs) < 2: # We just pass 1 qubit gates because they do not participate in the # Bridge operation continue if len(op.qargs) != 2: logger.warning("A 3-qubit or more gate has been found in the circuit.") continue control, target = op.qargs control_index = initial_mapping[inverse_trans_mapping[initial_mapping[control]]] target_index = initial_mapping[inverse_trans_mapping[initial_mapping[target]]] for _, potential_middle_index in hardware.out_edges(control_index): for _, potential_target_index in hardware.out_edges(potential_middle_index): if potential_target_index == target_index: best_swap_qubits = BridgeTwoQubitGate( inverse_trans_mapping[initial_mapping[control]], inverse_mapping[potential_middle_index], inverse_trans_mapping[initial_mapping[target]], ) current_mapping = best_swap_qubits.update_mapping(current_mapping) if isinstance(best_swap_qubits, SwapTwoQubitGate): control, target = current_mapping[best_swap_qubits.left], current_mapping[best_swap_qubits.right] swap_control, swap_target = inverse_mapping[control], inverse_mapping[target] best_swap_qubits = SwapTwoQubitGate( swap_control, swap_target ) #print("swap gates is :", best_swap_qubits.left, best_swap_qubits.right) trans_mapping[best_swap_qubits.left], trans_mapping[best_swap_qubits.right] = ( trans_mapping[best_swap_qubits.right], trans_mapping[best_swap_qubits.left], ) else: #print("brige gate is :", best_swap_qubits.left, best_swap_qubits.middle, best_swap_qubits.right) pass explored_mappings.add(mapping_to_str(current_mapping)) best_swap_qubits.apply(resulting_dag_quantum_circuit, front_layer, initial_mapping, trans_mapping) # Anyway, update the current front_layer current_node_index = update_layer( front_layer, topological_nodes, current_node_index ) # We are done here, we just need to return the results # resulting_dag_quantum_circuit.draw(scale=1, filename="qcirc.dot") resulting_circuit = dag_to_circuit(resulting_dag_quantum_circuit) return resulting_circuit, current_mapping