amazon-braket / amazon-braket/amazon-braket-default-simulator-python
LocalSimulator crash: 'Identifier' object has no attribute 'value' when a measurement's classical destination uses a non-literal index
- Ngôn ngữ chính
- Python
- Star
- 74
- Fork
- 34
- Merge trung bình
- 2 ngày 18 giờ
- Pull request đã merge (30 ngày)
- 3
Mô tả
**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**
```python
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.
Hướng dẫn đóng góp
Đánh giá
Issue này chưa được đánh giá.