def _multiplex(self, target_gate, list_of_angles, last_cnot=True): """ Return a recursive implementation of a multiplexor circuit, where each instruction itself has a decomposition based on smaller multiplexors. The LSB is the multiplexor "data" and the other bits are multiplexor "select". Args: target_gate (Gate): Ry or Rz gate to apply to target qubit, multiplexed over all other "select" qubits list_of_angles (list[float]): list of rotation angles to apply Ry and Rz last_cnot (bool): add the last cnot if last_cnot = True Returns: DAGCircuit: the circuit implementing the multiplexor's action """ list_len = len(list_of_angles) local_num_qubits = int(math.log2(list_len)) + 1 q = QuantumRegister(local_num_qubits) circuit = QuantumCircuit(q, name="multiplex" + local_num_qubits.__str__()) lsb = q[0] msb = q[local_num_qubits - 1] # case of no multiplexing: base case for recursion if local_num_qubits == 1: circuit.append(target_gate(list_of_angles[0]), [q[0]]) return circuit # calc angle weights, assuming recursion (that is the lower-level # requested angles have been correctly implemented by recursion angle_weight = np.kron([[0.5, 0.5], [0.5, -0.5]], np.identity(2 ** (local_num_qubits - 2))) # calc the combo angles list_of_angles = angle_weight.dot(np.array(list_of_angles)).tolist() # recursive step on half the angles fulfilling the above assumption multiplex_1 = self._multiplex(target_gate, list_of_angles[0:(list_len // 2)], False) circuit.append(multiplex_1.to_instruction(), q[0:-1]) # attach CNOT as follows, thereby flipping the LSB qubit circuit.append(CXGate(), [msb, lsb]) # implement extra efficiency from the paper of cancelling adjacent # CNOTs (by leaving out last CNOT and reversing (NOT inverting) the # second lower-level multiplex) multiplex_2 = self._multiplex(target_gate, list_of_angles[(list_len // 2):], False) if list_len > 1: circuit.append(multiplex_2.to_instruction().reverse_ops(), q[0:-1]) else: circuit.append(multiplex_2.to_instruction(), q[0:-1]) # attach a final CNOT if last_cnot: circuit.append(CXGate(), [msb, lsb]) return circuit
def test_topological_nodes(self): """The topological_nodes() method""" self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(HGate(), [self.qubit0], []) self.dag.apply_operation_back(CXGate(), [self.qubit2, self.qubit1], []) self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit2], []) self.dag.apply_operation_back(HGate(), [self.qubit2], []) named_nodes = self.dag.topological_nodes() expected = [('qr[0]', []), ('qr[1]', []), ('cx', [self.qubit0, self.qubit1]), ('h', [self.qubit0]), ('qr[2]', []), ('cx', [self.qubit2, self.qubit1]), ('cx', [self.qubit0, self.qubit2]), ('h', [self.qubit2]), ('qr[0]', []), ('qr[1]', []), ('qr[2]', []), ('cr[0]', []), ('cr[0]', []), ('cr[1]', []), ('cr[1]', [])] self.assertEqual(expected, [(i.name, i.qargs) for i in named_nodes])
def test_dag_collect_runs(self): """Test the collect_runs method with 3 different gates.""" self.dag.apply_operation_back(U1Gate(3.14), [self.qubit0]) self.dag.apply_operation_back(U1Gate(3.14), [self.qubit0]) self.dag.apply_operation_back(U1Gate(3.14), [self.qubit0]) self.dag.apply_operation_back(CXGate(), [self.qubit2, self.qubit1]) self.dag.apply_operation_back(CXGate(), [self.qubit1, self.qubit2]) self.dag.apply_operation_back(HGate(), [self.qubit2]) collected_runs = self.dag.collect_runs(['u1', 'cx', 'h']) self.assertEqual(len(collected_runs), 3) for run in collected_runs: if run[0].name == 'cx': self.assertEqual(len(run), 2) self.assertEqual(['cx'] * 2, [x.name for x in run]) self.assertEqual( [[self.qubit2, self.qubit1], [self.qubit1, self.qubit2]], [x.qargs for x in run]) elif run[0].name == 'h': self.assertEqual(len(run), 1) self.assertEqual(['h'], [x.name for x in run]) self.assertEqual([[self.qubit2]], [x.qargs for x in run]) elif run[0].name == 'u1': self.assertEqual(len(run), 3) self.assertEqual(['u1'] * 3, [x.name for x in run]) self.assertEqual([[self.qubit0], [self.qubit0], [self.qubit0]], [x.qargs for x in run]) else: self.fail('Unknown run encountered')
def test_remove_op_node_longer(self): """Test remove_op_node method in a "longer" dag""" self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1]) self.dag.apply_operation_back(HGate(), [self.qubit0]) self.dag.apply_operation_back(CXGate(), [self.qubit2, self.qubit1]) self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit2]) self.dag.apply_operation_back(HGate(), [self.qubit2]) op_nodes = list(self.dag.topological_op_nodes()) self.dag.remove_op_node(op_nodes[0]) expected = [('h', [self.qubit0]), ('cx', [self.qubit2, self.qubit1]), ('cx', [self.qubit0, self.qubit2]), ('h', [self.qubit2])] self.assertEqual(expected, [(i.name, i.qargs) for i in self.dag.topological_op_nodes()])
def test_quantum_successors(self): """The method dag.quantum_successors() returns successors connected by quantum edges""" # q_0: |0>─────■───|0>─ # ┌─┐┌─┴─┐ # q_1: |0>┤M├┤ X ├───── # └╥┘└───┘ # c_0: 0 ═╬═══════════ # ║ # c_1: 0 ═╩═══════════ self.dag.apply_operation_back(Measure(), [self.qubit1, self.clbit1], []) self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(Reset(), [self.qubit0], []) successor_measure = self.dag.quantum_successors( self.dag.named_nodes('measure').pop()) cnot_node = next(successor_measure) with self.assertRaises(StopIteration): next(successor_measure) self.assertIsInstance(cnot_node.op, CXGate) successor_cnot = self.dag.quantum_successors(cnot_node) # Ordering between Reset and out[q1] is indeterminant. successor1 = next(successor_cnot) successor2 = next(successor_cnot) with self.assertRaises(StopIteration): next(successor_cnot) self.assertTrue( (successor1.type == 'out' and isinstance(successor2.op, Reset)) or (successor2.type == 'out' and isinstance(successor1.op, Reset)))
def test_substituting_node_preserves_args_condition(self, inplace): """Verify args and condition are preserved by a substitution.""" dag = DAGCircuit() qr = QuantumRegister(2) cr = ClassicalRegister(1) dag.add_qreg(qr) dag.add_creg(cr) dag.apply_operation_back(HGate(), [qr[1]]) cx_gate = CXGate() cx_gate.condition = (cr, 1) node_to_be_replaced = dag.apply_operation_back(cx_gate, [qr[1], qr[0]]) dag.apply_operation_back(HGate(), [qr[1]]) replacement_node = dag.substitute_node(node_to_be_replaced, CZGate(), inplace=inplace) raise_if_dagcircuit_invalid(dag) self.assertEqual(replacement_node.name, 'cz') self.assertEqual(replacement_node.qargs, [qr[1], qr[0]]) self.assertEqual(replacement_node.cargs, []) self.assertEqual(replacement_node.condition, (cr, 1)) self.assertEqual(replacement_node is node_to_be_replaced, inplace)
def test_layers_basic(self): """The layers() method returns a list of layers, each of them with a list of nodes.""" qreg = QuantumRegister(2, 'qr') creg = ClassicalRegister(2, 'cr') qubit0 = qreg[0] qubit1 = qreg[1] clbit0 = creg[0] clbit1 = creg[1] x_gate = XGate() x_gate.condition = (creg, 3) dag = DAGCircuit() dag.add_qreg(qreg) dag.add_creg(creg) dag.apply_operation_back(HGate(), [qubit0], []) dag.apply_operation_back(CXGate(), [qubit0, qubit1], []) dag.apply_operation_back(Measure(), [qubit1, clbit1], []) dag.apply_operation_back(x_gate, [qubit1], []) dag.apply_operation_back(Measure(), [qubit0, clbit0], []) dag.apply_operation_back(Measure(), [qubit1, clbit1], []) layers = list(dag.layers()) self.assertEqual(5, len(layers)) name_layers = [[ node.op.name for node in layer["graph"].nodes() if node.type == "op" ] for layer in layers] self.assertEqual( [['h'], ['cx'], ['measure'], ['x'], ['measure', 'measure']], name_layers)
def test_instructions_equal(self): """Test equality of two instructions.""" hop1 = Instruction('h', 1, 0, []) hop2 = Instruction('s', 1, 0, []) hop3 = Instruction('h', 1, 0, []) self.assertFalse(hop1 == hop2) self.assertTrue(hop1 == hop3) uop1 = Instruction('u', 1, 0, [0.4, 0.5, 0.5]) uop2 = Instruction('u', 1, 0, [0.4, 0.6, 0.5]) uop3 = Instruction('v', 1, 0, [0.4, 0.5, 0.5]) uop4 = Instruction('u', 1, 0, [0.4, 0.5, 0.5]) self.assertFalse(uop1 == uop2) self.assertTrue(uop1 == uop4) self.assertFalse(uop1 == uop3) self.assertTrue(HGate() == HGate()) self.assertFalse(HGate() == CXGate()) self.assertFalse(hop1 == HGate()) eop1 = Instruction('kraus', 1, 0, [np.array([[1, 0], [0, 1]])]) eop2 = Instruction('kraus', 1, 0, [np.array([[0, 1], [1, 0]])]) eop3 = Instruction('kraus', 1, 0, [np.array([[1, 0], [0, 1]])]) eop4 = Instruction('kraus', 1, 0, [np.eye(4)]) self.assertTrue(eop1 == eop3) self.assertFalse(eop1 == eop2) self.assertFalse(eop1 == eop4)
def test_front_layer(self): """The method dag.front_layer() returns first layer""" self.dag.apply_operation_back(HGate(), [self.qubit0], []) self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(Reset(), [self.qubit0], []) op_nodes = self.dag.front_layer() self.assertEqual(len(op_nodes), 1) self.assertIsInstance(op_nodes[0].op, HGate)
def test_substituting_node_with_wrong_width_node_raises(self): """Verify replacing a node with one of a different shape raises.""" dag = DAGCircuit() qr = QuantumRegister(2) dag.add_qreg(qr) node_to_be_replaced = dag.apply_operation_back(CXGate(), [qr[0], qr[1]]) with self.assertRaises(DAGCircuitError) as _: dag.substitute_node(node_to_be_replaced, Measure())
def test_get_named_nodes(self): """The get_named_nodes(AName) method returns all the nodes with name AName""" self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(HGate(), [self.qubit0], []) self.dag.apply_operation_back(CXGate(), [self.qubit2, self.qubit1], []) self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit2], []) self.dag.apply_operation_back(HGate(), [self.qubit2], []) # The ordering is not assured, so we only compare the output (unordered) sets. # We use tuples because lists aren't hashable. named_nodes = self.dag.named_nodes('cx') node_qargs = {tuple(node.qargs) for node in named_nodes} expected_qargs = {(self.qubit0, self.qubit1), (self.qubit2, self.qubit1), (self.qubit0, self.qubit2)} self.assertEqual(expected_qargs, node_qargs)
def _process_cnot(self, node): """Process a CNOT gate node.""" id0 = self._process_bit_id(node.children[0]) id1 = self._process_bit_id(node.children[1]) if not (len(id0) == len(id1) or len(id0) == 1 or len(id1) == 1): raise QiskitError("internal error: qreg size mismatch", "line=%s" % node.line, "file=%s" % node.file) maxidx = max([len(id0), len(id1)]) for idx in range(maxidx): if len(id0) > 1 and len(id1) > 1: self.dag.apply_operation_back(CXGate(), [id0[idx], id1[idx]], [], self.condition) elif len(id0) > 1: self.dag.apply_operation_back(CXGate(), [id0[idx], id1[0]], [], self.condition) else: self.dag.apply_operation_back(CXGate(), [id0[0], id1[idx]], [], self.condition)
def test_apply_operation_back(self): """The apply_operation_back() method.""" self.dag.apply_operation_back(HGate(), [self.qubit0], [], condition=None) self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], [], condition=None) self.dag.apply_operation_back(Measure(), [self.qubit1, self.clbit1], [], condition=None) self.dag.apply_operation_back(XGate(), [self.qubit1], [], condition=self.condition) self.dag.apply_operation_back(Measure(), [self.qubit0, self.clbit0], [], condition=None) self.dag.apply_operation_back(Measure(), [self.qubit1, self.clbit1], [], condition=None) self.assertEqual(len(list(self.dag.nodes())), 16) self.assertEqual(len(list(self.dag.edges())), 17)
def _define(self): """ gate xx_minus_yy(theta, beta) a, b { rz(-beta) b; rz(-pi/2) a; sx a; rz(pi/2) a; s b; cx a, b; ry(theta/2) a; ry(-theta/2) b; cx a, b; sdg b; rz(-pi/2) a; sxdg a; rz(pi/2) a; rz(beta) b; } """ theta, beta = self.params register = QuantumRegister(2, "q") circuit = QuantumCircuit(register, name=self.name) a, b = register rules = [ (RZGate(-beta), [b], []), (RZGate(-pi / 2), [a], []), (SXGate(), [a], []), (RZGate(pi / 2), [a], []), (SGate(), [b], []), (CXGate(), [a, b], []), (RYGate(theta / 2), [a], []), (RYGate(-theta / 2), [b], []), (CXGate(), [a, b], []), (SdgGate(), [b], []), (RZGate(-pi / 2), [a], []), (SXdgGate(), [a], []), (RZGate(pi / 2), [a], []), (RZGate(beta), [b], []), ] for instr, qargs, cargs in rules: circuit._append(instr, qargs, cargs) self.definition = circuit
def test_get_op_nodes_all(self): """The method dag.op_nodes() returns all op nodes""" self.dag.apply_operation_back(HGate(), [self.qubit0], []) self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(Reset(), [self.qubit0], []) op_nodes = self.dag.op_nodes() self.assertEqual(len(op_nodes), 3) for node in op_nodes: self.assertIsInstance(node.op, Instruction)
def test_dag_nodes_on_wire_multiple_successors(self): """ Test that if a DAGNode has multiple successors in the DAG along one wire, they are all retrieved in order. This could be the case for a circuit such as q0_0: |0>──■─────────■── ┌─┴─┐┌───┐┌─┴─┐ q0_1: |0>┤ X ├┤ H ├┤ X ├ └───┘└───┘└───┘ Both the 2nd CX gate and the H gate follow the first CX gate in the DAG, so they both must be returned but in the correct order. """ self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(HGate(), [self.qubit1], []) self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) nodes = self.dag.nodes_on_wire(self.dag.qubits[1], only_ops=True) node_names = [nd.name for nd in nodes] self.assertEqual(node_names, ['cx', 'h', 'cx'])
def test_edges(self): """Test that DAGCircuit.edges() behaves as expected with ops.""" self.dag.apply_operation_back(HGate(), [self.qubit0], [], condition=None) self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], [], condition=None) self.dag.apply_operation_back(Measure(), [self.qubit1, self.clbit1], [], condition=None) self.dag.apply_operation_back(XGate(), [self.qubit1], [], condition=self.condition) self.dag.apply_operation_back(Measure(), [self.qubit0, self.clbit0], [], condition=None) self.dag.apply_operation_back(Measure(), [self.qubit1, self.clbit1], [], condition=None) out_edges = self.dag.edges(self.dag.output_map.values()) self.assertEqual(list(out_edges), []) in_edges = self.dag.edges(self.dag.input_map.values()) # number of edges for input nodes should be the same as number of wires self.assertEqual(len(list(in_edges)), 5)
def test_two_q_gates(self): """The method dag.two_qubit_ops() returns all 2Q gate operation nodes""" self.dag.apply_operation_back(HGate(), [self.qubit0], []) self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(Barrier(2), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(Reset(), [self.qubit0], []) op_nodes = self.dag.two_qubit_ops() self.assertEqual(len(op_nodes), 1) op_node = op_nodes.pop() self.assertIsInstance(op_node.op, Gate) self.assertEqual(len(op_node.qargs), 2)
def test_get_gates_nodes(self): """The method dag.gate_nodes() returns all gate nodes""" self.dag.apply_operation_back(HGate(), [self.qubit0], []) self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(Reset(), [self.qubit0], []) op_nodes = self.dag.gate_nodes() self.assertEqual(len(op_nodes), 2) op_node_1 = op_nodes.pop() op_node_2 = op_nodes.pop() self.assertIsInstance(op_node_1.op, Gate) self.assertIsInstance(op_node_2.op, Gate)
def apply(self, dag_circuit: DAGCircuit, front_layer: QuantumLayer, initial_mapping: ty.Dict[Qubit, int], trans_mapping: ty.Dict[Qubit, int]): dag_circuit.apply_operation_back(CXGate(), [self.left, self.middle]) dag_circuit.apply_operation_back(CXGate(), [self.middle, self.right]) dag_circuit.apply_operation_back(CXGate(), [self.left, self.middle]) dag_circuit.apply_operation_back(CXGate(), [self.middle, self.right]) # dag_circuit.apply_operation_back( # _BridgeGate(), [self.left, self.middle, self.right] # ) # Do not forget to remove the CNOT gate from self.left to self.right from the # front layer. op_to_remove: ty.Optional[DAGNode] = None for op in front_layer.ops: q1, q2 = initial_mapping[op.qargs[0]], initial_mapping[op.qargs[1]] if (len(op.qargs) == 2 and q1 == trans_mapping[self.left] and q2 == trans_mapping[self.right]): op_to_remove = op if op_to_remove is None: logger.warning( "Could not find a corresponding CNOT gate to remove with " "Bridge usage. Resulting circuit will likely be wrong.") else: front_layer.remove_operation(op_to_remove)
def test_dag_nodes_on_wire(self): """Test that listing the gates on a qubit/classical bit gets the correct gates""" self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(HGate(), [self.qubit0], []) qbit = self.dag.qubits[0] self.assertEqual([0, 10, 11, 1], [i._node_id for i in self.dag.nodes_on_wire(qbit)]) self.assertEqual([10, 11], [i._node_id for i in self.dag.nodes_on_wire(qbit, only_ops=True)]) cbit = self.dag.clbits[0] self.assertEqual([6, 7], [i._node_id for i in self.dag.nodes_on_wire(cbit)]) self.assertEqual([], [i._node_id for i in self.dag.nodes_on_wire(cbit, only_ops=True)]) with self.assertRaises(DAGCircuitError): next(self.dag.nodes_on_wire((qbit.register, 7)))
def test_substitute_circuit_one_middle(self): """The method substitute_node_with_dag() replaces a in-the-middle node with a DAG.""" cx_node = self.dag.op_nodes(op=CXGate).pop() flipped_cx_circuit = DAGCircuit() v = QuantumRegister(2, "v") flipped_cx_circuit.add_qreg(v) flipped_cx_circuit.apply_operation_back(HGate(), [v[0]], []) flipped_cx_circuit.apply_operation_back(HGate(), [v[1]], []) flipped_cx_circuit.apply_operation_back(CXGate(), [v[1], v[0]], []) flipped_cx_circuit.apply_operation_back(HGate(), [v[0]], []) flipped_cx_circuit.apply_operation_back(HGate(), [v[1]], []) self.dag.substitute_node_with_dag(cx_node, flipped_cx_circuit, wires=[v[0], v[1]]) self.assertEqual(self.dag.count_ops()['h'], 5)
def setUp(self): self.dag = DAGCircuit() qreg = QuantumRegister(3, 'qr') creg = ClassicalRegister(2, 'cr') self.dag.add_qreg(qreg) self.dag.add_creg(creg) self.qubit0 = qreg[0] self.qubit1 = qreg[1] self.qubit2 = qreg[2] self.clbit0 = creg[0] self.clbit1 = creg[1] self.condition = (creg, 3) self.dag.apply_operation_back(HGate(), [self.qubit0], []) self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(XGate(), [self.qubit1], [])
inplace=True) return_circuit.compose(decomposition_euler[2 * best_nbasis + 1], [q[1]], inplace=True) return return_circuit def num_basis_gates(self, unitary): """ Computes the number of basis gates needed in a decomposition of input unitary """ if hasattr(unitary, 'to_operator'): unitary = unitary.to_operator().data if hasattr(unitary, 'to_matrix'): unitary = unitary.to_matrix() unitary = np.asarray(unitary, dtype=complex) a, b, c = weyl_coordinates(unitary)[:] traces = [ 4 * (np.cos(a) * np.cos(b) * np.cos(c) + 1j * np.sin(a) * np.sin(b) * np.sin(c)), 4 * (np.cos(np.pi / 4 - a) * np.cos(self.basis.b - b) * np.cos(c) + 1j * np.sin(np.pi / 4 - a) * np.sin(self.basis.b - b) * np.sin(c)), 4 * np.cos(c), 4 ] return np.argmax([ trace_to_fid(traces[i]) * self.basis_fidelity**i for i in range(4) ]) two_qubit_cnot_decompose = TwoQubitBasisDecomposer(CXGate())