Improve single-qubit gate count in TwoQubitControlledUDecomposer#16123
Improve single-qubit gate count in TwoQubitControlledUDecomposer#16123ShellyGarion wants to merge 16 commits intoQiskit:mainfrom
TwoQubitControlledUDecomposer#16123Conversation
Coverage Report for CI Build 25432069188Warning Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes. Coverage increased (+0.06%) to 87.627%Details
Uncovered Changes
Coverage Regressions318 previously-covered lines in 9 files lost coverage.
Coverage Stats
💛 - Coveralls |
|
One or more of the following people are relevant to this code:
|
TwoQubitControlledUDecomposerTwoQubitControlledUDecomposer
mtreinish
left a comment
There was a problem hiding this comment.
Overall this looks good, thanks for fixing this. I just had a few comments inline.
| fn to_rxx_gate( | ||
| &self, | ||
| angle: f64, | ||
| ) -> PyResult<(TwoQubitGateSequence, SmallVec<[Matrix2<Complex64>; 4]>)> { |
There was a problem hiding this comment.
Since this is always 4 matrices you don't need the smallvec. Smallvec lets you have a dynamically sized array, like a vec, that is stack allocated up to a specified size and above that size is heap allocated like a normal vec. But we don't need that in this case
| ) -> PyResult<(TwoQubitGateSequence, SmallVec<[Matrix2<Complex64>; 4]>)> { | |
| ) -> PyResult<(TwoQubitGateSequence, [Matrix2<Complex64>; 4])> { |
| gates, | ||
| global_phase, | ||
| }, | ||
| smallvec![k2r, k2l, k1r, k1l], |
There was a problem hiding this comment.
| smallvec![k2r, k2l, k1r, k1l], | |
| [k2r, k2l, k1r, k1l], |
| let (inv_gate_name, inv_gate_params) = invert_1q_gate(gate); | ||
| gates.push((inv_gate_name, inv_gate_params, smallvec![0])); | ||
| } | ||
| let mut gates = Vec::with_capacity(1); |
There was a problem hiding this comment.
I don't think we need the vec here anymore there is only going to be one gate now so allocating a vec is extra overhead we don't need yet. We can just return a (RXXEquivalent, f64, [Matrix2<Complex64>; 4])
| global_phase += weyl_gates.global_phase; | ||
| weyl_gates.global_phase = global_phase; |
There was a problem hiding this comment.
Is there a reason to no just do?
| global_phase += weyl_gates.global_phase; | |
| weyl_gates.global_phase = global_phase; | |
| weyl_gates.global_phase += global_phase; |
| target_decomposed: TwoQubitWeylDecomposition, | ||
| target_decomposed: &TwoQubitWeylDecomposition, | ||
| atol: f64, | ||
| c_mats: SmallVec<[Matrix2<Complex64>; 4]>, |
There was a problem hiding this comment.
Same comment here about the type we don't need the small vec since there are always 4:
| c_mats: SmallVec<[Matrix2<Complex64>; 4]>, | |
| c_mats: [Matrix2<Complex64>; 4], |
| rzz_k2r = rzz_mats[2] | ||
| .try_inverse() | ||
| .expect("matrix k2r is not invertible"); // before RZZ(c), qubit 0 |
There was a problem hiding this comment.
Do you need to preserve the original input here? It doesn't look like it's used anywhere else. If not I would replace this by try_inverse_mut() which will invert the matrix in place and avoid an extra copy.
There was a problem hiding this comment.
Actually we don't need to invert these matrices, since these are the original matrices calculated in to_rxx_gates.
This code is removed now, and the function to_rxx_gates was updated to cover this option of returning the original K matirces in 0d11c70
| let mut target_1q_basis_list = EulerBasisSet::new(); | ||
| target_1q_basis_list.add_basis(self.euler_basis); |
There was a problem hiding this comment.
It's not a huge deal, but it would maybe be better to do this once per decomposer. This is a fixed value based on the euler_basis set when we build the struct so it feels a bit unnecessary to reconstruct the EulerBasisSet for each 1q component of the decomposition.
close #16036
This PR reduces the total number of 1-qubit unitaries needed to synthesize a general 2-qubit unitary using
TwoQubitControlledUDecomposerfrom 24 to 8.This is in particular useful for the peephole optimization in #13419.
Details:
The
TwoQubitControlledUDecomposerconsists of the following steps.We start from a general 4x4 unitary U.
Then we find a cannonical gate W (Weyl gate) s.t.
U(0,1) = c2r(0) c2l(1) W(0,1) c1r(0) c1l(1)this yields 4 1-qubit unitary gates.
Then, we write the Weyl gate W as:
W(0,1) = RXX(0,1) RYY(0,1) RZZ(0,1)Now, we rewrite RYY(0,1) and RZZ(0,1) as:
RYY(0,1) = sdg(0) sdg(0,1) RXX(0,1) s(0,1) s(0,1)RZZ(0,1) = h(0) h(0,1) h(0,1) h(0,1) h(0,1)yielding 8 additional 1-qubit unitary gates.
If V is the target controlled-U gate (which is locally equivalent to RXX), then we rewrite RXX using V:
RXX(0,1) = k2r(0) k2l(1) V(0,1) k1r(0) k1l(1)yielding 3*4 1-qubit unitary gates.
This gives a total of:
3*4 + 8 + 4 = 24 1-qubit unitary gates.
In this PR we multiply the 1-qubit unitary matrices betwen the 2-qubit gates, to get at most 8 1-qubit unitary gates (in the general case where 3 2-qubit gates are needed).
AI/LLM disclosure