def check_exact_decomposition(self, target_unitary, decomposer, tolerance=1.e-7): """Check exact decomposition for a particular target""" with self.subTest(unitary=target_unitary, decomposer=decomposer): decomp_circuit = decomposer(target_unitary) result = execute(decomp_circuit, UnitarySimulatorPy()).result() decomp_unitary = Operator(result.get_unitary()) result = execute(decomp_circuit, UnitarySimulatorPy()).result() decomp_unitary = result.get_unitary() target_unitary *= la.det(target_unitary)**(-0.25) decomp_unitary *= la.det(decomp_unitary)**(-0.25) maxdists = [np.max(np.abs(target_unitary + phase*decomp_unitary)) for phase in [1, 1j, -1, -1j]] maxdist = np.min(maxdists) self.assertTrue(np.abs(maxdist) < tolerance, "Worst distance {}".format(maxdist))
def test_cx_equivalence_2cx_random(self): """Check random circuits with 2 cx gates locally equivalent to some circuit with 2 cx. """ qr = QuantumRegister(2, name='q') qc = QuantumCircuit(qr) rnd = 2 * np.pi * np.random.random(size=3) qc.u3(rnd[0], rnd[1], rnd[2], qr[0]) rnd = 2 * np.pi * np.random.random(size=3) qc.u3(rnd[0], rnd[1], rnd[2], qr[1]) qc.cx(qr[1], qr[0]) rnd = 2 * np.pi * np.random.random(size=3) qc.u3(rnd[0], rnd[1], rnd[2], qr[0]) rnd = 2 * np.pi * np.random.random(size=3) qc.u3(rnd[0], rnd[1], rnd[2], qr[1]) qc.cx(qr[0], qr[1]) rnd = 2 * np.pi * np.random.random(size=3) qc.u3(rnd[0], rnd[1], rnd[2], qr[0]) rnd = 2 * np.pi * np.random.random(size=3) qc.u3(rnd[0], rnd[1], rnd[2], qr[1]) sim = UnitarySimulatorPy() U = execute(qc, sim).result().get_unitary() self.assertEqual(two_qubit_cnot_decompose.num_basis_gates(U), 2)
def test_2q_local_invariance_simple(self): """Check the local invariance parameters for known simple cases. """ sim = UnitarySimulatorPy() qr = QuantumRegister(2, name='q') qc = QuantumCircuit(qr) U = execute(qc, sim).result().get_unitary() vec = two_qubit_local_invariants(U) assert_allclose(vec, [1, 0, 3]) qr = QuantumRegister(2, name='q') qc = QuantumCircuit(qr) qc.cx(qr[1], qr[0]) U = execute(qc, sim).result().get_unitary() vec = two_qubit_local_invariants(U) assert_allclose(vec, [0, 0, 1]) qr = QuantumRegister(2, name='q') qc = QuantumCircuit(qr) qc.cx(qr[1], qr[0]) qc.cx(qr[0], qr[1]) U = execute(qc, sim).result().get_unitary() vec = two_qubit_local_invariants(U) assert_allclose(vec, [0, 0, -1]) qr = QuantumRegister(2, name='q') qc = QuantumCircuit(qr) qc.swap(qr[1], qr[0]) U = execute(qc, sim).result().get_unitary() vec = two_qubit_local_invariants(U) assert_allclose(vec, [-1, 0, -3])
def test_cx_equivalence_3cx_random(self, rnd, seed): """Check random circuits with 3 cx gates are outside the 0, 1, and 2 qubit regions. """ qr = QuantumRegister(2, name='q') qc = QuantumCircuit(qr) qc.u3(rnd[0], rnd[1], rnd[2], qr[0]) qc.u3(rnd[3], rnd[4], rnd[5], qr[1]) qc.cx(qr[1], qr[0]) qc.u3(rnd[6], rnd[7], rnd[8], qr[0]) qc.u3(rnd[9], rnd[10], rnd[11], qr[1]) qc.cx(qr[0], qr[1]) qc.u3(rnd[12], rnd[13], rnd[14], qr[0]) qc.u3(rnd[15], rnd[16], rnd[17], qr[1]) qc.cx(qr[1], qr[0]) qc.u3(rnd[18], rnd[19], rnd[20], qr[0]) qc.u3(rnd[21], rnd[22], rnd[23], qr[1]) sim = UnitarySimulatorPy() unitary = execute(qc, sim, seed_simulator=seed).result().get_unitary() self.assertEqual(two_qubit_cnot_decompose.num_basis_gates(unitary), 3)
def test_cx_equivalence_2cx(self, seed=2): """Check circuits with 2 cx gates locally equivalent to some circuit with 2 cx. """ state = np.random.default_rng(seed) rnd = 2 * np.pi * state.random(size=18) qr = QuantumRegister(2, name='q') qc = QuantumCircuit(qr) qc.u(rnd[0], rnd[1], rnd[2], qr[0]) qc.u(rnd[3], rnd[4], rnd[5], qr[1]) qc.cx(qr[1], qr[0]) qc.u(rnd[6], rnd[7], rnd[8], qr[0]) qc.u(rnd[9], rnd[10], rnd[11], qr[1]) qc.cx(qr[0], qr[1]) qc.u(rnd[12], rnd[13], rnd[14], qr[0]) qc.u(rnd[15], rnd[16], rnd[17], qr[1]) sim = UnitarySimulatorPy() unitary = execute(qc, sim).result().get_unitary() self.assertEqual(two_qubit_cnot_decompose.num_basis_gates(unitary), 2) self.assertTrue(Operator(two_qubit_cnot_decompose(unitary)).equiv(unitary))
def check_two_qubit_weyl_decomposition(self, target_unitary, tolerance=1.e-7): """Check TwoQubitWeylDecomposition() works for a given operator""" with self.subTest(unitary=target_unitary): decomp = TwoQubitWeylDecomposition(target_unitary) q = QuantumRegister(2) decomp_circuit = QuantumCircuit(q) decomp_circuit.append(UnitaryGate(decomp.K2r), [q[0]]) decomp_circuit.append(UnitaryGate(decomp.K2l), [q[1]]) decomp_circuit.append( UnitaryGate(Ud(decomp.a, decomp.b, decomp.c)), [q[0], q[1]]) decomp_circuit.append(UnitaryGate(decomp.K1r), [q[0]]) decomp_circuit.append(UnitaryGate(decomp.K1l), [q[1]]) result = execute(decomp_circuit, UnitarySimulatorPy()).result() decomp_unitary = result.get_unitary() target_unitary *= la.det(target_unitary)**(-0.25) decomp_unitary *= la.det(decomp_unitary)**(-0.25) maxdists = [ np.max(np.abs(target_unitary + phase * decomp_unitary)) for phase in [1, 1j, -1, -1j] ] maxdist = np.min(maxdists) self.assertTrue( np.abs(maxdist) < tolerance, "Worst distance {}".format(maxdist))
def test_cx_equivalence_3cx(self, seed=3): """Check circuits with 3 cx gates are outside the 0, 1, and 2 qubit regions. """ state = np.random.default_rng(seed) rnd = 2 * np.pi * state.random(size=24) qr = QuantumRegister(2, name='q') qc = QuantumCircuit(qr) qc.u(rnd[0], rnd[1], rnd[2], qr[0]) qc.u(rnd[3], rnd[4], rnd[5], qr[1]) qc.cx(qr[1], qr[0]) qc.u(rnd[6], rnd[7], rnd[8], qr[0]) qc.u(rnd[9], rnd[10], rnd[11], qr[1]) qc.cx(qr[0], qr[1]) qc.u(rnd[12], rnd[13], rnd[14], qr[0]) qc.u(rnd[15], rnd[16], rnd[17], qr[1]) qc.cx(qr[1], qr[0]) qc.u(rnd[18], rnd[19], rnd[20], qr[0]) qc.u(rnd[21], rnd[22], rnd[23], qr[1]) sim = UnitarySimulatorPy() unitary = execute(qc, sim).result().get_unitary() self.assertEqual(two_qubit_cnot_decompose.num_basis_gates(unitary), 3) self.assertTrue(Operator(two_qubit_cnot_decompose(unitary)).equiv(unitary))
def test_two_qubit_kak_from_paulis(self): """Verify decomposing Paulis with KAK """ pauli_xz = Pauli(label='XZ') unitary = Unitary(Operator(pauli_xz).data) decomp_circuit = two_qubit_kak(unitary) result = execute(decomp_circuit, UnitarySimulatorPy()).result() decomp_unitary = Unitary(result.get_unitary()) self.assertAlmostEqual(decomp_unitary, unitary)
def test_cx_equivalence_0cx_random(self, rnd, seed): """Check random circuits with 0 cx gates locally equivalent to identity.""" qr = QuantumRegister(2, name="q") qc = QuantumCircuit(qr) qc.u(rnd[0], rnd[1], rnd[2], qr[0]) qc.u(rnd[3], rnd[4], rnd[5], qr[1]) sim = UnitarySimulatorPy() unitary = execute(qc, sim, seed_simulator=seed).result().get_unitary() self.assertEqual(two_qubit_cnot_decompose.num_basis_gates(unitary), 0)
def test_two_qubit_kak_from_paulis(self): """Verify decomposing Paulis with KAK """ pauli_xz = Pauli(label='XZ') unitary = Operator(pauli_xz) decomp_circuit = two_qubit_kak(unitary) result = execute(decomp_circuit, UnitarySimulatorPy()).result() decomp_unitary = Operator(result.get_unitary()) equal_up_to_phase = matrix_equal(unitary.data, decomp_unitary.data, ignore_phase=True, atol=1e-7) self.assertTrue(equal_up_to_phase)
def test_two_qubit_kak(self): """Verify KAK decomposition for random Haar 4x4 unitaries. """ for _ in range(100): unitary = random_unitary(4) with self.subTest(unitary=unitary): decomp_circuit = two_qubit_kak(unitary) result = execute(decomp_circuit, UnitarySimulatorPy()).result() decomp_unitary = Unitary(result.get_unitary()) self.assertAlmostEqual(process_fidelity( unitary.representation, decomp_unitary.representation), 1.0, places=7)
def test_two_qubit_kak(self): """Verify KAK decomposition for random Haar 4x4 unitaries. """ for _ in range(100): unitary = random_unitary(4) with self.subTest(unitary=unitary): decomp_circuit = two_qubit_kak(unitary) result = execute(decomp_circuit, UnitarySimulatorPy()).result() decomp_unitary = Operator(result.get_unitary()) equal_up_to_phase = matrix_equal(unitary.data, decomp_unitary.data, ignore_phase=True, atol=1e-7) self.assertTrue(equal_up_to_phase)
def check_exact_decomposition(self, target_unitary, decomposer, tolerance=1.e-7): """Check exact decomposition for a particular target""" decomp_circuit = decomposer(target_unitary) result = execute(decomp_circuit, UnitarySimulatorPy(), optimization_level=0).result() decomp_unitary = result.get_unitary() maxdist = np.max(np.abs(target_unitary - decomp_unitary)) self.assertTrue( np.abs(maxdist) < tolerance, "Unitary {}: Worst distance {}".format(target_unitary, maxdist))
def check_one_qubit_euler_angles(self, operator, tolerance=1e-14): """Check euler_angles_1q works for the given unitary """ with self.subTest(operator=operator): target_unitary = operator.data angles = euler_angles_1q(target_unitary) decomp_circuit = QuantumCircuit(1) decomp_circuit.u3(*angles, 0) result = execute(decomp_circuit, UnitarySimulatorPy()).result() decomp_unitary = result.get_unitary() target_unitary *= la.det(target_unitary)**(-0.5) decomp_unitary *= la.det(decomp_unitary)**(-0.5) maxdist = np.max(np.abs(target_unitary - decomp_unitary)) if maxdist > 0.1: maxdist = np.max(np.abs(target_unitary + decomp_unitary)) self.assertTrue(np.abs(maxdist) < tolerance, "Worst distance {}".format(maxdist))
def test_one_qubit_euler_angles(self): """Verify euler_angles_1q produces correct Euler angles for a single-qubit unitary. """ for _ in range(100): unitary = random_unitary(2) with self.subTest(unitary=unitary): angles = euler_angles_1q(unitary.data) decomp_circuit = QuantumCircuit(1) decomp_circuit.u3(*angles, 0) result = execute(decomp_circuit, UnitarySimulatorPy()).result() decomp_unitary = Operator(result.get_unitary()) equal_up_to_phase = matrix_equal(unitary.data, decomp_unitary.data, ignore_phase=True, atol=1e-7) self.assertTrue(equal_up_to_phase)
def test_consolidate_small_block(self): """test a small block of gates can be turned into a unitary on same wires""" qr = QuantumRegister(2, "qr") qc = QuantumCircuit(qr) qc.u1(0.5, qr[0]) qc.u2(0.2, 0.6, qr[1]) qc.cx(qr[0], qr[1]) dag = circuit_to_dag(qc) pass_ = ConsolidateBlocks(force_consolidate=True) pass_.property_set['block_list'] = [list(dag.topological_op_nodes())] new_dag = pass_.run(dag) sim = UnitarySimulatorPy() result = execute(qc, sim).result() unitary = UnitaryGate(result.get_unitary()) self.assertEqual(len(new_dag.op_nodes()), 1) fidelity = process_fidelity(new_dag.op_nodes()[0].op.to_matrix(), unitary.to_matrix()) self.assertAlmostEqual(fidelity, 1.0, places=7)
def test_3q_blocks(self): """blocks of more than 2 qubits work.""" qr = QuantumRegister(3, "qr") qc = QuantumCircuit(qr) qc.u1(0.5, qr[0]) qc.u2(0.2, 0.6, qr[1]) qc.cx(qr[2], qr[1]) qc.cx(qr[0], qr[1]) dag = circuit_to_dag(qc) pass_ = ConsolidateBlocks(force_consolidate=True) pass_.property_set['block_list'] = [list(dag.topological_op_nodes())] new_dag = pass_.run(dag) sim = UnitarySimulatorPy() result = execute(qc, sim).result() unitary = UnitaryGate(result.get_unitary()) self.assertEqual(len(new_dag.op_nodes()), 1) fidelity = process_fidelity(new_dag.op_nodes()[0].op.to_matrix(), unitary.to_matrix()) self.assertAlmostEqual(fidelity, 1.0, places=7)
def test_block_spanning_two_regs(self): """blocks spanning wires on different quantum registers work.""" qr0 = QuantumRegister(1, "qr0") qr1 = QuantumRegister(1, "qr1") qc = QuantumCircuit(qr0, qr1) qc.u1(0.5, qr0[0]) qc.u2(0.2, 0.6, qr1[0]) qc.cx(qr0[0], qr1[0]) dag = circuit_to_dag(qc) pass_ = ConsolidateBlocks(force_consolidate=True) pass_.property_set['block_list'] = [list(dag.topological_op_nodes())] new_dag = pass_.run(dag) sim = UnitarySimulatorPy() result = execute(qc, sim).result() unitary = UnitaryGate(result.get_unitary()) self.assertEqual(len(new_dag.op_nodes()), 1) fidelity = process_fidelity(new_dag.op_nodes()[0].op.to_matrix(), unitary.to_matrix()) self.assertAlmostEqual(fidelity, 1.0, places=7)
def test_cx_equivalence_2cx_random(self, rnd, seed): """Check random circuits with 2 cx gates locally equivalent to some circuit with 2 cx. """ qr = QuantumRegister(2, name='q') qc = QuantumCircuit(qr) qc.u3(rnd[0], rnd[1], rnd[2], qr[0]) qc.u3(rnd[3], rnd[4], rnd[5], qr[1]) qc.cx(qr[1], qr[0]) qc.u3(rnd[6], rnd[7], rnd[8], qr[0]) qc.u3(rnd[9], rnd[10], rnd[11], qr[1]) qc.cx(qr[0], qr[1]) qc.u3(rnd[12], rnd[13], rnd[14], qr[0]) qc.u3(rnd[15], rnd[16], rnd[17], qr[1]) sim = UnitarySimulatorPy() unitary = execute(qc, sim, seed_simulator=seed).result().get_unitary() self.assertEqual(two_qubit_cnot_decompose.num_basis_gates(unitary), 2)
def test_block_spanning_two_regs_different_index(self): """blocks spanning wires on different quantum registers work when the wires could have conflicting indices. This was raised in #2806 when a CX was applied across multiple registers and their indices collided, raising an error.""" qr0 = QuantumRegister(1, "qr0") qr1 = QuantumRegister(2, "qr1") qc = QuantumCircuit(qr0, qr1) qc.cx(qr0[0], qr1[1]) dag = circuit_to_dag(qc) pass_ = ConsolidateBlocks(force_consolidate=True) pass_.property_set['block_list'] = [list(dag.topological_op_nodes())] new_dag = pass_.run(dag) sim = UnitarySimulatorPy() original_result = execute(qc, sim).result() original_unitary = UnitaryGate(original_result.get_unitary()) from qiskit.converters import dag_to_circuit new_result = execute(dag_to_circuit(new_dag), sim).result() new_unitary = UnitaryGate(new_result.get_unitary()) self.assertEqual(original_unitary, new_unitary)
def run(self, dag): """iterate over each block and replace it with an equivalent Unitary on the same wires. """ new_dag = DAGCircuit() for qreg in dag.qregs.values(): new_dag.add_qreg(qreg) for creg in dag.cregs.values(): new_dag.add_creg(creg) sim = UnitarySimulatorPy() # compute ordered indices for the global circuit wires global_index_map = {} for wire in dag.wires: if not isinstance(wire[0], QuantumRegister): continue global_qregs = list(dag.qregs.values()) global_index_map[wire] = global_qregs.index(wire[0]) + wire[1] blocks = self.property_set['block_list'] nodes_seen = set() for node in dag.topological_op_nodes(): # skip already-visited nodes or input/output nodes if node in nodes_seen or node.type == 'in' or node.type == 'out': continue # check if the node belongs to the next block if blocks and node in blocks[0]: block = blocks[0] # find the qubits involved in this block block_qargs = set() for nd in block: block_qargs |= set(nd.qargs) # convert block to a sub-circuit, then simulate unitary and add block_width = len(block_qargs) q = QuantumRegister(block_width) subcirc = QuantumCircuit(q) block_index_map = self._block_qargs_to_indices( block_qargs, global_index_map) for nd in block: nodes_seen.add(nd) subcirc.append(nd.op, [q[block_index_map[i]] for i in nd.qargs]) qobj = assemble_circuits(subcirc) unitary_matrix = sim.run(qobj).result().get_unitary() unitary = Unitary(unitary_matrix) new_dag.apply_operation_back( unitary, sorted(block_qargs, key=lambda x: block_index_map[x])) del blocks[0] else: # the node could belong to some future block, but in that case # we simply skip it. It is guaranteed that we will revisit that # future block, via its other nodes for block in blocks[1:]: if node in block: break # freestanding nodes can just be added else: nodes_seen.add(node) new_dag.apply_operation_back(node.op, node.qargs, node.cargs) return new_dag