Skip to content

Transformation passes ignore statements nested inside box and if blocks #342

Description

@ryanhill1

Summary

The module transformation passes iterate _unrolled_ast.statements at the top level only, so any quantum statement nested inside a box or an if block is invisible to them. remove_idle_qubits() and reverse_qubit_order() therefore emit programs that are silently wrong — and in the if case, invalid QASM.

Found while working on #340 (verbatim boxes make nested statements the normal case), but this is independent of pragmas: every repro below is on main with plain box / if.

Repro (pyqasm 1.0.4)

import pyqasm

qasm = """OPENQASM 3.0;
include "stdgates.inc";
qubit[3] q;
bit c;
h q[2];
box {
  cx q[2], q[1];
}
c = measure q[1];
"""
m = pyqasm.loads(qasm); m.unroll(); m.remove_idle_qubits()
print(pyqasm.dumps(m))
qubit[2] q;          // q[0] dropped, register collapsed to 2
h q[1];              // top-level operands remapped
box {
  cx q[2], q[1];     // <- untouched: q[2] is out of range now
}
c[0] = measure q[0];

reverse_qubit_order() on the same program remaps h q[2] -> h q[0] and leaves the cx inside the box pointing at the old indices.

With an if block the result is not just wrong but unparseable, and a second problem shows up — a qubit used only inside a branch is classified as idle and removed:

qasm = """OPENQASM 3.0;
include "stdgates.inc";
qubit[3] q;
bit c;
h q[2];
c = measure q[2];
if (c == 1) {
  x q[1];
}
"""
m = pyqasm.loads(qasm); m.unroll(); m.remove_idle_qubits()
qubit[1] q;
h q[0];
c[0] = measure q[0];
if (c[0] == true) {
  x q[1];            // <- q[1] both un-remapped and no longer declared
}

Reloading that output fails with Index 1 out of range for register of size 1.

remove_measurements() / has_measurements() / remove_barriers() have the same blind spot: they filter the top-level statement list, so a measure or barrier inside a box or if block is neither reported nor removed.

Root cause

  • QasmModule._remap_qubits and QasmModule.reverse_qubit_order loop over self._unrolled_ast.statements and test isinstance(operation, QUANTUM_STATEMENTS). Box and BranchingStatement are not in QUANTUM_STATEMENTS, and their bodies are never descended into.
  • remove_measurements / remove_barriers rebuild the top-level list only.
  • Separately, _visit_basic_gate_operation records gates applied inside a branch in _is_branch_qubits (for depth) but never increments QubitDepthNode.num_gates, and is_idle() is defined as _total_ops() == 0. So a qubit touched only inside an if block reads as idle.

Expected

Nested quantum statements should be walked like top-level ones: operand indices remapped by remove_idle_qubits / reverse_qubit_order, and measurements/barriers found and removed wherever they live. A qubit used only inside a branch is not idle and must not be removed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions