qiskit/test/python/quantum_info/operators/symplectic/test_pauli.py

632 lines
22 KiB
Python

# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2024.
#
# 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.
# pylint: disable=invalid-name
"""Tests for Pauli operator class."""
import itertools as it
import re
import unittest
from functools import lru_cache
from test import QiskitTestCase, combine
import numpy as np
from ddt import data, ddt, unpack
from qiskit import QuantumCircuit
from qiskit.circuit import Qubit
from qiskit.circuit.library import (
CXGate,
CYGate,
CZGate,
ECRGate,
efficient_su2,
HGate,
IGate,
SdgGate,
SGate,
SwapGate,
XGate,
YGate,
ZGate,
)
from qiskit.circuit.library.generalized_gates import PauliGate
from qiskit.compiler.transpiler import transpile
from qiskit.exceptions import QiskitError
from qiskit.primitives import BackendEstimator
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.quantum_info.operators import Operator, Pauli, SparsePauliOp
from qiskit.quantum_info.random import random_clifford, random_pauli
from qiskit.utils import optionals
LABEL_REGEX = re.compile(r"(?P<coeff>[+-]?1?[ij]?)(?P<pauli>[IXYZ]*)")
PHASE_MAP = {"": 0, "-i": 1, "-": 2, "i": 3}
def _split_pauli_label(label):
match_ = LABEL_REGEX.fullmatch(label)
return match_["pauli"], match_["coeff"]
def _phase_from_label(label):
coeff = LABEL_REGEX.fullmatch(label)["coeff"] or ""
return PHASE_MAP[coeff.replace("+", "").replace("1", "").replace("j", "i")]
@lru_cache(maxsize=8)
def pauli_group_labels(nq, full_group=True):
"""Generate list of the N-qubit pauli group string labels"""
labels = ["".join(i) for i in it.product(("I", "X", "Y", "Z"), repeat=nq)]
if full_group:
labels = ["".join(i) for i in it.product(("", "-i", "-", "i"), labels)]
return labels
def operator_from_label(label):
"""Construct operator from full Pauli group label"""
pauli, coeff = _split_pauli_label(label)
coeff = (-1j) ** _phase_from_label(coeff)
return coeff * Operator.from_label(pauli)
@ddt
class TestPauliConversions(QiskitTestCase):
"""Test representation conversions of Pauli"""
@data(*pauli_group_labels(1), *pauli_group_labels(2))
def test_labels(self, label):
"""Test round trip label conversion"""
pauli = Pauli(label)
self.assertEqual(Pauli(str(pauli)), pauli)
@data("S", "XX-")
def test_invalid_labels(self, label):
"""Test raise if invalid labels are supplied"""
with self.assertRaises(QiskitError):
Pauli(label)
@data(*pauli_group_labels(1), *pauli_group_labels(2))
def test_to_operator(self, label):
"""Test Pauli operator conversion"""
value = Operator(Pauli(label))
target = operator_from_label(label)
self.assertEqual(value, target)
@data(*pauli_group_labels(1), *pauli_group_labels(2))
def test_to_matrix_sparse(self, label):
"""Test Pauli operator conversion"""
spmat = Pauli(label).to_matrix(sparse=True)
value = Operator(spmat.todense())
target = operator_from_label(label)
self.assertEqual(value, target)
@data(*pauli_group_labels(1), *pauli_group_labels(2))
def test_to_instruction(self, label):
"""Test Pauli to instruction"""
pauli = Pauli(label)
value = Operator(pauli.to_instruction())
target = Operator(pauli)
self.assertEqual(value, target)
@data((IGate(), "I"), (XGate(), "X"), (YGate(), "Y"), (ZGate(), "Z"))
@unpack
def test_init_single_pauli_gate(self, gate, label):
"""Test initialization from Pauli basis gates"""
self.assertEqual(str(Pauli(gate)), label)
@data("IXYZ", "XXY", "ZYX", "ZI", "Y")
def test_init_pauli_gate(self, label):
"""Test initialization from Pauli basis gates"""
pauli = Pauli(PauliGate(label))
self.assertEqual(str(pauli), label)
@ddt
class TestPauliProperties(QiskitTestCase):
"""Test Pauli properties"""
@data("I", "XY", "XYZ", "IXYZ", "IXYZX")
def test_len(self, label):
"""Test __len__ method"""
self.assertEqual(len(Pauli(label)), len(label))
@data(*it.product(pauli_group_labels(1, full_group=False), pauli_group_labels(1)))
@unpack
def test_equal(self, label1, label2):
"""Test __eq__ method"""
pauli1 = Pauli(label1)
pauli2 = Pauli(label2)
target = (
np.all(pauli1.z == pauli2.z)
and np.all(pauli1.x == pauli2.x)
and pauli1.phase == pauli2.phase
)
self.assertEqual(pauli1 == pauli2, target)
@data(*it.product(pauli_group_labels(1, full_group=False), pauli_group_labels(1)))
@unpack
def test_equiv(self, label1, label2):
"""Test equiv method"""
pauli1 = Pauli(label1)
pauli2 = Pauli(label2)
target = np.all(pauli1.z == pauli2.z) and np.all(pauli1.x == pauli2.x)
self.assertEqual(pauli1.equiv(pauli2), target)
@data(*pauli_group_labels(1))
def test_phase(self, label):
"""Test phase attribute"""
pauli = Pauli(label)
_, coeff = _split_pauli_label(str(pauli))
target = _phase_from_label(coeff)
self.assertEqual(pauli.phase, target)
@data(*((p, q) for p in ["I", "X", "Y", "Z"] for q in range(4)))
@unpack
def test_phase_setter(self, pauli, phase):
"""Test phase setter"""
pauli = Pauli(pauli)
pauli.phase = phase
_, coeff = _split_pauli_label(str(pauli))
value = _phase_from_label(coeff)
self.assertEqual(value, phase)
def test_x_setter(self):
"""Test phase attribute"""
pauli = Pauli("II")
pauli.x = True
self.assertEqual(pauli, Pauli("XX"))
def test_z_setter(self):
"""Test phase attribute"""
pauli = Pauli("II")
pauli.z = True
self.assertEqual(pauli, Pauli("ZZ"))
@data(
*(
("IXYZ", i)
for i in [0, 1, 2, 3, slice(None, None, None), slice(None, 2, None), [0, 3], [2, 1, 3]]
)
)
@unpack
def test_getitem(self, label, qubits):
"""Test __getitem__"""
pauli = Pauli(label)
value = str(pauli[qubits])
val_array = np.array(list(reversed(label)))[qubits]
target = "".join(reversed(val_array.tolist()))
self.assertEqual(value, target, msg=f"indices = {qubits}")
@data(
(0, "iY", "iIIY"),
([1, 0], "XZ", "IZX"),
(slice(None, None, None), "XYZ", "XYZ"),
(slice(None, None, -1), "XYZ", "ZYX"),
)
@unpack
def test_setitem(self, qubits, value, target):
"""Test __setitem__"""
pauli = Pauli("III")
pauli[qubits] = value
self.assertEqual(str(pauli), target)
def test_insert(self):
"""Test insert method"""
pauli = Pauli("III")
pauli = pauli.insert([2, 0, 4], "XYZ")
self.assertEqual(str(pauli), "IXIZIY")
def test_delete(self):
"""Test delete method"""
pauli = Pauli("IXYZ")
pauli = pauli.delete([])
self.assertEqual(str(pauli), "IXYZ")
pauli = pauli.delete([0, 2])
self.assertEqual(str(pauli), "IY")
@ddt
class TestPauli(QiskitTestCase):
"""Tests for Pauli operator class."""
@data(*pauli_group_labels(2))
def test_conjugate(self, label):
"""Test conjugate method."""
value = Pauli(label).conjugate()
target = operator_from_label(label).conjugate()
self.assertEqual(Operator(value), target)
@data(*pauli_group_labels(2))
def test_transpose(self, label):
"""Test transpose method."""
value = Pauli(label).transpose()
target = operator_from_label(label).transpose()
self.assertEqual(Operator(value), target)
@data(*pauli_group_labels(2))
def test_adjoint(self, label):
"""Test adjoint method."""
value = Pauli(label).adjoint()
target = operator_from_label(label).adjoint()
self.assertEqual(Operator(value), target)
@data(*pauli_group_labels(2))
def test_inverse(self, label):
"""Test inverse method."""
pauli = Pauli(label)
value = pauli.inverse()
target = pauli.adjoint()
self.assertEqual(value, target)
@data(*it.product(pauli_group_labels(2, full_group=False), repeat=2))
@unpack
def test_dot(self, label1, label2):
"""Test dot method."""
p1 = Pauli(label1)
p2 = Pauli(label2)
value = Operator(p1.dot(p2))
op1 = operator_from_label(label1)
op2 = operator_from_label(label2)
target = op1.dot(op2)
self.assertEqual(value, target)
target = op1 @ op2
self.assertEqual(value, target)
@data(*pauli_group_labels(1))
def test_dot_qargs(self, label2):
"""Test dot method with qargs."""
label1 = "-iXYZ"
p1 = Pauli(label1)
p2 = Pauli(label2)
qargs = [0]
value = Operator(p1.dot(p2, qargs=qargs))
op1 = operator_from_label(label1)
op2 = operator_from_label(label2)
target = op1.dot(op2, qargs=qargs)
self.assertEqual(value, target)
@data(*it.product(pauli_group_labels(2, full_group=False), repeat=2))
@unpack
def test_compose(self, label1, label2):
"""Test compose method."""
p1 = Pauli(label1)
p2 = Pauli(label2)
value = Operator(p1.compose(p2))
op1 = operator_from_label(label1)
op2 = operator_from_label(label2)
target = op1.compose(op2)
self.assertEqual(value, target)
@data(*pauli_group_labels(1))
def test_compose_qargs(self, label2):
"""Test compose method with qargs."""
label1 = "-XYZ"
p1 = Pauli(label1)
p2 = Pauli(label2)
qargs = [0]
value = Operator(p1.compose(p2, qargs=qargs))
op1 = operator_from_label(label1)
op2 = operator_from_label(label2)
target = op1.compose(op2, qargs=qargs)
self.assertEqual(value, target)
@data(*it.product(pauli_group_labels(1, full_group=False), repeat=2))
@unpack
def test_tensor(self, label1, label2):
"""Test tensor method."""
p1 = Pauli(label1)
p2 = Pauli(label2)
value = Operator(p1.tensor(p2))
op1 = operator_from_label(label1)
op2 = operator_from_label(label2)
target = op1.tensor(op2)
self.assertEqual(value, target)
@data(*it.product(pauli_group_labels(1, full_group=False), repeat=2))
@unpack
def test_expand(self, label1, label2):
"""Test expand method."""
p1 = Pauli(label1)
p2 = Pauli(label2)
value = Operator(p1.expand(p2))
op1 = operator_from_label(label1)
op2 = operator_from_label(label2)
target = op1.expand(op2)
self.assertEqual(value, target)
@data("II", "XI", "YX", "ZZ", "YZ")
def test_power(self, label):
"""Test power method."""
iden = Pauli("II")
op = Pauli(label)
self.assertTrue(op**2, iden)
@data(1, 1.0, -1, -1.0, 1j, -1j)
def test_multiply(self, val):
"""Test multiply method."""
op = val * Pauli(([True, True], [False, False], 0))
phase = (-1j) ** op.phase
self.assertEqual(phase, val)
op = Pauli(([True, True], [False, False], 0)) * val
phase = (-1j) ** op.phase
self.assertEqual(phase, val)
def test_multiply_except(self):
"""Test multiply method raises exceptions."""
op = Pauli("XYZ")
self.assertRaises(QiskitError, op._multiply, 2)
@data(0, 1, 2, 3)
def test_negate(self, phase):
"""Test negate method"""
op = Pauli(([False], [True], phase))
neg = -op
self.assertTrue(op.equiv(neg))
self.assertEqual(neg.phase, (op.phase + 2) % 4)
@data(*it.product(pauli_group_labels(1, False), repeat=2))
@unpack
def test_commutes(self, p1, p2):
"""Test commutes method"""
P1 = Pauli(p1)
P2 = Pauli(p2)
self.assertEqual(P1.commutes(P2), P1.dot(P2) == P2.dot(P1))
@data(*it.product(pauli_group_labels(1, False), repeat=2))
@unpack
def test_anticommutes(self, p1, p2):
"""Test anticommutes method"""
P1 = Pauli(p1)
P2 = Pauli(p2)
self.assertEqual(P1.anticommutes(P2), P1.dot(P2) == -P2.dot(P1))
@data(
*it.product(
(IGate(), XGate(), YGate(), ZGate(), HGate(), SGate(), SdgGate()),
pauli_group_labels(1, False),
)
)
@unpack
def test_evolve_clifford1(self, gate, label):
"""Test evolve method for 1-qubit Clifford gates."""
op = Operator(gate)
pauli = Pauli(label)
value = Operator(pauli.evolve(gate))
value_h = Operator(pauli.evolve(gate, frame="h"))
value_s = Operator(pauli.evolve(gate, frame="s"))
value_inv = Operator(pauli.evolve(gate.inverse()))
target = op.adjoint().dot(pauli).dot(op)
self.assertEqual(value, target)
self.assertEqual(value, value_h)
self.assertEqual(value_inv, value_s)
@data(
*it.product(
(CXGate(), CYGate(), CZGate(), SwapGate(), ECRGate()), pauli_group_labels(2, False)
)
)
@unpack
def test_evolve_clifford2(self, gate, label):
"""Test evolve method for 2-qubit Clifford gates."""
op = Operator(gate)
pauli = Pauli(label)
value = Operator(pauli.evolve(gate))
value_h = Operator(pauli.evolve(gate, frame="h"))
value_s = Operator(pauli.evolve(gate, frame="s"))
value_inv = Operator(pauli.evolve(gate.inverse()))
target = op.adjoint().dot(pauli).dot(op)
self.assertEqual(value, target)
self.assertEqual(value, value_h)
self.assertEqual(value_inv, value_s)
@data(
*it.product(
(
IGate(),
XGate(),
YGate(),
ZGate(),
HGate(),
SGate(),
SdgGate(),
CXGate(),
CYGate(),
CZGate(),
SwapGate(),
ECRGate(),
),
[int, np.int8, np.uint8, np.int16, np.uint16, np.int32, np.uint32, np.int64, np.uint64],
)
)
@unpack
def test_phase_dtype_evolve_clifford(self, gate, dtype):
"""Test phase dtype for evolve method for Clifford gates."""
z = np.ones(gate.num_qubits, dtype=bool)
x = np.ones(gate.num_qubits, dtype=bool)
phase = (np.sum(z & x) % 4).astype(dtype)
paulis = Pauli((z, x, phase))
evo = paulis.evolve(gate)
self.assertEqual(evo.phase.dtype, dtype)
def test_evolve_clifford_qargs(self):
"""Test evolve method for random Clifford"""
cliff = random_clifford(3, seed=10)
op = Operator(cliff)
pauli = random_pauli(5, seed=10)
qargs = [3, 0, 1]
value = Operator(pauli.evolve(cliff, qargs=qargs))
value_h = Operator(pauli.evolve(cliff, qargs=qargs, frame="h"))
value_s = Operator(pauli.evolve(cliff, qargs=qargs, frame="s"))
value_inv = Operator(pauli.evolve(cliff.adjoint(), qargs=qargs))
target = Operator(pauli).compose(op.adjoint(), qargs=qargs).dot(op, qargs=qargs)
self.assertEqual(value, target)
self.assertEqual(value, value_h)
self.assertEqual(value_inv, value_s)
@data("s", "h")
def test_evolve_with_misleading_name(self, frame):
"""Test evolve by circuit contents, not by name (fixed bug)."""
circ = QuantumCircuit(2, name="cx")
p = Pauli("IX")
self.assertEqual(p, p.evolve(circ, frame=frame))
def test_barrier_delay_sim(self):
"""Test barrier and delay instructions can be simulated"""
target_circ = QuantumCircuit(2)
target_circ.x(0)
target_circ.y(1)
target = Pauli(target_circ)
circ = QuantumCircuit(2)
circ.x(0)
circ.delay(100, 0)
circ.barrier([0, 1])
circ.y(1)
value = Pauli(circ)
self.assertEqual(value, target)
@data(("", 0), ("-", 2), ("i", 3), ("-1j", 1))
@unpack
def test_zero_qubit_pauli_construction(self, label, phase):
"""Test that Paulis of zero qubits can be constructed."""
expected = Pauli(label + "X")[0:0] # Empty slice from a 1q Pauli, which becomes phaseless
expected.phase = phase
test = Pauli(label)
self.assertEqual(expected, test)
def test_circuit_with_bit(self):
"""Test new-style Bit support when converting from QuantumCircuit"""
circ = QuantumCircuit([Qubit()])
circ.x(0)
value = Pauli(circ)
target = Pauli("X")
self.assertEqual(value, target)
def test_apply_layout_with_transpile(self):
"""Test the apply_layout method with a transpiler layout."""
psi = efficient_su2(4, reps=4, entanglement="circular")
op = Pauli("IZZZ")
backend = GenericBackendV2(num_qubits=7)
transpiled_psi = transpile(psi, backend, optimization_level=3, seed_transpiler=12345)
permuted_op = op.apply_layout(transpiled_psi.layout)
identity_op = Pauli("I" * 7)
initial_layout = transpiled_psi.layout.initial_index_layout(filter_ancillas=True)
final_layout = transpiled_psi.layout.routing_permutation()
qargs = [final_layout[x] for x in initial_layout]
expected_op = identity_op.compose(op, qargs=qargs)
self.assertNotEqual(op, permuted_op)
self.assertEqual(permuted_op, expected_op)
def test_apply_layout_consistency(self):
"""Test that the Pauli apply_layout() is consistent with the SparsePauliOp apply_layout()."""
psi = efficient_su2(4, reps=4, entanglement="circular")
op = Pauli("IZZZ")
sparse_op = SparsePauliOp(op)
backend = GenericBackendV2(num_qubits=7)
transpiled_psi = transpile(psi, backend, optimization_level=3, seed_transpiler=12345)
permuted_op = op.apply_layout(transpiled_psi.layout)
permuted_sparse_op = sparse_op.apply_layout(transpiled_psi.layout)
self.assertEqual(SparsePauliOp(permuted_op), permuted_sparse_op)
def test_permute_pauli_estimator_example(self):
"""Test using the apply_layout method with an estimator workflow."""
psi = efficient_su2(4, reps=4, entanglement="circular")
op = Pauli("XXXI")
backend = GenericBackendV2(num_qubits=7, seed=0)
backend.set_options(seed_simulator=123)
with self.assertWarns(DeprecationWarning):
estimator = BackendEstimator(backend=backend, skip_transpilation=True)
thetas = list(range(len(psi.parameters)))
transpiled_psi = transpile(psi, backend, optimization_level=3)
permuted_op = op.apply_layout(transpiled_psi.layout)
with self.assertWarns(DeprecationWarning):
job = estimator.run(transpiled_psi, permuted_op, thetas)
res = job.result().values
if optionals.HAS_AER:
np.testing.assert_allclose(res, [0.20898438], rtol=0.5, atol=0.2)
else:
np.testing.assert_allclose(res, [0.15820312], rtol=0.5, atol=0.2)
def test_apply_layout_invalid_qubits_list(self):
"""Test that apply_layout with an invalid qubit count raises."""
op = Pauli("YI")
with self.assertRaises(QiskitError):
op.apply_layout([0, 1], 1)
def test_apply_layout_invalid_layout_list(self):
"""Test that apply_layout with an invalid layout list raises."""
op = Pauli("YI")
with self.assertRaises(QiskitError):
op.apply_layout([0, 3], 2)
def test_apply_layout_invalid_layout_list_no_num_qubits(self):
"""Test that apply_layout with an invalid layout list raises."""
op = Pauli("YI")
with self.assertRaises(QiskitError):
op.apply_layout([0, 2])
def test_apply_layout_layout_list_no_num_qubits(self):
"""Test apply_layout with a layout list and no qubit count"""
op = Pauli("YI")
res = op.apply_layout([1, 0])
self.assertEqual(Pauli("IY"), res)
def test_apply_layout_layout_list_and_num_qubits(self):
"""Test apply_layout with a layout list and qubit count"""
op = Pauli("YI")
res = op.apply_layout([4, 0], 5)
self.assertEqual(Pauli("IIIIY"), res)
def test_apply_layout_null_layout_no_num_qubits(self):
"""Test apply_layout with a null layout"""
op = Pauli("IZ")
res = op.apply_layout(layout=None)
self.assertEqual(op, res)
def test_apply_layout_null_layout_and_num_qubits(self):
"""Test apply_layout with a null layout a num_qubits provided"""
op = Pauli("IZ")
res = op.apply_layout(layout=None, num_qubits=5)
# this should expand the operator
self.assertEqual(Pauli("IIIIZ"), res)
def test_apply_layout_null_layout_invalid_num_qubits(self):
"""Test apply_layout with a null layout and num_qubits smaller than capable"""
op = Pauli("IZ")
with self.assertRaises(QiskitError):
op.apply_layout(layout=None, num_qubits=1)
def test_apply_layout_negative_indices(self):
"""Test apply_layout with negative indices"""
op = Pauli("IZ")
with self.assertRaises(QiskitError):
op.apply_layout(layout=[-1, 0], num_qubits=3)
def test_apply_layout_duplicate_indices(self):
"""Test apply_layout with duplicate indices"""
op = Pauli("IZ")
with self.assertRaises(QiskitError):
op.apply_layout(layout=[0, 0], num_qubits=3)
@combine(phase=["", "-i", "-", "i"], layout=[None, []])
def test_apply_layout_zero_qubit(self, phase, layout):
"""Test apply_layout with a zero-qubit operator"""
op = Pauli(phase)
res = op.apply_layout(layout=layout, num_qubits=5)
self.assertEqual(Pauli(phase + "IIIII"), res)
if __name__ == "__main__":
unittest.main()