-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Extend RemoveIdentityEquivalent and CommutativeOptimization with PauliProductRotations
#15810
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
Changes from 6 commits
5d2d7d0
dff5668
49960e1
1730cf3
3080c2b
e0fc630
b36d910
bdbc70d
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 | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3372,6 +3372,45 @@ impl PauliProductRotation { | |||||||||
| )?; | ||||||||||
| Ok(gate.unbind()) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// Attempts to merge `self` and `other`. | ||||||||||
| /// If successful, returns the merged `PauliProductRotation. | ||||||||||
| /// If not successful, returns `None`. | ||||||||||
| pub fn merge_with(&self, other: &Self) -> Option<Self> { | ||||||||||
| if self.x == other.x && self.z == other.z { | ||||||||||
| Some(PauliProductRotation { | ||||||||||
| z: self.z.clone(), | ||||||||||
| x: self.x.clone(), | ||||||||||
| angle: radd_param(self.angle.clone(), other.angle.clone()), | ||||||||||
| }) | ||||||||||
| } else { | ||||||||||
| None | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// For a `PauliProductRotation`` gate with a floating-point angle return a tuple ``(Tr(gate) / dim, dim)``. | ||||||||||
| /// Return `None` if the angle is parameterized. | ||||||||||
|
Collaborator
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.
Suggested change
|
||||||||||
| pub fn rotation_trace_and_dim(&self) -> Option<(Complex64, f64)> { | ||||||||||
| let Param::Float(angle) = self.angle else { | ||||||||||
| return None; | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| let num_qubits = self | ||||||||||
| .z | ||||||||||
| .iter() | ||||||||||
| .zip(self.x.iter()) | ||||||||||
| .filter(|(z, x)| **z || **x) | ||||||||||
| .count(); | ||||||||||
| let dim = 2u32.pow(num_qubits as u32); | ||||||||||
| let tr_over_dim = if num_qubits == 0 { | ||||||||||
| // This is an identity Pauli rotation. | ||||||||||
| (Complex64::new(0.0, -angle / 2.)).exp() | ||||||||||
| } else { | ||||||||||
| Complex64::new((angle / 2.).cos(), 0.) | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| Some((tr_over_dim, dim as f64)) | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| impl PartialEq for PauliProductRotation { | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| features_transpiler: | ||
| - | | ||
| The transpiler passes :class:`.RemoveIdentityEquivalent` and :class:`.CommutativeOptimization` | ||
| have been extended to handle :class:`.PauliProductRotationGate` gates. |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -26,6 +26,7 @@ | |||||
| PhaseGate, | ||||||
| UnitaryGate, | ||||||
| PauliEvolutionGate, | ||||||
| PauliProductRotationGate, | ||||||
| Initialize, | ||||||
| U2Gate, | ||||||
| CZGate, | ||||||
|
|
@@ -43,7 +44,7 @@ | |||||
| ) | ||||||
| from qiskit.circuit.parameter import Parameter | ||||||
| from qiskit.transpiler.passes import CommutativeOptimization | ||||||
| from qiskit.quantum_info import Operator, SparsePauliOp, Clifford | ||||||
| from qiskit.quantum_info import Operator, SparsePauliOp, Clifford, Pauli | ||||||
|
|
||||||
| from test import QiskitTestCase | ||||||
|
|
||||||
|
|
@@ -313,6 +314,48 @@ def test_not_merge_pauli_evolutions(self): | |||||
|
|
||||||
| self.assertEqual(qct, qc) | ||||||
|
|
||||||
| def test_merge_pauli_product_rotations(self): | ||||||
| """Test that the pass merges PauliProductRotationGates.""" | ||||||
|
|
||||||
| qc = QuantumCircuit(4) | ||||||
| qc.append(PauliProductRotationGate(Pauli("XXII"), -1), [0, 1, 2, 3]) | ||||||
| qc.append(PauliProductRotationGate(Pauli("ZZIY"), 1), [0, 1, 2, 3]) | ||||||
|
Collaborator
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. Could you shuffle the indices here to ensure the merging is not dependent on the index order? E.g.
Suggested change
(assuming I got it right here lol)
Member
Author
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. Nice catch, the current PR does not support the suggested merging yet, but I can add it by canonicalizing PauliProductRotations/PauliProductMeasurements to have their qubits sorted by index. But of course this will incur some overhead. If we could assume that the qubits of all PPRs/PPMs in a circuit are sorted, then we could implement commutation checking very efficiently (see the follow-up PR #15815, especially the comment on other potential optimizations). When we canonicalize gates inside the CommutativeOptimization pass, we only do it tentatively, which means that when a gate is not canceled or merged we use the original instance of the gate in the final circuit. Should we perhaps change this behavior to always canonicalize PPRs/PPMs as a side-effect of the CommutativeOptimization pass? Should we do it in a separate pass? Having PPRs/PPMs sorted by index would improve performance when running CommutativeOptimizations multiple times in the optimization loop. What do you think?
Member
Author
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. Update: the time for canonicalizing PauliProductRotations by sorting the qubits (and adjusting the Paulis) is negligible both for small- and large- support gates. So this is now implemented in bdbc70d. |
||||||
| qc.append(PauliProductRotationGate(Pauli("ZZIY"), 1), [0, 1, 2, 3]) | ||||||
| qc.append(PauliProductRotationGate(Pauli("YYXX"), 1), [0, 1, 2, 3]) | ||||||
| qc.append(PauliProductRotationGate(Pauli("XXII"), 1), [0, 1, 2, 3]) | ||||||
|
|
||||||
| # The two "XXII" rotations should cancel. | ||||||
| # The two "ZZIY" rotations should get combined. | ||||||
| qct = CommutativeOptimization()(qc) | ||||||
|
|
||||||
| expected = QuantumCircuit(4) | ||||||
| expected.append(PauliProductRotationGate(Pauli("ZZIY"), 2), [0, 1, 2, 3]) | ||||||
| expected.append(PauliProductRotationGate(Pauli("YYXX"), 1), [0, 1, 2, 3]) | ||||||
|
|
||||||
| self.assertEqual(Operator(expected), Operator(qc)) | ||||||
| self.assertEqual(qct, expected) | ||||||
|
|
||||||
| def test_merge_parameterized_pauli_product_rotations(self): | ||||||
| """Test that the pass merges parameterized PauliProductRotationGates.""" | ||||||
|
|
||||||
| p = Parameter("p") | ||||||
| q = Parameter("q") | ||||||
| r = Parameter("r") | ||||||
|
|
||||||
| qc = QuantumCircuit(4) | ||||||
| qc.append(PauliProductRotationGate(Pauli("YYXX"), p), [0, 1, 2, 3]) | ||||||
| qc.append(PauliProductRotationGate(Pauli("YYXX"), q), [0, 1, 2, 3]) | ||||||
| qc.append(PauliProductRotationGate(Pauli("ZXXX"), r), [0, 1, 2, 3]) | ||||||
| qc.append(PauliProductRotationGate(Pauli("YYXX"), -1), [0, 1, 2, 3]) | ||||||
|
|
||||||
| qct = CommutativeOptimization()(qc) | ||||||
|
|
||||||
| expected = QuantumCircuit(4) | ||||||
| expected.append(PauliProductRotationGate(Pauli("ZXXX"), r), [0, 1, 2, 3]) | ||||||
| expected.append(PauliProductRotationGate(Pauli("YYXX"), p + q - 1), [0, 1, 2, 3]) | ||||||
|
|
||||||
| self.assertEqual(qct, expected) | ||||||
|
|
||||||
| def test_2pi_multiples(self): | ||||||
| """Test 2pi multiples are handled with the correct phase they introduce.""" | ||||||
| for eps in [0, 1e-10, -1e-10]: | ||||||
|
|
||||||
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 quotes are a bit off here and below, also we could use
[...]since it's Rust docs, right?