-
Notifications
You must be signed in to change notification settings - Fork 28
fix: reject mixed declared/physical programs under consolidate_qubits #362
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cb573d7
33be273
3e1b60f
c18b624
f4fcfc6
50373a1
77f3775
53dbc76
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -331,48 +331,138 @@ def test_incorrect_qubit_reg(qasm_code, error_message, error_span, caplog): | |||||||||||||||||||||
| assert error_span in caplog.text | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def test_physical_qubits_are_not_consolidated(): | ||||||||||||||||||||||
| """Physical qubits are absolute hardware indices and belong to no declared register, | ||||||||||||||||||||||
| so consolidation must leave them alone instead of raising (see #343).""" | ||||||||||||||||||||||
| qasm = """OPENQASM 3.0; | ||||||||||||||||||||||
| @pytest.mark.parametrize( | ||||||||||||||||||||||
| "operation", | ||||||||||||||||||||||
| [ | ||||||||||||||||||||||
| "cz $2, q[1];", | ||||||||||||||||||||||
| "c = measure $2;", | ||||||||||||||||||||||
| "reset $2;", | ||||||||||||||||||||||
| "barrier $2;", | ||||||||||||||||||||||
| ], | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| def test_mixed_declared_and_physical_rejected_when_consolidating(operation): | ||||||||||||||||||||||
| """A program mixing declared registers with physical qubits would consolidate into | ||||||||||||||||||||||
| two address spaces the output cannot relate, so it is rejected (issue #353).""" | ||||||||||||||||||||||
| qasm = f"""OPENQASM 3.0; | ||||||||||||||||||||||
| include "stdgates.inc"; | ||||||||||||||||||||||
| qubit[2] q; | ||||||||||||||||||||||
| bit c; | ||||||||||||||||||||||
| h q[0]; | ||||||||||||||||||||||
| {operation} | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| result = loads(qasm, device_qubits=5) | ||||||||||||||||||||||
| with pytest.raises( | ||||||||||||||||||||||
| ValidationError, match=r"mixes declared registers with physical qubits \(\$2\)" | ||||||||||||||||||||||
| ): | ||||||||||||||||||||||
| result.unroll(consolidate_qubits=True) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def test_mixed_error_lists_physical_qubits_in_numeric_order(): | ||||||||||||||||||||||
| """A lexicographic sort would report ($10, $2) once a device has ten or more | ||||||||||||||||||||||
| qubits, which reads as unordered when scanning for the offending references.""" | ||||||||||||||||||||||
| qasm = """OPENQASM 3.0; | ||||||||||||||||||||||
| include "stdgates.inc"; | ||||||||||||||||||||||
| qubit[1] q; | ||||||||||||||||||||||
| h q[0]; | ||||||||||||||||||||||
| h $2; | ||||||||||||||||||||||
| h $10; | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| result = loads(qasm, device_qubits=16) | ||||||||||||||||||||||
| with pytest.raises( | ||||||||||||||||||||||
| ValidationError, match=r"mixes declared registers with physical qubits \(\$2, \$10\)" | ||||||||||||||||||||||
| ): | ||||||||||||||||||||||
| result.unroll(consolidate_qubits=True) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def test_mixed_error_names_the_way_out(): | ||||||||||||||||||||||
| """The message is the whole diagnostic here -- no statement node is available at | ||||||||||||||||||||||
| finalize time, so there is no line number to fall back on.""" | ||||||||||||||||||||||
| qasm = """OPENQASM 3.0; | ||||||||||||||||||||||
| include "stdgates.inc"; | ||||||||||||||||||||||
| qubit[1] q; | ||||||||||||||||||||||
| h q[0]; | ||||||||||||||||||||||
| h $2; | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| result = loads(qasm, device_qubits=5) | ||||||||||||||||||||||
| with pytest.raises(ValidationError, match=r"Unroll without 'consolidate_qubits=True'"): | ||||||||||||||||||||||
| result.unroll(consolidate_qubits=True) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @pytest.mark.parametrize( | ||||||||||||||||||||||
| "declaration", ["int __PYQASM_QUBITS__ = 3;", "qubit[2] __PYQASM_QUBITS__;"] | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| def test_reserved_name_is_reported_before_physical_qubit_exits(declaration): | ||||||||||||||||||||||
| """Declaring the reserved name must raise even when the program also uses physical | ||||||||||||||||||||||
| qubits, which would otherwise return early or report the wrong problem.""" | ||||||||||||||||||||||
| qasm = f"""OPENQASM 3.0; | ||||||||||||||||||||||
| include "stdgates.inc"; | ||||||||||||||||||||||
| {declaration} | ||||||||||||||||||||||
| h $1; | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| result = loads(qasm, device_qubits=5) | ||||||||||||||||||||||
| with pytest.raises(ValidationError, match=r"'__PYQASM_QUBITS__' is already defined"): | ||||||||||||||||||||||
| result.unroll(consolidate_qubits=True) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def test_zero_sized_register_still_counts_as_declared(): | ||||||||||||||||||||||
| """A zero-sized declared register is still a second address space (Argus P1).""" | ||||||||||||||||||||||
| qasm = """OPENQASM 3.0; | ||||||||||||||||||||||
| include "stdgates.inc"; | ||||||||||||||||||||||
| qubit[0] q; | ||||||||||||||||||||||
| h $1; | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| result = loads(qasm) | ||||||||||||||||||||||
| with pytest.raises(ValidationError, match=r"mixes declared registers with physical qubits"): | ||||||||||||||||||||||
| result.unroll(consolidate_qubits=True) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def test_mixed_declared_and_physical_still_unrolls_without_consolidation(): | ||||||||||||||||||||||
| """The mixed-program rejection applies only under consolidate_qubits=True.""" | ||||||||||||||||||||||
|
Comment on lines
+418
to
+420
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 P2 (5/10) · Testing: The regression test does not verify that the ValidationError names every physical qubit Users could receive incomplete diagnostics for mixed programs while CI still reports the acceptance criterion as satisfied.
Suggested change
|
||||||||||||||||||||||
| qasm = """OPENQASM 3.0; | ||||||||||||||||||||||
| include "stdgates.inc"; | ||||||||||||||||||||||
| qubit[2] q; | ||||||||||||||||||||||
| h q[0]; | ||||||||||||||||||||||
| cz $2, q[1]; | ||||||||||||||||||||||
| c = measure $2; | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| result = loads(qasm, device_qubits=5) | ||||||||||||||||||||||
| result.unroll() | ||||||||||||||||||||||
| expected_qasm = """OPENQASM 3.0; | ||||||||||||||||||||||
| qubit[5] __PYQASM_QUBITS__; | ||||||||||||||||||||||
| include "stdgates.inc"; | ||||||||||||||||||||||
| bit[1] c; | ||||||||||||||||||||||
| h __PYQASM_QUBITS__[0]; | ||||||||||||||||||||||
| cz $2, __PYQASM_QUBITS__[1]; | ||||||||||||||||||||||
| c = measure $2; | ||||||||||||||||||||||
| qubit[2] q; | ||||||||||||||||||||||
| h q[0]; | ||||||||||||||||||||||
| cz $2, q[1]; | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| result = loads(qasm, device_qubits=5) | ||||||||||||||||||||||
| result.unroll(consolidate_qubits=True) | ||||||||||||||||||||||
| check_unrolled_qasm(dumps(result), expected_qasm) | ||||||||||||||||||||||
| # two consolidated slots plus physical $2, which sizes the count to its own index + 1. | ||||||||||||||||||||||
| # neither number is the declared qubit[5], which comes from device_qubits (see #353) | ||||||||||||||||||||||
| assert result.num_qubits == 3 | ||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Type: Implementation Rationale: The Change Requested: Add the output assertion. Verified against this commit —
Suggested change
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def test_physical_qubits_only(): | ||||||||||||||||||||||
| """With nothing to consolidate, no internal register is declared: the program | ||||||||||||||||||||||
| keeps speaking the physical address space alone (issue #353).""" | ||||||||||||||||||||||
| qasm = """OPENQASM 3.0; | ||||||||||||||||||||||
| include "stdgates.inc"; | ||||||||||||||||||||||
| h $1; | ||||||||||||||||||||||
| cz $2, $1; | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| expected_qasm = """OPENQASM 3.0; | ||||||||||||||||||||||
| qubit[5] __PYQASM_QUBITS__; | ||||||||||||||||||||||
| include "stdgates.inc"; | ||||||||||||||||||||||
| h $1; | ||||||||||||||||||||||
| cz $2, $1; | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| result = loads(qasm, device_qubits=5) | ||||||||||||||||||||||
| result.unroll(consolidate_qubits=True) | ||||||||||||||||||||||
| check_unrolled_qasm(dumps(result), expected_qasm) | ||||||||||||||||||||||
| # nothing was consolidated, so the count comes entirely from the physical indices | ||||||||||||||||||||||
| # while the emitted declaration is sized by device_qubits (see #353) | ||||||||||||||||||||||
| # the count comes entirely from the physical indices | ||||||||||||||||||||||
| assert result.num_qubits == 3 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def test_physical_qubits_only_without_device_qubits(): | ||||||||||||||||||||||
| """The unreferenced declaration is suppressed with or without device_qubits set.""" | ||||||||||||||||||||||
| qasm = """OPENQASM 3.0; | ||||||||||||||||||||||
| include "stdgates.inc"; | ||||||||||||||||||||||
| h $1; | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| result = loads(qasm) | ||||||||||||||||||||||
| result.unroll(consolidate_qubits=True) | ||||||||||||||||||||||
| assert "__PYQASM_QUBITS__" not in dumps(result) | ||||||||||||||||||||||
| assert result.num_qubits == 2 | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type: Implementation
Severity: Medium
Rationale: This early return skips the
INTERNAL_QUBIT_REGISTERreserved-name loop directly below, and the check is reachable with that name already declared. Verified againstorigin/main:origin/mainint __PYQASM_QUBITS__ = 3; h $1;ValidationError: Variable '__PYQASM_QUBITS__' is already definedqubit[2] __PYQASM_QUBITS__; h $1;...mixes declared registers with physical qubits ($1)Neither outcome corrupts the output — no internal register is emitted on this path, so nothing actually collides. The concern is the contract: the docstring edited a few lines above still promises a raise "if the reserved register
'__PYQASM_QUBITS__'is already declared", and on this path it no longer does. The second row is also a diagnostic downgrade: the user's real problem is the reserved name, but they are told about physical qubits instead.Change Requested: Move the
global_scopereserved-name loop (lines 526-532) above the new physical-qubit block, so it runs before either exit. One move fixes both rows and keeps the docstring honest. If the intent is instead that the guard should not apply when no register is emitted, narrow the docstring to say so.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reproduced both rows exactly, and took your first option in f4fcfc6 — the reserved-name loop now runs before either physical-qubit exit. Keeping the guard is the right reading: the docstring promise stays true, and a user who declared
__PYQASM_QUBITS__is told about that rather than about physical qubits.Pinned with a parametrized test over both the
intandqubit[2]declarations.