qml.transforms.single_qubit_fusion

single_qubit_fusion(tape, atol=1e-08, exclude_gates=None)[source]

Quantum function transform to fuse together groups of single-qubit operations into a general single-qubit unitary operation (Rot).

Fusion is performed only between gates that implement the property single_qubit_rot_angles. Any sequence of two or more single-qubit gates (on the same qubit) with that property defined will be fused into one Rot.

Parameters
  • tape (QNode or QuantumTape or Callable) – A quantum circuit.

  • atol (float) – An absolute tolerance for which to apply a rotation after fusion. After fusion of gates, if the fused angles \(\theta\) are such that \(|\theta|\leq \text{atol}\), no rotation gate will be applied.

  • exclude_gates (None or list[str]) – A list of gates that should be excluded from full fusion. If set to None, all single-qubit gates that can be fused will be fused.

Returns

The transformed circuit as described in qml.transform.

Return type

qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], Callable]

Example

>>> dev = qml.device('default.qubit', wires=1)

You can apply the transform directly on QNode:

@qml.transforms.single_qubit_fusion
@qml.qnode(device=dev)
def qfunc(r1, r2):
    qml.Hadamard(wires=0)
    qml.Rot(*r1, wires=0)
    qml.Rot(*r2, wires=0)
    qml.RZ(r1[0], wires=0)
    qml.RZ(r2[0], wires=0)
    return qml.expval(qml.X(0))

The single qubit gates are fused before execution.

Note

The fused angles between two sets of rotation angles are not always defined uniquely because Euler angles are not unique for some rotations. single_qubit_fusion makes a particular choice in this case.

Warning

This function is not differentiable everywhere. It has singularities for specific input rotation angles, where the derivative will be NaN.

Warning

This function is numerically unstable at its singular points. It is recommended to use it with 64-bit floating point precision.

Consider the following quantum function.

def qfunc(r1, r2):
    qml.Hadamard(wires=0)
    qml.Rot(*r1, wires=0)
    qml.Rot(*r2, wires=0)
    qml.RZ(r1[0], wires=0)
    qml.RZ(r2[0], wires=0)
    return qml.expval(qml.X(0))

The circuit before optimization:

>>> qnode = qml.QNode(qfunc, dev)
>>> print(qml.draw(qnode)([0.1, 0.2, 0.3], [0.4, 0.5, 0.6]))
0: ──H──Rot(0.1, 0.2, 0.3)──Rot(0.4, 0.5, 0.6)──RZ(0.1)──RZ(0.4)──┤ ⟨X⟩

Full single-qubit gate fusion allows us to collapse this entire sequence into a single qml.Rot rotation gate.

>>> optimized_qfunc = qml.transforms.single_qubit_fusion(qfunc)
>>> optimized_qnode = qml.QNode(optimized_qfunc, dev)
>>> print(qml.draw(optimized_qnode)([0.1, 0.2, 0.3], [0.4, 0.5, 0.6]))
0: ──Rot(3.57, 2.09, 2.05)──┤ ⟨X⟩

The matrix for an individual rotation is given by

\[\begin{split}R(\phi_j,\theta_j,\omega_j) &= \begin{bmatrix} e^{-i(\phi_j+\omega_j)/2}\cos(\theta_j/2) & -e^{i(\phi_j-\omega_j)/2}\sin(\theta_j/2)\\ e^{-i(\phi_j-\omega_j)/2}\sin(\theta_j/2) & e^{i(\phi_j+\omega_j)/2}\cos(\theta_j/2) \end{bmatrix}\\ &= \begin{bmatrix} e^{-i\alpha_j}c_j & -e^{i\beta_j}s_j \\ e^{-i\beta_j}s_j & e^{i\alpha_j}c_j \end{bmatrix},\end{split}\]

where we introduced abbreviations \(\alpha_j,\beta_j=\frac{\phi_j\pm\omega_j}{2}\), \(c_j=\cos(\theta_j / 2)\) and \(s_j=\sin(\theta_j / 2)\) for notational brevity. The upper left entry of the matrix product \(R(\phi_2,\theta_2,\omega_2)R(\phi_1,\theta_1,\omega_1)\) reads

