-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support standalone Var
throughout transpiler
#12322
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,13 +42,13 @@ | |
SwitchCaseOp, | ||
WhileLoopOp, | ||
) | ||
from qiskit.circuit.classical import expr, types | ||
from qiskit.circuit.annotated_operation import ( | ||
AnnotatedOperation, | ||
InverseModifier, | ||
ControlModifier, | ||
PowerModifier, | ||
) | ||
from qiskit.circuit.classical import expr | ||
from qiskit.circuit.delay import Delay | ||
from qiskit.circuit.measure import Measure | ||
from qiskit.circuit.reset import Reset | ||
|
@@ -2175,6 +2175,38 @@ def _control_flow_expr_circuit(self): | |
base.append(CustomCX(), [3, 4]) | ||
return base | ||
|
||
def _standalone_var_circuit(self): | ||
a = expr.Var.new("a", types.Bool()) | ||
b = expr.Var.new("b", types.Uint(8)) | ||
c = expr.Var.new("c", types.Uint(8)) | ||
|
||
qc = QuantumCircuit(5, 5, inputs=[a]) | ||
qc.add_var(b, 12) | ||
qc.h(0) | ||
qc.cx(0, 1) | ||
qc.measure([0, 1], [0, 1]) | ||
qc.store(a, expr.bit_xor(qc.clbits[0], qc.clbits[1])) | ||
with qc.if_test(a) as else_: | ||
qc.cx(2, 3) | ||
qc.cx(3, 4) | ||
qc.cx(4, 2) | ||
with else_: | ||
qc.add_var(c, 12) | ||
with qc.while_loop(a): | ||
with qc.while_loop(a): | ||
qc.add_var(c, 12) | ||
qc.cz(1, 0) | ||
qc.cz(4, 1) | ||
qc.store(a, False) | ||
with qc.switch(expr.bit_and(b, 7)) as case: | ||
with case(0): | ||
qc.cz(0, 1) | ||
qc.cx(1, 2) | ||
qc.cy(2, 0) | ||
with case(case.DEFAULT): | ||
qc.store(b, expr.bit_and(b, 7)) | ||
return qc | ||
|
||
@data(0, 1, 2, 3) | ||
def test_qpy_roundtrip(self, optimization_level): | ||
"""Test that the output of a transpiled circuit can be round-tripped through QPY.""" | ||
|
@@ -2300,6 +2332,46 @@ def test_qpy_roundtrip_control_flow_expr_backendv2(self, optimization_level): | |
round_tripped = qpy.load(buffer)[0] | ||
self.assertEqual(round_tripped, transpiled) | ||
|
||
@data(0, 1, 2, 3) | ||
def test_qpy_roundtrip_standalone_var(self, optimization_level): | ||
"""Test that the output of a transpiled circuit with control flow including standalone `Var` | ||
nodes can be round-tripped through QPY.""" | ||
backend = GenericBackendV2(num_qubits=7) | ||
transpiled = transpile( | ||
self._standalone_var_circuit(), | ||
backend=backend, | ||
basis_gates=backend.operation_names | ||
+ ["if_else", "for_loop", "while_loop", "switch_case"], | ||
optimization_level=optimization_level, | ||
seed_transpiler=2024_05_01, | ||
) | ||
buffer = io.BytesIO() | ||
qpy.dump(transpiled, buffer) | ||
buffer.seek(0) | ||
round_tripped = qpy.load(buffer)[0] | ||
self.assertEqual(round_tripped, transpiled) | ||
|
||
@data(0, 1, 2, 3) | ||
def test_qpy_roundtrip_standalone_var_target(self, optimization_level): | ||
"""Test that the output of a transpiled circuit with control flow including standalone `Var` | ||
nodes can be round-tripped through QPY.""" | ||
backend = GenericBackendV2(num_qubits=11) | ||
backend.target.add_instruction(IfElseOp, name="if_else") | ||
backend.target.add_instruction(ForLoopOp, name="for_loop") | ||
backend.target.add_instruction(WhileLoopOp, name="while_loop") | ||
backend.target.add_instruction(SwitchCaseOp, name="switch_case") | ||
transpiled = transpile( | ||
self._standalone_var_circuit(), | ||
backend=backend, | ||
optimization_level=optimization_level, | ||
seed_transpiler=2024_05_01, | ||
) | ||
buffer = io.BytesIO() | ||
qpy.dump(transpiled, buffer) | ||
buffer.seek(0) | ||
round_tripped = qpy.load(buffer)[0] | ||
self.assertEqual(round_tripped, transpiled) | ||
|
||
@data(0, 1, 2, 3) | ||
def test_qasm3_output(self, optimization_level): | ||
"""Test that the output of a transpiled circuit can be dumped into OpenQASM 3.""" | ||
|
@@ -2350,6 +2422,21 @@ def test_qasm3_output_control_flow_expr(self, optimization_level): | |
str, | ||
) | ||
|
||
@data(0, 1, 2, 3) | ||
def test_qasm3_output_standalone_var(self, optimization_level): | ||
"""Test that the output of a transpiled circuit with control flow and standalone `Var` nodes | ||
can be dumped into OpenQASM 3.""" | ||
transpiled = transpile( | ||
self._standalone_var_circuit(), | ||
backend=GenericBackendV2(num_qubits=13, control_flow=True), | ||
optimization_level=optimization_level, | ||
seed_transpiler=2024_05_01, | ||
) | ||
# TODO: There's not a huge amount we can sensibly test for the output here until we can | ||
# round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump | ||
# itself doesn't throw an error, though. | ||
Comment on lines
+2435
to
+2437
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could assert the stores are preserved and present in the qasm string independent of the transpilers optimization or something I guess. But yeah it probably just makes more sense to wait for the parser to gain support for this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment is basically just copied from the tests above from when we added the enhanced conditions to control flow. |
||
self.assertIsInstance(qasm3.dumps(transpiled), str) | ||
|
||
@data(0, 1, 2, 3) | ||
def test_transpile_target_no_measurement_error(self, opt_level): | ||
"""Test that transpile with a target which contains ideal measurement works | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with this, but this basically means we're treating this as always supported through the transpiler regardless of what the target says. Is that the correct behavior for this? I'm mostly wondering for backends that don't have any classical logic support, and how we intend to indicate this to backends. For other control flow operations they have to be explicitly in the target to work. Is the answer just documentation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Short of adding more magic fields to
supported_instruction
Target
, which could never really capture all the constraints of dynamic-circuits requirements already, or some sort of global flag onTarget
/BackendV2
that we haven't really prepared to put in place, I think documentation is the answer.Dynamic circuits have always been tricky through these tests, because there's so much that's never really been possible to represent as a restriction to the transpiler (nesting can only be to
n
levels, max ofm
blocks, switches must have adefault
, etc etc).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let just go with documenting this for now as part of the 1.1.x final documentation, I'll convert this into an issue to track. Both as an upgrade note in the final release notes and also as a guidance in the writing a backend section of the docs.
I think for 1.2.x we can look at adding a global flag in the target for something like supports store, etc to provide a high level indicator for this kind of capability above the per instruction look ups. We'd probably need to do this into an opt-in way (so it defaults true) but I think having the capability to express this in the target is what we'll want longer term.