From ee3850a28eb9709269ea67affc88673f67e4b8db Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 29 Oct 2024 17:39:48 +0000 Subject: [PATCH] Improve speed of `Pauli.to_label` This uses Numpy vectorised operations and builtin Python tooling to remove Python-space loops from the Pauli label creation. This is a speed up of around 40x on my 2020 Intel MacBook Pro for most Paulis. --- .../operators/symplectic/base_pauli.py | 28 +++++++------------ .../pauli-label-perf-b704cbcc5ef92794.yaml | 4 +++ 2 files changed, 14 insertions(+), 18 deletions(-) create mode 100644 releasenotes/notes/pauli-label-perf-b704cbcc5ef92794.yaml diff --git a/qiskit/quantum_info/operators/symplectic/base_pauli.py b/qiskit/quantum_info/operators/symplectic/base_pauli.py index 1d9e88929b2d..f49127bc32d0 100644 --- a/qiskit/quantum_info/operators/symplectic/base_pauli.py +++ b/qiskit/quantum_info/operators/symplectic/base_pauli.py @@ -33,6 +33,8 @@ # utility for _to_matrix _PARITY = np.array([-1 if bin(i).count("1") % 2 else 1 for i in range(256)], dtype=complex) +# Utility for `_to_label` +_TO_LABEL_CHARS = np.array([ord("I"), ord("X"), ord("Z"), ord("Y")], dtype=np.uint8) class BasePauli(BaseOperator, AdjointMixin, MultiplyMixin): @@ -496,25 +498,15 @@ def _to_label(z, x, phase, group_phase=False, full_group=True, return_phase=Fals the phase ``q`` for the coefficient :math:`(-i)^(q + x.z)` for the label from the full Pauli group. """ - num_qubits = z.size - phase = int(phase) - coeff_labels = {0: "", 1: "-i", 2: "-", 3: "i"} - label = "" - for i in range(num_qubits): - if not z[num_qubits - 1 - i]: - if not x[num_qubits - 1 - i]: - label += "I" - else: - label += "X" - elif not x[num_qubits - 1 - i]: - label += "Z" - else: - label += "Y" - if not group_phase: - phase -= 1 - phase %= 4 + # Map each qubit to the {I: 0, X: 1, Z: 2, Y: 3} integer form, then use Numpy advanced + # indexing to get a new data buffer which is compatible with an ASCII string label. + index = z << 1 + index += x + ascii_label = _TO_LABEL_CHARS[index[::-1]].data.tobytes() + phase = (int(phase) if group_phase else int(phase) - ascii_label.count(b"Y")) % 4 + label = ascii_label.decode("ascii") if phase and full_group: - label = coeff_labels[phase] + label + label = ("", "-i", "-", "i")[phase] + label if return_phase: return label, phase return label diff --git a/releasenotes/notes/pauli-label-perf-b704cbcc5ef92794.yaml b/releasenotes/notes/pauli-label-perf-b704cbcc5ef92794.yaml new file mode 100644 index 000000000000..22035371edc2 --- /dev/null +++ b/releasenotes/notes/pauli-label-perf-b704cbcc5ef92794.yaml @@ -0,0 +1,4 @@ +--- +features_quantum_info: + - | + The performance of :meth:`.Pauli.to_label` has significantly improved for large Paulis.