feat: Classical control flow with mid circuit measurements#347
feat: Classical control flow with mid circuit measurements#347
Conversation
More to come
* Squeeze single-element density matrix before computing expectation * Pinned setuptools to fix doc build * Fixed all linter errors
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #347 +/- ##
==========================================
Coverage 100.00% 100.00%
==========================================
Files 48 49 +1
Lines 4142 4752 +610
Branches 426 533 +107
==========================================
+ Hits 4142 4752 +610 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
src/braket/default_simulator/simulation_strategies/batch_operation_strategy.py
Outdated
Show resolved
Hide resolved
src/braket/default_simulator/simulation_strategies/single_operation_strategy.py
Outdated
Show resolved
Hide resolved
|
Some product-level UX questions:
|
laurencap
left a comment
There was a problem hiding this comment.
Reviewed 1.5 files today. Should have more time tomorrow!
| condition = cast_to(BooleanLiteral, self.visit(node.condition)) | ||
| for statement in node.if_block if condition.value else node.else_block: | ||
| self.visit(statement) | ||
| self.context.handle_branching_statement(node, self.visit) |
There was a problem hiding this comment.
Curious about this. Doesn't the visitor usually extract all the important fields, and then call context methods on extracted fields? Why not call visit from this visitor?
There was a problem hiding this comment.
This is really annoying, and I'm totally open to better ways to do this. The problem is, the node itself can contain MCM conditionals, so calling visit right away will trigger the branch hander; the recursive nature of these calls means evaluation is completed "bottom-up," while the branch simulations have to be done "top-down."
| measurement_target (Identifier | IndexedIdentifier | None): The AST node | ||
| for the classical variable being assigned, e.g. ``b`` in | ||
| ``b = measure q[0]``. Used by the branched MCM path to update | ||
| per-path classical variables. None for end-of-circuit measurements. |
There was a problem hiding this comment.
b[0] = measure q[0] is a valid circuit-final measurement right? In those cases, this is None. Is there no reason to track b[0] for circuit-final measurements?
I guess, because we return results by qubit index order at the end, it's not needed for anything?
There was a problem hiding this comment.
Renamed to classical_destination and clarified documentation; None guarantees that it's terminal because the result can't be used (it isn't assigned to anything); however, assignment to a target may be either terminal or feedforward, but we don't know until the result is actually used.
| def add_verbatim_marker(self, marker) -> None: | ||
| """Add verbatim markers""" | ||
|
|
||
| def handle_branching_statement(self, node: BranchingStatement, visit_block: Callable) -> None: |
There was a problem hiding this comment.
Cross commenting from the last file -- visit_block callback feels odd to me. Not that important though. Nice documentation here
| with self.enter_scope(): | ||
| self.declare_variable(node.identifier.name, node.type, i) | ||
| visit_block(deepcopy(node.block)) | ||
| except _BreakSignal: |
There was a problem hiding this comment.
I see, maybe these were moved for ease of integration with _Continue/BreakSignal? Is that basically what changed from interpreter.py, support for these signals?
There was a problem hiding this comment.
Yeah, break and continue actually weren't supported at all before, even for static circuits
| but no control flow depended on them) are registered in the circuit | ||
| as normal end-of-circuit measurements. | ||
| """ | ||
| if not self._is_branched and self._pending_mcm_targets: |
There was a problem hiding this comment.
is_branched and circuit properties won't be called until interpretation is complete?
There was a problem hiding this comment.
_is_branched ia called frequently, but _pending_measurements will always be empty once the simulation is in branched mode (although that can be optimized).
| zero_index = i & ~mask | ||
|
|
||
| # Transfer the amplitude (with proper scaling) | ||
| state[zero_index] += state[i] |
There was a problem hiding this comment.
do you want to copy state into a separate array first, rather than mutating the input array in place? (for consistency with how the Measure operation works)
MCM tests now all start from an OpenQASM program.
| for statement in node.if_block if condition.value else node.else_block: | ||
| self.visit(statement) | ||
| if self.context.supports_midcircuit_measurement: | ||
| self.context.handle_branching_statement(node) |
There was a problem hiding this comment.
This is only handled under BranchingStatement would cause the following use case to fail:
OPENQASM 3.0;
qubit[3] __qubits__;
bit[1] mcm;
bit __bit_1__;
__bit_1__ = measure __qubits__[1];
mcm[0] = __bit_1__;
when mcm[0] = __bit_1__; is called, __bit_1__ is not yet initialized because it's not a branch statement. It throws NameError: Identifier '__bit_1__' is not initialized.
|
Opened #351 to fix and allow re-measuring qubits. |
Issue #, if available:
Description of changes:
Testing done:
Merge Checklist
Put an
xin the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your pull request.General
Tests
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.