Skip to content

Commit 98c6eab

Browse files
committed
Preserve ordering of gates when translating
1 parent 9799911 commit 98c6eab

3 files changed

Lines changed: 80 additions & 18 deletions

File tree

python/qiskit_paulice/checks.py

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -378,31 +378,56 @@ def _input_basis_gates(circuit: QuantumCircuit) -> list[str]:
378378

379379

380380
def _translate_to_basis(circuit: QuantumCircuit, basis_gates: list[str]) -> QuantumCircuit:
381-
"""Re-express ``circuit`` in ``basis_gates`` via basis translation only.
382-
383-
No coupling map or target is supplied, so this performs a local gate-set
384-
translation without any routing or relayout -- qubit placement (and any ISA
385-
layout) is preserved. Registers and measurements pass through unchanged.
381+
"""Re-express ``circuit`` in ``basis_gates`` while **preserving gate order**.
382+
383+
Running ``BasisTranslator`` on the whole circuit round-trips through a DAG and
384+
re-emits gates in a topological order that reshuffles independent (parallel)
385+
gates -- which would smear the entangling-layer structure the check picker
386+
carefully preserved. Instead each instruction is translated on its own and
387+
appended in the circuit's original order, so that structure survives. No
388+
coupling map or target is supplied, so there is no routing or relayout; qubit
389+
placement (and any ISA layout) is preserved, and measurements pass through.
386390
"""
387391
pm = PassManager(
388392
[
389393
UnrollCustomDefinitions(_SEL, basis_gates),
390394
BasisTranslator(_SEL, basis_gates),
391395
]
392396
)
393-
try:
394-
return pm.run(circuit)
395-
except TranspilerError:
396-
# The input basis can't express the checks the picker inserted -- it is
397-
# not universal (e.g. {h, cx}, with no phase gate, as for a virtual GHZ).
398-
# Leave the circuit in the package's internal Clifford basis.
399-
warnings.warn(
400-
f"Could not re-express the output in the input gate set {sorted(basis_gates)}; "
401-
"it is not universal for the inserted checks. Returning the circuit in the "
402-
"internal Clifford basis instead.",
403-
stacklevel=2,
404-
)
405-
return circuit
397+
keep = set(basis_gates) | {"measure", "barrier"}
398+
# Translate each unique gate once (Clifford gates carry no parameters, so the name
399+
# is a complete key); ``None`` marks a gate the basis cannot express.
400+
translated_block: dict[str, QuantumCircuit | None] = {}
401+
out = circuit.copy_empty_like()
402+
for instruction in circuit.data:
403+
op = instruction.operation
404+
if op.name in keep:
405+
out.append(instruction)
406+
continue
407+
if op.name not in translated_block:
408+
block = QuantumCircuit(op.num_qubits)
409+
block.append(op, range(op.num_qubits))
410+
try:
411+
translated_block[op.name] = pm.run(block)
412+
except TranspilerError:
413+
translated_block[op.name] = None
414+
block = translated_block[op.name]
415+
if block is None:
416+
# The input basis can't express the checks the picker inserted -- it is not
417+
# universal (e.g. {h, cx}, with no phase gate, as for a virtual GHZ). Leave
418+
# the circuit in the package's internal Clifford basis (already order-preserved).
419+
warnings.warn(
420+
f"Could not re-express the output in the input gate set {sorted(basis_gates)}; "
421+
"it is not universal for the inserted checks. Returning the circuit in the "
422+
"internal Clifford basis instead.",
423+
stacklevel=2,
424+
)
425+
return circuit
426+
for sub in block.data:
427+
out.append(
428+
sub.operation, [instruction.qubits[block.find_bit(q).index] for q in sub.qubits]
429+
)
430+
return out
406431

407432

408433
def _strip_measurements_cregs_barriers(circuit: QuantumCircuit):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
fixes:
3+
- |
4+
:func:`qiskit_paulice.add_pauli_checks` now translates its output to the input circuit's gate
5+
set gate-by-gate, preserving gate order and the circuit's layer structure.

test/test_checks.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,38 @@ def test_non_universal_input_basis_falls_back(self):
130130
out = _translate_to_basis(qc, ["h", "cx"])
131131
self.assertEqual(_gate_names(out), {"sx"})
132132

133+
def test_translation_preserves_gate_order(self):
134+
# Whole-circuit BasisTranslator round-trips through a DAG and re-emits
135+
# independent gates in topological order, which groups an interleaved
136+
# brickwork [(0,1), (2,3), (0,1), (2,3)] into [(0,1), (0,1), (2,3),
137+
# (2,3)] and smears the entangling-layer structure the picker preserved.
138+
# Translation must keep every payload gate in its original position.
139+
num_qubits = 4
140+
qc = QuantumCircuit(num_qubits)
141+
qc.h(range(num_qubits))
142+
for _ in range(3):
143+
qc.cx(0, 1)
144+
qc.cx(2, 3)
145+
for qubit in range(num_qubits):
146+
qc.s(qubit)
147+
qc.measure_all()
148+
checked = add_pauli_checks(qc, [0], _DEFAULT_NOISE, seed=7)[-1]
149+
150+
def two_qubit_pairs(circuit, payload_only=False):
151+
pairs = []
152+
for instruction in circuit.data:
153+
if len(instruction.qubits) != 2:
154+
continue
155+
pair = tuple(sorted(circuit.find_bit(q).index for q in instruction.qubits))
156+
if payload_only and max(pair) >= num_qubits:
157+
continue
158+
pairs.append(pair)
159+
return pairs
160+
161+
self.assertEqual(
162+
two_qubit_pairs(checked.circuit, payload_only=True), two_qubit_pairs(qc)
163+
)
164+
133165

134166
class TestAddPauliChecksShallowWires(unittest.TestCase):
135167
"""GHZ circuits stress the picker: per-qubit wires are very shallow."""

0 commit comments

Comments
 (0)