-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Port control-flow operations to Rust. #14568
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
base: main
Are you sure you want to change the base?
Conversation
This works for everything except control flow.
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 was snooping around the code just to take a look at what's up ahead. No big suggestions here, if anything just questions.
#[derive(Clone, Debug)] | ||
pub enum InstructionView<'a, T> { | ||
ControlFlow(ControlFlowView<'a, T>), | ||
StandardGate(StandardGateView<'a>), | ||
StandardInstruction(StandardInstructionView<'a>), | ||
Gate(&'a PyGate), | ||
Instruction(&'a PyInstruction), | ||
Operation(&'a PyOperation), | ||
Unitary(UnitaryGateView<'a>), | ||
} |
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.
What's the difference between this and OperationRef
? I assume that this one having a specific lifetime attached to it at all times allows something that OperationRef
doesn't.
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.
The difference is pretty much that this represents an operation alongside its parameters, whereas an OperationRef
is just the operation. All of these variants have references to the data they need from their parameter list baked in.
It's called InstructionView
instead of InstructionRef
because it's a heavier-weight thing—the variants here typically hold references to more than just one thing, e.g. ControlfFlowView::IfElse
holds references to its condition (which happens to come from the underlying operation) and to its true and false blocks.
} | ||
|
||
#[derive(Clone, Debug)] | ||
pub struct UnitaryGateView<'a>(pub &'a UnitaryGate); |
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.
If this already Deref
s into a UnitaryGate
, meaning that it inherits the methods, is there any need for the inner part to still be pub
?
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.
The UnitaryGateView
in its current form is mostly just here as an example, and isn't really something that needs to be present in the final version of this PR (we can put a UnitaryGate
ref here instead).
When we added native support for UnitaryGate
, we ended up putting its parameter (the matrix) directly into the operation to avoid needing another variant on Param
that would seldom be applicable. But with the changes in this PR, we could potentially move the matrix to Parameters
and still have the same nice interface via UnitaryGateView
.
/// The parameter list of an instruction. | ||
#[derive(Clone, Debug)] | ||
pub enum Parameters<T> { | ||
Params(SmallVec<[Param; 3]>), | ||
Box { | ||
body: T, | ||
}, | ||
ForLoop { | ||
indexset: Vec<usize>, | ||
loop_param: Option<PyObject>, | ||
body: T, | ||
}, | ||
IfElse { | ||
true_body: T, | ||
false_body: Option<T>, | ||
}, | ||
Switch { | ||
cases: Vec<T>, | ||
}, | ||
While { | ||
body: T, | ||
}, | ||
} |
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.
Is this going to stay like this once #14207 merges?
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.
Yeah, this shouldn't need to change. These are different from ParameterExpression
and the Python-side Parameter
(which inherits from ParameterExpression
).
This is more like an enumeration of the various parameter lists that we support for a given instruction.
@@ -688,7 +733,7 @@ pub struct PackedInstruction { | |||
pub qubits: Interned<[Qubit]>, | |||
/// The index under which the interner has stored `clbits`. | |||
pub clbits: Interned<[Clbit]>, | |||
pub params: Option<Box<SmallVec<[Param; 3]>>>, | |||
pub params: Option<Box<Parameters<PyObject>>>, |
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 assuming that PyObject
is a placeholder until #14207 has merged
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.
It is indeed a placeholder, but this is for the block type of control flow operations. So eventually, this would be Parameters<CircuitData>
(once a CircuitData
can losslessly represent a control flow block).
Summary
Ports control flow operations to Rust with automatic conversion of blocks into and out of
DAGCircuit
.Control flow instructions can be accessed by calling a
view
method of an instruction type, which provides the following view:The block type
T
is dependent on the source of the instruction, namely whether it's aPackedInstruction
or aDAGInstruction
(new).Details and comments
There are a lot of changes here in support of this. The big ones are:
Parameters
enum type to represent parameter lists specific to the operation in question. Previously, we used duck-typing per-parameter, which has been a pain-point for us. TheParam
type still exists for use with gates, but we should be able to remove theObj
catch-all type from it so that it's purely numeric.DAGCircuit
now uses its own instruction typeDAGInstruction
instead ofPackedInstruction
. This is to accommodate the storage requirements of DAG instructions storing their blocks asDAGCircuit
s, but it seems likely we'll want custom storage for DAG instructions in the future anyways.Instruction
trait which represents something that is equivalent/convertible to our Python-space instruction. It is implemented forCircuitInstruction
,PackedInstruction
,OperationFromPython
andNormalOperation
. Thecircuit_instruction
mod provides a blanket implementation ofcreate_py_op
for all of these via theCreatePythonOperation
trait, which sits next toFromPythonOperation
.OperationRef
, there is now anInstructionView
enum that provides an ergonomic immutable view of our instruction types. The benefit of this is obvious for control flow (shown above), but it's also the thing that will allow us to get rid of duck-typing for gates. The traitIntoInstructionView
is implemented for all of our (&)instruction types, and provides all of these views.matrix
anddefinition
have been moved out ofOperation
and thus no longer need the duck-typedparams
to be threaded in.To-do
DAGCircuit::compose
's use of control flow to Rust until OxidizeVariableMapper
and uses. #14361 merges. I also need this to merge before I can implement semantic equality forCondition
andTarget
(I believe this is responsible for the majority of the still-failing tests).TODO
s scattered throughout.