\[x = e^{-i(\alpha_2+\alpha_1)} c_2 c_1 - e^{i(\beta_2-\beta_1)} s_2 s_1\]

and should equal \(e^{-i\alpha_f}c_f\) for the fused rotation angles. This means that we can obtain \(\theta_f\) from the magnitude of the matrix product entry above, choosing \(c_f=\cos(\theta_f / 2)\) to be non-negative:

\[\begin{split}c_f = |x| &= \left| e^{-i(\alpha_2+\alpha_1)} c_2 c_1 -e^{i(\beta_2-\beta_1)} s_2 s_1 \right| \\ &= \sqrt{c_1^2 c_2^2 + s_1^2 s_2^2 - 2 c_1 c_2 s_1 s_2 \cos(\omega_1 + \phi_2)}.\end{split}\]

Now we again make a choice and pick \(\theta_f\) to be non-negative:

\[\theta_f = 2\arccos(|x|).\]

We can also extract the angle combination \(\alpha_f\) from \(x\) via \(\operatorname{arg}(x)\), which can be readily computed with \(\arctan\):

\[\alpha_f = -\arctan\left( \frac{-c_1c_2\sin(\alpha_1+\alpha_2)-s_1s_2\sin(\beta_2-\beta_1)} {c_1c_2\cos(\alpha_1+\alpha_2)-s_1s_2\cos(\beta_2-\beta_1)} \right).\]

We can use the standard numerical function arctan2, which computes \(\arctan(x_1/x_2)\) from \(x_1\) and \(x_2\) while handling special points suitably, to obtain the argument of the underlying complex number \(x_2 + x_1 i\).

Finally, to obtain \(\beta_f\), we need a second element of the matrix product from above. We compute the lower-left entry to be

\[y = e^{-i(\beta_2+\alpha_1)} s_2 c_1 + e^{i(\alpha_2-\beta_1)} c_2 s_1,\]

which should equal \(e^{-i \beta_f}s_f\). From this, we can compute

\[\beta_f = -\arctan\left( \frac{-c_1s_2\sin(\alpha_1+\beta_2)+s_1c_2\sin(\alpha_2-\beta_1)} {c_1s_2\cos(\alpha_1+\beta_2)+s_1c_2\cos(\alpha_2-\beta_1)} \right).\]

From this, we may extract

\[\phi_f = \alpha_f + \beta_f\qquad \omega_f = \alpha_f - \beta_f\]

and are done.

Special cases:

There are a number of special cases for which we can skip the computation above and can combine rotation angles directly.

  1. If \(\omega_1=\phi_2=0\), we can simply merge the RY rotation angles \(\theta_j\) and obtain \((\phi_1, \theta_1+\theta_2, \omega_2)\).

  2. If \(\theta_j=0\), we can merge the two RZ rotations of the same Rot and obtain \((\phi_1+\omega_1+\phi_2, \theta_2, \omega_2)\) or \((\phi_1, \theta_1, \omega_1+\phi_2+\omega_2)\). If both RY angles vanish we get \((\phi_1+\omega_1+\phi_2+\omega_2, 0, 0)\).

Note that this optimization is not performed for differentiable input parameters, in order to maintain differentiability.

Mathematical properties:

All functions above are well-defined on the domain we are using them on, if we handle \(\arctan\) via standard numerical implementations such as np.arctan2. Based on the choices we made in the derivation above, the fused angles will lie in the intervals

\[\phi_f, \omega_f \in [-\pi, \pi],\quad \theta_f \in [0, \pi].\]

Close to the boundaries of these intervals, single_qubit_fusion exhibits discontinuities, depending on the combination of input angles. These discontinuities also lead to singular (non-differentiable) points as discussed below.

Differentiability:

The function derived above is differentiable almost everywhere. In particular, there are two problematic scenarios at which the derivative is not defined. First, the square root is not differentiable at \(0\), making all input angles with \(|x|=0\) singular. Second, \(\arccos\) is not differentiable at \(1\), making all input angles with \(|x|=1\) singular.