Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion qiskit/transpiler/passes/optimization/light_cone.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ def _get_initial_lightcone(
)
lightcone_qubits = [dag.qubits[i] for i in self.indices]
# `lightcone_operations` is a list of tuples, each containing (operation, list_of_qubits)
lightcone_operations = [(PauliGate(self.bit_terms), lightcone_qubits)]
# `bit_terms` uses sparse positional ordering (`bit_terms[i]` acts on
# `indices[i]`), while `PauliGate` uses dense ordering where the
# rightmost character acts on local qubit 0, so reverse the label.
lightcone_operations = [(PauliGate(self.bit_terms[::-1]), lightcone_qubits)]

return set(lightcone_qubits), lightcone_operations

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
fixes:
- |
Fixed a bug in :class:`.LightCone` where the positional association between
multi-qubit sparse ``bit_terms`` and ``indices`` was reversed when
constructing the initial observable. Asymmetric observables could therefore
cause incorrect gate removal and change expectation values. The
``bit_terms[i]`` to ``indices[i]`` association is now preserved correctly.
Fixed `#16866 <https://github.com/Qiskit/qiskit/issues/16866>`__.
29 changes: 27 additions & 2 deletions test/python/transpiler/test_light_cone.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,28 @@ def test_parameterized_asymmetric(self):
expected.cx(2, 3)
expected.ry(theta[8], 0)
expected.ry(theta[10], 2)
expected.rz(theta[14], 2)
# "IZIX" is X(q0) Z(q2): RZ on q0 anticommutes (kept) while RZ on
# q2 commutes (dropped). Pre-gh-16866 reversal kept rz(theta[14], 2).
expected.rz(theta[12], 0)

self.assertEqual(expected, new_circuit)

def test_asymmetric_xz_preserves_bit_term_order(self):
"""Regression test for gh-16866: `bit_terms[i]` must act on `indices[i]`."""
# Sparse "XZ" on [0, 1] means X(q0) Z(q1), so Z(q0) does not
# commute with the observable and must be retained. Reversing the
# association would wrongly interpret it as Z(q0) X(q1) and drop Z(q0).
light_cone = LightCone(bit_terms="XZ", indices=[0, 1])
pm = PassManager([light_cone])

qc = QuantumCircuit(2)
qc.h(0)
qc.z(0)

new_circuit = pm.run(qc)

self.assertEqual(qc, new_circuit)

def test_all_commuting(self):
"""Test for a circuit that fully commutes with an observable."""
bit_terms, indices, _ = SparsePauliOp("IIIZ").to_sparse_list()[0]
Expand Down Expand Up @@ -312,7 +330,14 @@ def test_large_observable(self, pauli_string):

new_circuit = pm.run(qc)

expected = QuantumCircuit(15)
if pauli_string == "YYYYYZXYYYYYYYY":
# Correct sparse association gives Y(q5) Y(q6), which does not
# commute with CX(5, 6), so it must be retained. The pre-gh-16866
# reversal mirrored the observable to Z(q5) X(q6), which commutes
# and wrongly expected an empty circuit.
expected = qc
else:
expected = QuantumCircuit(15)

self.assertEqual(expected, new_circuit)

Expand Down