-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Directly use PackedOperation for two qubit decomposers #14650
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
Conversation
This commit reworks the rust code for the two qubit decomposers to natively work with PackedOperation instead of doing `Option<StandardGate>` to represent either a StadandardGate::CX gate for cx based decomposition and `None` for any other gate used for the 2q target. Prior to having a more complete circuit data model defined in rust the two qubit decomposers were written using string identifiers where 1q gates were represented by their names and 2q gates were either CX or a sentinel value USER_GATE. This was because the TwoQubitBasisDecomposer has a custom code path that is CX only, but the other KAK gates used in the decomposer are generic. The USER_GATE string was used to tell the python code which was constructing the circuit from a gate sequence to use the gate instance stored in the python class instead of something hard coded. This enabled standard gates and custom gates. However, since the two qubit decomposers were originally ported to Rust the Rust side of Qiskit has grown significantly and we can now natively build the circuit fully in Rust and represent all the operations. The model used in the decomposers was then expanded to work with Rust native gate representations but the same pattern using the sentinel value and custom operation reconstruction in Python was still used. This mismatch has caused confusion like as reported in Qiskit#14418 but also hinders the usage of the decomposers in other rust callers. For example, looking at implementing Qiskit#12213 we will need to work with the two qubit decomposer directly in rust for the qsd implementation and working with gates natively in rust will be necessary. This commit migrates the custom sequene type used for the two qubit decomposer to work natively with a PackedOperation and store all operations used in the sequence. We can consider removing the custom sequence type once the last python usage from CircuitData is removed and we don't need the py token for interacting with global phase. It's slighly heavier weight than the custom sequence type, but we are double allocating and it will be simpler if everything is natively in CircuitData eventually. But for this commit it was better to keep the sequence type around and simplify it so the conversion is more straightforward. Fixes Qiskit#14418
One or more of the following people are relevant to this code:
|
Pull Request Test Coverage Report for Build 16292604447Details
💛 - Coveralls |
aac39da
to
6a408f2
Compare
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.
Thank you @mtreinish. These changes look great. I have tried to leave some minor comments wherever possible.
2, | ||
0, |
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 unsure as to whether these would be considered magic numbers and could use some associated variable names (at least in this file, rather than in two_qubit_decompose.rs
) or if they are already considered self-explanatory. I'll leave it up to your judgement @mtreinish.
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 would consider these self explanatory because the from_packed_operation
function's arguments document these as num_qubits
and num_clbits
for the circuit. So we're just passing the size of the circuit to the generator function. We're creating a 2 qubit 0 clbit circuit with this function call.
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 code looks good to me 🙂. Thank you @mtreinish.
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.
Adding just a couple of comments (and acknowledgement that this will be a fun merge for me with the control flow PR lol).
@@ -1268,13 +1264,11 @@ impl TwoQubitWeylDecomposition { | |||
} | |||
} | |||
|
|||
type TwoQubitSequenceVec = Vec<(Option<StandardGate>, SmallVec<[f64; 3]>, SmallVec<[u8; 2]>)>; | |||
type TwoQubitSequenceVec = Vec<(PackedOperation, SmallVec<[f64; 3]>, SmallVec<[u8; 2]>)>; |
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.
Beyond the scope of this PR, but generally I think we should get out of the habit of having these alias types. It seems like it leads to an extra layer of indirection when trying to understand the code. I'd rather see a struct type with named fields for the element (PackedOperation, SmallVec<[f64; 3]>, SmallVec<[u8; 2]>)
and then the Vec
be inlined where applicable.
Co-authored-by: Kevin Hartman <kevin@hart.mn>
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.
Before I finish reviewing, I think that PackedOperation
isn't an ideal type for us here since it represents more than just a gate-like operation.
From the comment in this review pass, perhaps should we consider a new Gate
enum to represent this:
enum Gate {
Standard(StandardGate),
Python(PyGate),
Unitary(Unitary),
}
If you opt to do that, we could keep it local to this pass for now, but it might be a useful type to live somewhere in operations.rs
at some point.
I wouldn't suggest this if you weren't already dealing directly with operations and f64
parameter vectors (as opposed to working with &PackedInstruction
s).
} | ||
OperationRef::Gate(gate) => gate.gate.clone_ref(py), | ||
OperationRef::Unitary(unitary) => unitary.create_py_op(py, None)?.into_any(), | ||
_ => unreachable!("decomposer gate must be a gate"), |
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.
Despite the name of the PR, I think this is a good indicator that PackedOperation
isn't the best type for us to use as TwoQubitDecomposer::gate
. I think the right way to do this from a typing perspective would be to define a new enum that can be exactly one of the things we actually support, e.g.
enum Gate {
Standard(StandardGate),
Python(PyGate),
Unitary(Unitary),
}
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 get where you're coming from here but I disagree. The point of this PR is to move all the processing to rust and move this closer to CircuitData
with the goal of standardizing on that medium term. We're not at the point where that's feasible yet because of ParameterExpression
but medium term this will all be replaced. Using a PackedOperation
here will make that easier in the next steps.
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 using PackedOperation
if it'll make things easier for us in the future. Though, I'm not sure I understand what you meant exactly by "move this closer to CircuitData
". Could you elaborate?
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.
Just what you said elsewhere, this is a step closer to using PackedInstruction
. The eventual goal is to remove these types and just work natively with CircuitData
instead of a custom vec type.
Ideally I'd like this to just return an iterator of PackedInstruction
s so we can collect it into either a CircuitData
or DAGCircuit
depending on the context calling this. But in the short term this is an incremental improvement working towards that.
} | ||
OperationRef::Gate(gate) => gate.gate.clone_ref(py), | ||
OperationRef::Unitary(unitary) => unitary.create_py_op(py, None)?.into_any(), | ||
_ => unreachable!("decomposer gate must be a gate"), |
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 using PackedOperation
if it'll make things easier for us in the future. Though, I'm not sure I understand what you meant exactly by "move this closer to CircuitData
". Could you elaborate?
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.
Looks good, thanks for the changes!
Summary
This commit reworks the rust code for the two qubit decomposers to natively work with PackedOperation instead of doing
Option<StandardGate>
to represent either a StadandardGate::CX gate for cx based decomposition andNone
for any other gate used for the 2q target. Prior to having a more complete circuit data model defined in rust the two qubit decomposers were written using string identifiers where 1q gates were represented by their names and 2q gates were either CX or a sentinel value USER_GATE. This was because the TwoQubitBasisDecomposer has a custom code path that is CX only, but the other KAK gates used in the decomposer are generic. The USER_GATE string was used to tell the python code which was constructing the circuit from a gate sequence to use the gate instance stored in the python class instead of something hard coded. This enabled standard gates and custom gates.However, since the two qubit decomposers were originally ported to Rust the Rust side of Qiskit has grown significantly and we can now natively build the circuit fully in Rust and represent all the operations. The model used in the decomposers was then expanded to work with Rust native gate representations but the same pattern using the sentinel value and custom operation reconstruction in Python was still used. This mismatch has caused confusion like as reported in #14418 but also hinders the usage of the decomposers in other rust callers. For example, looking at implementing #12213 we will need to work with the two qubit decomposer directly in rust for the qsd implementation and working with gates natively in rust will be necessary.
This commit migrates the custom sequence type used for the two qubit decomposer to work natively with a PackedOperation and store all operations used in the sequence. We can consider removing the custom sequence type once the last python usage from CircuitData is removed and we don't need the py token for interacting with global phase. It's slighly heavier weight than the custom sequence type, but we are double allocating and it will be simpler if everything is natively in CircuitData eventually. But for this commit it was better to keep the sequence type around and simplify it so the conversion is more straightforward.
Details and comments
Fixes #14418