Skip to content

LocalSimulator crash: 'Identifier' object has no attribute 'value' when a measurement's classical destination uses a non-literal index #390

Description

@AJ-comp

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.

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