Skip to content

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

Draft
wants to merge 54 commits into
base: main
Choose a base branch
from

Conversation

kevinhartman
Copy link
Contributor

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:

pub enum ControlFlowView<'a, T> {
    Box(Option<&'a Duration>, &'a T),
    BreakLoop,
    ContinueLoop,
    ForLoop {
        indexset: &'a [usize],
        loop_param: Option<&'a PyObject>,
        body: &'a T,
    },
    IfElse {
        condition: &'a Condition,
        true_body: &'a T,
        false_body: Option<&'a T>,
    },
    Switch {
        target: &'a Target,
        cases_specifier: Vec<(&'a Vec<CaseSpecifier>, &'a T)>,
    },
    While {
        condition: &'a Condition,
        body: &'a T,
    },
}

The block type T is dependent on the source of the instruction, namely whether it's a PackedInstruction or a DAGInstruction (new).

Details and comments

There are a lot of changes here in support of this. The big ones are:

  • A new 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. The Param type still exists for use with gates, but we should be able to remove the Obj catch-all type from it so that it's purely numeric.
  • The DAGCircuit now uses its own instruction type DAGInstruction instead of PackedInstruction. This is to accommodate the storage requirements of DAG instructions storing their blocks as DAGCircuits, but it seems likely we'll want custom storage for DAG instructions in the future anyways.
  • There's now an Instruction trait which represents something that is equivalent/convertible to our Python-space instruction. It is implemented for CircuitInstruction, PackedInstruction, OperationFromPython and NormalOperation. The circuit_instruction mod provides a blanket implementation of create_py_op for all of these via the CreatePythonOperation trait, which sits next to FromPythonOperation.
  • Similar to OperationRef, there is now an InstructionView 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 trait IntoInstructionView is implemented for all of our (&)instruction types, and provides all of these views.
  • Methods like matrix and definition have been moved out of Operation and thus no longer need the duck-typed params to be threaded in.

To-do

  • I can't port DAGCircuit::compose's use of control flow to Rust until Oxidize VariableMapper and uses. #14361 merges. I also need this to merge before I can implement semantic equality for Condition and Target (I believe this is responsible for the majority of the still-failing tests).
  • Add more context to this description to cover alternatives considered. This probably isn't the simplest approach we could take implementation-wise, but it seemed like the right direction to me and I'd like others to have that context before reading a 4K diff.
  • Address clean-up TODOs scattered throughout.

Copy link
Contributor

@raynelfss raynelfss left a 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.

Comment on lines +279 to +288
#[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>),
}
Copy link
Contributor

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.

Copy link
Contributor Author

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this already Derefs into a UnitaryGate, meaning that it inherits the methods, is there any need for the inner part to still be pub?

Copy link
Contributor Author

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.

Comment on lines +77 to +99
/// 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,
},
}
Copy link
Contributor

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?

Copy link
Contributor Author

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>>>,
Copy link
Contributor

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

Copy link
Contributor Author

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants