Describe the bug
A measurement whose classical destination is indexed with anything other than an integer literal —
b[k], b[i], b[0:1] — crashes with an internal AttributeError once a later if promotes the
deferred measurement to branched execution. A literal index (b[0]) works.
The same programs run correctly when no branching is triggered, so branched and non-branched
execution disagree.
To reproduce
from braket.devices import LocalSimulator
from braket.ir.openqasm import Program
qasm = """OPENQASM 3;
include "stdgates.inc";
qubit[1] q;
bit[1] r;
const int k = 0;
r[k] = measure q[0];
if (true) { x q[0]; }
"""
LocalSimulator().run(Program(source=qasm), shots=10).result()
AttributeError: 'Identifier' object has no attribute 'value'
Expected behavior
{'1': 10} — identical to the same program with r[k] replaced by r[0], which works.
Isolating controls
Two things vary: the destination index, and what follows the measurement. Header is
qubit[1] q; bit[1] r; const int k = 0; throughout.
|
if (true) { x q[0]; } |
if (true) { int z = 1; } |
if (true) { } |
no if |
r[k] = measure q[0]; |
AttributeError |
{'0': 10} |
{'0': 10} |
{'0': 10} |
r[0] = measure q[0]; |
{'1': 10} |
{'0': 10} |
{'0': 10} |
{'0': 10} |
One cell of eight crashes. It needs both a non-literal destination index and a later if whose body
contains a quantum operation — a classical-only body is not enough. ({'1': 10} vs {'0': 10} is
just x q[0] flipping the qubit; only crash-vs-runs matters here.) Note there is no for loop
anywhere — a loop is simply the usual way to end up with a non-literal index.
Other destination forms, branched vs non-branched
Every non-literal index form behaves the same way. non-branched is the program without the
trailing if; branched is the same program with it:
| destination |
non-branched |
branched |
r[0] literal |
'1' |
'1' |
r[k], const int k = 0 |
'1' |
AttributeError: 'Identifier' … |
r[i], for int i in [0:1] |
'10' |
AttributeError: 'Identifier' … |
r[0:1] = measure q |
'10' |
AttributeError: 'RangeDefinition' … |
The literal is unaffected; every other form runs correctly until branching is involved, which is
what makes branched and non-branched execution disagree.
(r[1-1] fails on both paths — that is a separate gap in interpreter.py, where a destination
index that is a BinaryExpression is never evaluated. Not part of this report.)
Root cause
interpreter.py resolves the destination index and passes it to add_measure as
classical_targets; _branch_measurement receives it and uses it correctly for
self._circuit.add_measure(...), but does not forward it to
_update_classical_from_measurement. _update_indexed_target therefore re-derives the index from
the raw AST via _resolve_index, which does indices[0][0].value and so assumes an
IntegerLiteral.
This is not a scoping problem. const int k = 0 is still perfectly in scope when the crash
happens — _resolve_index never looks it up; it reads the attribute straight off the node, and an
Identifier carries .name, not .value.
Resolving the index lazily at replay time would not be enough either. Probing the context at the
moment _update_indexed_target runs:
const int k = 0; → get_value_by_identifier(k) returns IntegerLiteral(value=0), still in scope.
for int i in [0:1] { r[i] = measure q[i]; } → get_value_by_identifier(i) raises
KeyError: 'Undefined key: i' — the loop variable is gone once the loop exits.
So the index has to be resolved before the enclosing scope dies, which the interpreter already does.
The non-branched flush path (_flush_pending_mcm_targets) sidesteps all of this by destructuring
the destination away (_mcm_dest) and using the already-resolved classical_targets.
Versions
- amazon-braket-default-simulator 1.39.5 (reproduced on
main @ f454cee; _resolve_index there is
identical to the release)
- amazon-braket-sdk 1.124.0
- Python 3.12.9
Note on history
Not a regression from working behavior. On a deterministic feed-forward probe (x q[0] so the
branch must fire; correct result '11'):
| version |
r[0] literal |
r[k] const |
| 1.39.5 |
{'11': 6} |
AttributeError: 'Identifier' … |
| 1.37.0 |
{'11': 6} |
AttributeError: 'Identifier' … |
| 1.36.1 |
NameError: Identifier 'r' is not initialized |
same |
1.36.1 predates branched MCM and rejects the shape outright, so there is no earlier version where
a non-literal destination index worked.
Related
Same area as #386 / #389, but a distinct defect — #389 fixes identifier lookup, this is index
resolution. The repro still crashes on that branch.
I have a fix (forward classical_targets instead of re-deriving the index) and it also removes the
xfail from test_5_2_for_loop_operations_with_branching. Happy to open a PR.
Describe the bug
A measurement whose classical destination is indexed with anything other than an integer literal —
b[k],b[i],b[0:1]— crashes with an internalAttributeErroronce a laterifpromotes thedeferred measurement to branched execution. A literal index (
b[0]) works.The same programs run correctly when no branching is triggered, so branched and non-branched
execution disagree.
To reproduce
Expected behavior
{'1': 10}— identical to the same program withr[k]replaced byr[0], which works.Isolating controls
Two things vary: the destination index, and what follows the measurement. Header is
qubit[1] q; bit[1] r; const int k = 0;throughout.if (true) { x q[0]; }if (true) { int z = 1; }if (true) { }ifr[k] = measure q[0];AttributeError{'0': 10}{'0': 10}{'0': 10}r[0] = measure q[0];{'1': 10}{'0': 10}{'0': 10}{'0': 10}One cell of eight crashes. It needs both a non-literal destination index and a later
ifwhose bodycontains a quantum operation — a classical-only body is not enough. (
{'1': 10}vs{'0': 10}isjust
x q[0]flipping the qubit; only crash-vs-runs matters here.) Note there is noforloopanywhere — a loop is simply the usual way to end up with a non-literal index.
Other destination forms, branched vs non-branched
Every non-literal index form behaves the same way.
non-branchedis the program without thetrailing
if;branchedis the same program with it:r[0]literal'1''1'r[k],const int k = 0'1'AttributeError: 'Identifier' …r[i],for int i in [0:1]'10'AttributeError: 'Identifier' …r[0:1] = measure q'10'AttributeError: 'RangeDefinition' …The literal is unaffected; every other form runs correctly until branching is involved, which is
what makes branched and non-branched execution disagree.
(
r[1-1]fails on both paths — that is a separate gap ininterpreter.py, where a destinationindex that is a
BinaryExpressionis never evaluated. Not part of this report.)Root cause
interpreter.pyresolves the destination index and passes it toadd_measureasclassical_targets;_branch_measurementreceives it and uses it correctly forself._circuit.add_measure(...), but does not forward it to_update_classical_from_measurement._update_indexed_targettherefore re-derives the index fromthe raw AST via
_resolve_index, which doesindices[0][0].valueand so assumes anIntegerLiteral.This is not a scoping problem.
const int k = 0is still perfectly in scope when the crashhappens —
_resolve_indexnever looks it up; it reads the attribute straight off the node, and anIdentifiercarries.name, not.value.Resolving the index lazily at replay time would not be enough either. Probing the context at the
moment
_update_indexed_targetruns:const int k = 0;→get_value_by_identifier(k)returnsIntegerLiteral(value=0), still in scope.for int i in [0:1] { r[i] = measure q[i]; }→get_value_by_identifier(i)raisesKeyError: 'Undefined key: i'— the loop variable is gone once the loop exits.So the index has to be resolved before the enclosing scope dies, which the interpreter already does.
The non-branched flush path (
_flush_pending_mcm_targets) sidesteps all of this by destructuringthe destination away (
_mcm_dest) and using the already-resolvedclassical_targets.Versions
main@ f454cee;_resolve_indexthere isidentical to the release)
Note on history
Not a regression from working behavior. On a deterministic feed-forward probe (
x q[0]so thebranch must fire; correct result
'11'):r[0]literalr[k]const{'11': 6}AttributeError: 'Identifier' …{'11': 6}AttributeError: 'Identifier' …NameError: Identifier 'r' is not initialized1.36.1 predates branched MCM and rejects the shape outright, so there is no earlier version where
a non-literal destination index worked.
Related
Same area as #386 / #389, but a distinct defect — #389 fixes identifier lookup, this is index
resolution. The repro still crashes on that branch.
I have a fix (forward
classical_targetsinstead of re-deriving the index) and it also removes thexfailfromtest_5_2_for_loop_operations_with_branching. Happy to open a PR.