mirror of https://github.com/Qiskit/qiskit.git
223 lines
8.1 KiB
Python
223 lines
8.1 KiB
Python
# This code is part of Qiskit.
|
|
#
|
|
# (C) Copyright IBM 2023.
|
|
#
|
|
# This code is licensed under the Apache License, Version 2.0. You may
|
|
# obtain a copy of this license in the LICENSE.txt file in the root directory
|
|
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
|
#
|
|
# Any modifications or derivative works of this code must retain this
|
|
# copyright notice, and modified files need to carry a notice indicating
|
|
# that they have been altered from the originals.
|
|
|
|
"""Test Qiskit's AnnotatedOperation class."""
|
|
|
|
import ddt
|
|
import numpy as np
|
|
|
|
from qiskit.circuit import QuantumCircuit, pauli_twirl_2q_gates, Gate, Parameter
|
|
from qiskit.circuit.library import (
|
|
CXGate,
|
|
ECRGate,
|
|
CZGate,
|
|
iSwapGate,
|
|
SwapGate,
|
|
PermutationGate,
|
|
XGate,
|
|
CCXGate,
|
|
RZXGate,
|
|
)
|
|
from qiskit.circuit.random import random_circuit
|
|
from qiskit.exceptions import QiskitError
|
|
from qiskit.quantum_info import Operator
|
|
from qiskit.transpiler.target import Target
|
|
from test import QiskitTestCase # pylint: disable=wrong-import-order
|
|
|
|
|
|
@ddt.ddt
|
|
class TestTwirling(QiskitTestCase):
|
|
"""Testing qiskit.circuit.twirl_circuit"""
|
|
|
|
@ddt.data(CXGate, ECRGate, CZGate, iSwapGate)
|
|
def test_twirl_circuit_equiv(self, gate):
|
|
"""Test the twirled circuit is equivalent."""
|
|
qc = QuantumCircuit(2)
|
|
qc.append(gate(), (0, 1))
|
|
for i in range(100):
|
|
with self.subTest(i):
|
|
res = pauli_twirl_2q_gates(qc, gate, i)
|
|
np.testing.assert_allclose(
|
|
Operator(qc), Operator(res), err_msg=f"gate: {gate} not equiv to\n{res}"
|
|
)
|
|
self.assertNotEqual(res, qc)
|
|
# Assert we have more than just a 2q gate in the circuit
|
|
self.assertGreater(len(res.count_ops()), 1)
|
|
|
|
def test_twirl_circuit_None(self):
|
|
"""Test the default twirl all gates."""
|
|
qc = QuantumCircuit(2)
|
|
qc.cx(0, 1)
|
|
qc.cz(0, 1)
|
|
qc.ecr(0, 1)
|
|
qc.iswap(0, 1)
|
|
res = pauli_twirl_2q_gates(qc, seed=12345)
|
|
np.testing.assert_allclose(
|
|
Operator(qc), Operator(res), err_msg=f"{qc}\nnot equiv to\n{res}"
|
|
)
|
|
self.assertNotEqual(res, qc)
|
|
self.assertEqual(sum(res.count_ops().values()), 20)
|
|
|
|
def test_twirl_circuit_list(self):
|
|
"""Test twirling for a circuit list of gates to twirl."""
|
|
qc = QuantumCircuit(2)
|
|
qc.cx(0, 1)
|
|
qc.cz(0, 1)
|
|
qc.ecr(0, 1)
|
|
qc.iswap(0, 1)
|
|
res = pauli_twirl_2q_gates(qc, twirling_gate=["cx", iSwapGate()], seed=12345)
|
|
np.testing.assert_allclose(
|
|
Operator(qc), Operator(res), err_msg=f"{qc}\nnot equiv to\n{res}"
|
|
)
|
|
self.assertNotEqual(res, qc)
|
|
self.assertEqual(sum(res.count_ops().values()), 12)
|
|
|
|
@ddt.data(CXGate, ECRGate, CZGate, iSwapGate)
|
|
def test_many_twirls_equiv(self, gate):
|
|
"""Test the twirled circuits are equivalent if num_twirls>1."""
|
|
qc = QuantumCircuit(2)
|
|
qc.append(gate(), (0, 1))
|
|
res = pauli_twirl_2q_gates(qc, gate, seed=424242, num_twirls=1000)
|
|
for twirled_circuit in res:
|
|
np.testing.assert_allclose(
|
|
Operator(qc), Operator(twirled_circuit), err_msg=f"gate: {gate} not equiv to\n{res}"
|
|
)
|
|
self.assertNotEqual(twirled_circuit, qc)
|
|
|
|
def test_invalid_gate(self):
|
|
"""Test an error is raised with a non-standard gate."""
|
|
|
|
class MyGate(Gate):
|
|
"""Custom gate."""
|
|
|
|
def __init__(self):
|
|
super().__init__("custom", num_qubits=2, params=[])
|
|
|
|
qc = QuantumCircuit(2)
|
|
qc.append(MyGate(), (0, 1))
|
|
|
|
with self.assertRaises(QiskitError):
|
|
pauli_twirl_2q_gates(qc, twirling_gate=MyGate())
|
|
|
|
def test_custom_standard_gate(self):
|
|
"""Test an error is raised with an unsupported standard gate."""
|
|
qc = QuantumCircuit(2)
|
|
qc.swap(0, 1)
|
|
res = pauli_twirl_2q_gates(qc, twirling_gate=SwapGate())
|
|
np.testing.assert_allclose(
|
|
Operator(qc), Operator(res), err_msg=f"gate: {qc} not equiv to\n{res}"
|
|
)
|
|
self.assertNotEqual(qc, res)
|
|
|
|
def test_invalid_string(self):
|
|
"""Test an error is raised with an unsupported standard gate."""
|
|
qc = QuantumCircuit(2)
|
|
qc.swap(0, 1)
|
|
with self.assertRaises(QiskitError):
|
|
pauli_twirl_2q_gates(qc, twirling_gate="swap")
|
|
|
|
def test_invalid_str_entry_in_list(self):
|
|
"""Test an error is raised with an unsupported string gate in list."""
|
|
qc = QuantumCircuit(2)
|
|
qc.swap(0, 1)
|
|
with self.assertRaises(QiskitError):
|
|
pauli_twirl_2q_gates(qc, twirling_gate=[CXGate, "swap"])
|
|
|
|
def test_invalid_class_entry_in_list(self):
|
|
"""Test an error is raised with an unsupported string gate in list."""
|
|
qc = QuantumCircuit(2)
|
|
qc.swap(0, 1)
|
|
res = pauli_twirl_2q_gates(qc, twirling_gate=[SwapGate(), "cx"])
|
|
np.testing.assert_allclose(
|
|
Operator(qc), Operator(res), err_msg=f"gate: {qc} not equiv to\n{res}"
|
|
)
|
|
self.assertNotEqual(qc, res)
|
|
|
|
@ddt.data(CXGate, ECRGate, CZGate, iSwapGate)
|
|
def test_full_circuit(self, gate):
|
|
"""Test a circuit with a random assortment of gates."""
|
|
qc = random_circuit(5, 25, seed=12345678942)
|
|
qc.append(PermutationGate([1, 2, 0]), [0, 1, 2])
|
|
res = pauli_twirl_2q_gates(qc)
|
|
np.testing.assert_allclose(
|
|
Operator(qc), Operator(res), err_msg=f"gate: {gate} not equiv to\n{res}"
|
|
)
|
|
|
|
@ddt.data(CXGate, ECRGate, CZGate, iSwapGate)
|
|
def test_control_flow(self, gate):
|
|
"""Test we twirl inside control flow blocks."""
|
|
qc = QuantumCircuit(2, 1)
|
|
with qc.if_test((qc.clbits[0], 0)):
|
|
qc.append(gate(), [0, 1])
|
|
res = pauli_twirl_2q_gates(qc)
|
|
np.testing.assert_allclose(
|
|
Operator(res.data[0].operation.blocks[0]),
|
|
Operator(gate()),
|
|
err_msg=f"gate: {gate} not equiv to\n{res}",
|
|
)
|
|
|
|
def test_metadata_is_preserved(self):
|
|
"""Test we preserve circuit metadata after twirling."""
|
|
qc = QuantumCircuit(2)
|
|
qc.cx(0, 1)
|
|
qc.ecr(0, 1)
|
|
qc.iswap(0, 1)
|
|
qc.cz(0, 1)
|
|
qc.metadata = {"is_this_circuit_twirled?": True}
|
|
res = pauli_twirl_2q_gates(qc, twirling_gate=CZGate, num_twirls=5)
|
|
for out_circ in res:
|
|
self.assertEqual(out_circ.metadata, qc.metadata)
|
|
|
|
def test_random_circuit_optimized(self):
|
|
"""Test we run 1q gate optimization if specified."""
|
|
qc = random_circuit(5, 25, seed=1234567842)
|
|
qc.barrier()
|
|
qc = qc.decompose()
|
|
target = Target.from_configuration(basis_gates=["cx", "iswap", "cz", "ecr", "r"])
|
|
res = pauli_twirl_2q_gates(qc, seed=12345678, num_twirls=5, target=target)
|
|
for out_circ in res:
|
|
self.assertEqual(
|
|
Operator(out_circ),
|
|
Operator(qc),
|
|
f"{qc}\nnot equiv to\n{out_circ}",
|
|
)
|
|
count_ops = out_circ.count_ops()
|
|
self.assertNotIn("x", count_ops)
|
|
self.assertNotIn("y", count_ops)
|
|
self.assertNotIn("z", count_ops)
|
|
self.assertNotIn("id", count_ops)
|
|
self.assertIn("r", count_ops)
|
|
|
|
def test_error_on_invalid_qubit_count(self):
|
|
"""Test an error is raised on non-2q gates."""
|
|
qc = QuantumCircuit(5)
|
|
with self.assertRaises(QiskitError):
|
|
pauli_twirl_2q_gates(qc, [CCXGate()])
|
|
with self.assertRaises(QiskitError):
|
|
pauli_twirl_2q_gates(qc, [XGate()])
|
|
|
|
def test_error_on_parameterized_gate(self):
|
|
"""Test an error is raised on parameterized 2q gates."""
|
|
qc = QuantumCircuit(5)
|
|
with self.assertRaises(QiskitError):
|
|
pauli_twirl_2q_gates(qc, [RZXGate(3.24)])
|
|
|
|
def test_with_global_phase(self):
|
|
"""Test twirling a circuit with parameterized global phase."""
|
|
|
|
x = Parameter("x")
|
|
qc = QuantumCircuit(2, global_phase=x)
|
|
res = pauli_twirl_2q_gates(qc, seed=2)
|
|
bound = res.assign_parameters([1])
|
|
|
|
self.assertEqual(bound.global_phase, 1)
|