Skip to content

Commit ea621d6

Browse files
committed
review: copy before filtering, flag branch use instead of counting it
- remove_measurements/remove_barriers(in_place=False) took the copy after filtering, and the nested filter rewrites box and if bodies in place, so the original lost its nested measurements and barriers too - branch operations are now flagged rather than counted: a branch body can be visited more than once and a custom gate marks both its own operands and those of its expansion, so the count was never meaningful - reverse-order test used a branch operand that maps to itself, which could not tell a walked branch from a skipped one
1 parent 60647d0 commit ea621d6

6 files changed

Lines changed: 79 additions & 20 deletions

File tree

src/pyqasm/elements.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,19 @@ class QubitDepthNode(DepthNode):
8686
num_measurements: int = 0
8787
num_gates: int = 0
8888
num_barriers: int = 0
89-
# Operations applied inside an if/else block. Counted apart from the others because
90-
# their depth is only settled once the branch closes, but they still make the qubit used.
91-
num_branch_ops: int = 0
89+
# Set when the qubit is operated on inside an if/else block. Those operations are
90+
# flagged rather than counted: their depth is only settled once the branch closes,
91+
# and a branch body may be visited more than once, so only the fact that the qubit
92+
# is used can be relied on.
93+
used_in_branch: bool = False
9294

9395
def _total_ops(self) -> int:
9496
return (
9597
self.num_resets
9698
+ self.num_measurements
9799
+ self.num_gates
98100
+ self.num_barriers
99-
+ self.num_branch_ops
101+
+ int(self.used_in_branch)
100102
)
101103

102104
def is_idle(self) -> bool:

src/pyqasm/modules/base.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -253,16 +253,15 @@ def remove_measurements(self, in_place: bool = True) -> Optional["QasmModule"]:
253253
Returns:
254254
QasmModule: The module with the measurements removed if in_place is False
255255
"""
256+
# copy first: the filtering rewrites nested box and if bodies in place, so it
257+
# has to run on the module that is being returned
258+
curr_module = self if in_place else self.copy()
256259
stmt_list = (
257-
self._statements
258-
if len(self._unrolled_ast.statements) == 0
259-
else self._unrolled_ast.statements
260+
curr_module._statements
261+
if len(curr_module._unrolled_ast.statements) == 0
262+
else curr_module._unrolled_ast.statements
260263
)
261264
stmts_without_meas = drop_statements(stmt_list, qasm3_ast.QuantumMeasurementStatement)
262-
curr_module = self
263-
264-
if not in_place:
265-
curr_module = self.copy()
266265

267266
for qubit in curr_module._qubit_depths.values():
268267
qubit.num_measurements = 0
@@ -309,15 +308,14 @@ def remove_barriers(self, in_place: bool = True) -> Optional["QasmModule"]:
309308
Returns:
310309
QasmModule: The module with the barriers removed if in_place is False
311310
"""
311+
# copy first, as in remove_measurements: nested bodies are filtered in place
312+
curr_module = self if in_place else self.copy()
312313
stmt_list = (
313-
self._statements
314-
if len(self._unrolled_ast.statements) == 0
315-
else self._unrolled_ast.statements
314+
curr_module._statements
315+
if len(curr_module._unrolled_ast.statements) == 0
316+
else curr_module._unrolled_ast.statements
316317
)
317318
stmts_without_barriers = drop_statements(stmt_list, qasm3_ast.QuantumBarrier)
318-
curr_module = self
319-
if not in_place:
320-
curr_module = self.copy()
321319

322320
for qubit in curr_module._qubit_depths.values():
323321
qubit.num_barriers = 0

src/pyqasm/visitor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1057,7 +1057,7 @@ def _mark_branch_qubit(self, qubit_name: str, qubit_idx: int) -> None:
10571057
qubit_idx: The index of the qubit within the register.
10581058
"""
10591059
self._is_branch_qubits.add((qubit_name, qubit_idx))
1060-
self._module._qubit_depths[(qubit_name, qubit_idx)].num_branch_ops += 1
1060+
self._module._qubit_depths[(qubit_name, qubit_idx)].used_in_branch = True
10611061

10621062
@staticmethod
10631063
def _get_qubit_name_and_id(

tests/qasm3/test_barrier.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,25 @@ def test_remove_barriers_inside_box_and_branch():
143143
check_unrolled_qasm(dumps(module), expected_qasm)
144144

145145

146+
def test_remove_barriers_not_in_place_leaves_the_original_alone():
147+
"""Filtering rewrites nested bodies in place, so it must run on the returned copy."""
148+
qasm_str = """OPENQASM 3.0;
149+
include "stdgates.inc";
150+
qubit[2] q;
151+
h q[0];
152+
box {
153+
barrier q;
154+
x q[1];
155+
}
156+
"""
157+
module = loads(qasm_str)
158+
module.unroll()
159+
new_module = module.remove_barriers(in_place=False)
160+
161+
assert "barrier" not in dumps(new_module)
162+
assert "barrier" in dumps(module)
163+
164+
146165
def test_unroll_barrier():
147166
qasm_str = """
148167
OPENQASM 3.0;

tests/qasm3/test_measurement.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,27 @@ def test_remove_measurement_inside_box_and_branch():
167167
check_unrolled_qasm(dumps(module), expected_qasm)
168168

169169

170+
def test_remove_measurement_not_in_place_leaves_the_original_alone():
171+
"""Filtering rewrites nested bodies in place, so it must run on the returned copy."""
172+
qasm3_string = """
173+
OPENQASM 3.0;
174+
include "stdgates.inc";
175+
qubit[2] q;
176+
bit[2] c;
177+
h q[0];
178+
box {
179+
c[0] = measure q[0];
180+
x q[1];
181+
}
182+
"""
183+
module = loads(qasm3_string)
184+
module.unroll()
185+
new_module = module.remove_measurements(in_place=False)
186+
187+
assert "measure" not in dumps(new_module)
188+
assert "measure" in dumps(module)
189+
190+
170191
def test_init_measure():
171192
qasm3_string = """
172193
OPENQASM 3.0;

tests/qasm3/test_transformations.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ def test_reverse_qubit_order_inside_box_and_branch():
337337
}
338338
c = measure q[0];
339339
if (c == 1) {
340-
x q[1];
340+
x q[0];
341341
}
342342
"""
343343
expected_qasm3_str = """
@@ -351,9 +351,28 @@ def test_reverse_qubit_order_inside_box_and_branch():
351351
}
352352
c[0] = measure q[2];
353353
if (c[0] == true) {
354-
x q[1];
354+
x q[2];
355355
}
356356
"""
357357
module = loads(qasm3_str)
358358
module.reverse_qubit_order()
359359
check_unrolled_qasm(dumps(module), expected_qasm3_str)
360+
361+
362+
def test_remove_idle_qubits_keeps_qubits_used_by_a_custom_gate_in_a_branch():
363+
"""The expansion of a custom gate in a branch marks its qubits as used."""
364+
qasm3_str = """
365+
OPENQASM 3.0;
366+
include "stdgates.inc";
367+
qubit[2] q;
368+
bit c;
369+
gate my_gate p, r { x p; cx p, r; }
370+
h q[0];
371+
c = measure q[0];
372+
if (c == 1) {
373+
my_gate q[0], q[1];
374+
}
375+
"""
376+
module = loads(qasm3_str)
377+
module.remove_idle_qubits()
378+
assert module.num_qubits == 2

0 commit comments

Comments
 (0)