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

Đang mở
#390 0 bình luận 0 reaction 0 người được giao Xem trên GitHub
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

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.