-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Add 2q fractional gates to the UnitarySynthesis transpiler pass
#13568
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 27 commits
89ca81e
077ecb9
18b20f6
967da13
3bb10fd
14c7aac
eeff4cd
043a795
b94070a
0a1644b
48fec9b
009d87e
85e2962
c5d0c97
d7b84e9
ea9a2d0
132a44f
b1cb5a0
9cdf2da
1be749d
1495781
231af7f
bb874c1
c2a9ad4
d39c005
106ae4a
9131e3d
6a82649
23b9e6b
5bb55b5
f4da2da
1bd71c5
dd1b38d
55556f8
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 |
|---|---|---|
|
|
@@ -2448,23 +2448,25 @@ pub enum RXXEquivalent { | |
| } | ||
|
|
||
| impl RXXEquivalent { | ||
| fn matrix(&self, py: Python, param: f64) -> PyResult<Array2<Complex64>> { | ||
| fn matrix(&self, param: f64) -> PyResult<Array2<Complex64>> { | ||
| match self { | ||
| Self::Standard(gate) => Ok(gate.matrix(&[Param::Float(param)]).unwrap()), | ||
| Self::CustomPython(gate_cls) => { | ||
| Self::CustomPython(gate_cls) => Python::with_gil(|py: Python| { | ||
| let gate_obj = gate_cls.bind(py).call1((param,))?; | ||
| let raw_matrix = gate_obj | ||
| .call_method0(intern!(py, "to_matrix"))? | ||
| .extract::<PyReadonlyArray2<Complex64>>()?; | ||
| Ok(raw_matrix.as_array().to_owned()) | ||
| } | ||
| }), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[derive(Clone, Debug)] | ||
| #[pyclass(module = "qiskit._accelerate.two_qubit_decompose", subclass)] | ||
| pub struct TwoQubitControlledUDecomposer { | ||
| rxx_equivalent_gate: RXXEquivalent, | ||
| euler_basis: EulerBasis, | ||
| #[pyo3(get)] | ||
| scale: f64, | ||
| } | ||
|
|
@@ -2479,7 +2481,6 @@ impl TwoQubitControlledUDecomposer { | |
| /// invert 2q gate sequence | ||
| fn invert_2q_gate( | ||
| &self, | ||
| py: Python, | ||
| gate: (Option<StandardGate>, SmallVec<[f64; 3]>, SmallVec<[u8; 2]>), | ||
| ) -> PyResult<InverseReturn> { | ||
| let (gate, params, qubits) = gate; | ||
|
|
@@ -2516,7 +2517,7 @@ impl TwoQubitControlledUDecomposer { | |
| .collect::<SmallVec<_>>(); | ||
| Ok((Some(inv_gate.0), inv_gate_params, qubits)) | ||
| } | ||
| RXXEquivalent::CustomPython(gate_cls) => { | ||
| RXXEquivalent::CustomPython(gate_cls) => Python::with_gil(|py: Python| { | ||
| let gate_obj = gate_cls.bind(py).call1(PyTuple::new(py, params)?)?; | ||
| let raw_inverse = gate_obj.call_method0(intern!(py, "inverse"))?; | ||
| let inverse: OperationFromPython = raw_inverse.extract()?; | ||
|
|
@@ -2537,7 +2538,7 @@ impl TwoQubitControlledUDecomposer { | |
| "rxx gate inverse is not valid for this decomposer", | ||
| )) | ||
| } | ||
| } | ||
| }), | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -2550,20 +2551,19 @@ impl TwoQubitControlledUDecomposer { | |
| /// Circuit: Circuit equivalent to an RXXGate. | ||
| /// Raises: | ||
| /// QiskitError: If the circuit is not equivalent to an RXXGate. | ||
| fn to_rxx_gate(&self, py: Python, angle: f64) -> PyResult<TwoQubitGateSequence> { | ||
| fn to_rxx_gate(&self, angle: f64) -> PyResult<TwoQubitGateSequence> { | ||
| // The user-provided RXXGate equivalent gate may be locally equivalent to the RXXGate | ||
| // but with some scaling in the rotation angle. For example, RXXGate(angle) has Weyl | ||
| // parameters (angle, 0, 0) for angle in [0, pi/2] but the user provided gate, i.e. | ||
| // :code:`self.rxx_equivalent_gate(angle)` might produce the Weyl parameters | ||
| // (scale * angle, 0, 0) where scale != 1. This is the case for the CPhaseGate. | ||
|
|
||
| let mat = self.rxx_equivalent_gate.matrix(py, self.scale * angle)?; | ||
| let mat = self.rxx_equivalent_gate.matrix(self.scale * angle)?; | ||
| let decomposer_inv = | ||
| TwoQubitWeylDecomposition::new_inner(mat.view(), Some(DEFAULT_FIDELITY), None)?; | ||
|
|
||
| let euler_basis = EulerBasis::ZYZ; | ||
| let mut target_1q_basis_list = EulerBasisSet::new(); | ||
| target_1q_basis_list.add_basis(euler_basis); | ||
| target_1q_basis_list.add_basis(self.euler_basis); | ||
|
|
||
| // Express the RXXGate in terms of the user-provided RXXGate equivalent gate. | ||
| let mut gates = Vec::with_capacity(13); | ||
|
|
@@ -2600,14 +2600,14 @@ impl TwoQubitControlledUDecomposer { | |
| gates.push((None, smallvec![self.scale * angle], smallvec![0, 1])); | ||
|
|
||
| if let Some(unitary_k1r) = unitary_k1r { | ||
| global_phase += unitary_k1r.global_phase; | ||
| global_phase -= unitary_k1r.global_phase; | ||
| for gate in unitary_k1r.gates.into_iter().rev() { | ||
| let (inv_gate_name, inv_gate_params) = invert_1q_gate(gate); | ||
| gates.push((Some(inv_gate_name), inv_gate_params, smallvec![0])); | ||
| } | ||
| } | ||
| if let Some(unitary_k1l) = unitary_k1l { | ||
| global_phase += unitary_k1l.global_phase; | ||
| global_phase -= unitary_k1l.global_phase; | ||
|
ElePT marked this conversation as resolved.
|
||
| for gate in unitary_k1l.gates.into_iter().rev() { | ||
| let (inv_gate_name, inv_gate_params) = invert_1q_gate(gate); | ||
| gates.push((Some(inv_gate_name), inv_gate_params, smallvec![1])); | ||
|
|
@@ -2623,18 +2623,17 @@ impl TwoQubitControlledUDecomposer { | |
| /// Appends U_d(a, b, c) to the circuit. | ||
| fn weyl_gate( | ||
| &self, | ||
| py: Python, | ||
| circ: &mut TwoQubitGateSequence, | ||
| target_decomposed: TwoQubitWeylDecomposition, | ||
| atol: f64, | ||
| ) -> PyResult<()> { | ||
| let circ_a = self.to_rxx_gate(py, -2.0 * target_decomposed.a)?; | ||
| let circ_a = self.to_rxx_gate(-2.0 * target_decomposed.a)?; | ||
| circ.gates.extend(circ_a.gates); | ||
| let mut global_phase = circ_a.global_phase; | ||
|
|
||
| // translate the RYYGate(b) into a circuit based on the desired Ctrl-U gate. | ||
| if (target_decomposed.b).abs() > atol { | ||
| let circ_b = self.to_rxx_gate(py, -2.0 * target_decomposed.b)?; | ||
| let circ_b = self.to_rxx_gate(-2.0 * target_decomposed.b)?; | ||
| global_phase += circ_b.global_phase; | ||
| circ.gates | ||
| .push((Some(StandardGate::SdgGate), smallvec![], smallvec![0])); | ||
|
|
@@ -2656,7 +2655,7 @@ impl TwoQubitControlledUDecomposer { | |
| // circuit if c < 0. | ||
| let mut gamma = -2.0 * target_decomposed.c; | ||
| if gamma <= 0.0 { | ||
| let circ_c = self.to_rxx_gate(py, gamma)?; | ||
| let circ_c = self.to_rxx_gate(gamma)?; | ||
| global_phase += circ_c.global_phase; | ||
| circ.gates | ||
| .push((Some(StandardGate::HGate), smallvec![], smallvec![0])); | ||
|
|
@@ -2670,15 +2669,15 @@ impl TwoQubitControlledUDecomposer { | |
| } else { | ||
| // invert the circuit above | ||
| gamma *= -1.0; | ||
| let circ_c = self.to_rxx_gate(py, gamma)?; | ||
| let circ_c = self.to_rxx_gate(gamma)?; | ||
| global_phase -= circ_c.global_phase; | ||
| circ.gates | ||
| .push((Some(StandardGate::HGate), smallvec![], smallvec![0])); | ||
| circ.gates | ||
| .push((Some(StandardGate::HGate), smallvec![], smallvec![1])); | ||
| for gate in circ_c.gates.into_iter().rev() { | ||
| let (inv_gate_name, inv_gate_params, inv_gate_qubits) = | ||
| self.invert_2q_gate(py, gate)?; | ||
| self.invert_2q_gate(gate)?; | ||
| circ.gates | ||
| .push((inv_gate_name, inv_gate_params, inv_gate_qubits)); | ||
| } | ||
|
|
@@ -2695,18 +2694,16 @@ impl TwoQubitControlledUDecomposer { | |
|
|
||
| /// Returns the Weyl decomposition in circuit form. | ||
| /// Note: atol is passed to OneQubitEulerDecomposer. | ||
| fn call_inner( | ||
| pub fn call_inner( | ||
| &self, | ||
| py: Python, | ||
| unitary: ArrayView2<Complex64>, | ||
| atol: f64, | ||
| atol: Option<f64>, | ||
| ) -> PyResult<TwoQubitGateSequence> { | ||
| let target_decomposed = | ||
| TwoQubitWeylDecomposition::new_inner(unitary, Some(DEFAULT_FIDELITY), None)?; | ||
|
|
||
| let euler_basis = EulerBasis::ZYZ; | ||
| let mut target_1q_basis_list = EulerBasisSet::new(); | ||
| target_1q_basis_list.add_basis(euler_basis); | ||
| target_1q_basis_list.add_basis(self.euler_basis); | ||
|
|
||
| let c1r = target_decomposed.K1r.view(); | ||
| let c2r = target_decomposed.K2r.view(); | ||
|
|
@@ -2741,17 +2738,17 @@ impl TwoQubitControlledUDecomposer { | |
| gates, | ||
| global_phase, | ||
| }; | ||
| self.weyl_gate(py, &mut gates1, target_decomposed, atol)?; | ||
| self.weyl_gate(&mut gates1, target_decomposed, atol.unwrap_or(DEFAULT_ATOL))?; | ||
| global_phase += gates1.global_phase; | ||
|
|
||
| if let Some(unitary_c1r) = unitary_c1r { | ||
| global_phase -= unitary_c1r.global_phase; | ||
| global_phase += unitary_c1r.global_phase; | ||
| for gate in unitary_c1r.gates.into_iter() { | ||
| gates1.gates.push((Some(gate.0), gate.1, smallvec![0])); | ||
| } | ||
| } | ||
| if let Some(unitary_c1l) = unitary_c1l { | ||
| global_phase -= unitary_c1l.global_phase; | ||
| global_phase += unitary_c1l.global_phase; | ||
|
ElePT marked this conversation as resolved.
|
||
| for gate in unitary_c1l.gates.into_iter() { | ||
| gates1.gates.push((Some(gate.0), gate.1, smallvec![1])); | ||
| } | ||
|
|
@@ -2760,19 +2757,9 @@ impl TwoQubitControlledUDecomposer { | |
| gates1.global_phase = global_phase; | ||
| Ok(gates1) | ||
| } | ||
| } | ||
|
|
||
| #[pymethods] | ||
| impl TwoQubitControlledUDecomposer { | ||
| /// Initialize the KAK decomposition. | ||
| /// Args: | ||
| /// rxx_equivalent_gate: Gate that is locally equivalent to an :class:`.RXXGate`: | ||
| /// :math:`U \sim U_d(\alpha, 0, 0) \sim \text{Ctrl-U}` gate. | ||
| /// Raises: | ||
| /// QiskitError: If the gate is not locally equivalent to an :class:`.RXXGate`. | ||
| #[new] | ||
| #[pyo3(signature=(rxx_equivalent_gate))] | ||
| pub fn new(py: Python, rxx_equivalent_gate: RXXEquivalent) -> PyResult<Self> { | ||
| /// Initialize the KAK decomposition. | ||
| pub fn new_inner(rxx_equivalent_gate: RXXEquivalent, euler_basis: &str) -> PyResult<Self> { | ||
|
ElePT marked this conversation as resolved.
|
||
| let atol = DEFAULT_ATOL; | ||
|
Comment on lines
+2747
to
2824
Contributor
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. I know we discussed the addition of a
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. the
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.
Contributor
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. Yes, let's keep them. |
||
| let test_angles = [0.2, 0.3, PI2]; | ||
|
|
||
|
|
@@ -2788,14 +2775,17 @@ impl TwoQubitControlledUDecomposer { | |
| } | ||
| } | ||
| RXXEquivalent::CustomPython(gate_cls) => { | ||
| if gate_cls.bind(py).call1((test_angle,)).ok().is_none() { | ||
| let takes_param = Python::with_gil(|py: Python| { | ||
| gate_cls.bind(py).call1((test_angle,)).ok().is_none() | ||
| }); | ||
| if takes_param { | ||
| return Err(QiskitError::new_err( | ||
| "Equivalent gate needs to take exactly 1 angle parameter.", | ||
| )); | ||
| } | ||
| } | ||
| }; | ||
| let mat = rxx_equivalent_gate.matrix(py, test_angle)?; | ||
| let mat = rxx_equivalent_gate.matrix(test_angle)?; | ||
| let decomp = | ||
| TwoQubitWeylDecomposition::new_inner(mat.view(), Some(DEFAULT_FIDELITY), None)?; | ||
| let mat_rxx = StandardGate::RXXGate | ||
|
|
@@ -2836,17 +2826,35 @@ impl TwoQubitControlledUDecomposer { | |
| Ok(TwoQubitControlledUDecomposer { | ||
| scale, | ||
| rxx_equivalent_gate, | ||
| euler_basis: EulerBasis::__new__(euler_basis)?, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| #[pymethods] | ||
| impl TwoQubitControlledUDecomposer { | ||
| /// Initialize the KAK decomposition. | ||
| /// Args: | ||
| /// rxx_equivalent_gate: Gate that is locally equivalent to an :class:`.RXXGate`: | ||
| /// :math:`U \sim U_d(\alpha, 0, 0) \sim \text{Ctrl-U}` gate. | ||
| /// euler_basis: Basis string to be provided to :class:`.OneQubitEulerDecomposer` | ||
| /// for 1Q synthesis. | ||
| /// Raises: | ||
| /// QiskitError: If the gate is not locally equivalent to an :class:`.RXXGate`. | ||
| #[new] | ||
| #[pyo3(signature=(rxx_equivalent_gate, euler_basis="ZXZ"))] | ||
| pub fn new(rxx_equivalent_gate: RXXEquivalent, euler_basis: &str) -> PyResult<Self> { | ||
| TwoQubitControlledUDecomposer::new_inner(rxx_equivalent_gate, euler_basis) | ||
| } | ||
|
|
||
| #[pyo3(signature=(unitary, atol))] | ||
| #[pyo3(signature=(unitary, atol=None))] | ||
| fn __call__( | ||
| &self, | ||
| py: Python, | ||
| unitary: PyReadonlyArray2<Complex64>, | ||
| atol: f64, | ||
| atol: Option<f64>, | ||
| ) -> PyResult<CircuitData> { | ||
| let sequence = self.call_inner(py, unitary.as_array(), atol)?; | ||
| let sequence = self.call_inner(unitary.as_array(), atol)?; | ||
| match &self.rxx_equivalent_gate { | ||
| RXXEquivalent::Standard(rxx_gate) => CircuitData::from_standard_gates( | ||
| py, | ||
|
|
||
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 this changes since longer term we'll need to do this when running this in a parallel context. But I'm curious what prompted these changes?
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.
See the discussion here: #13568 (comment)
I wanted to have a purely-rust
new_innerfunction, so I copied the solution from your PR :)