qml.transforms.merge_rotations

merge_rotations(tape, atol=1e-08, include_gates=None)[source]

Quantum transform to combine rotation gates of the same type that act sequentially.

If the combination of two rotation produces an angle that is close to 0, neither gate will be applied.

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

  • atol (float) – After fusion of gates, if the fused angle \(\theta\) is such that \(|\theta|\leq \text{atol}\), no rotation gate will be applied.

  • include_gates (None or list[str]) – A list of specific operations to merge. If set to None (default), all operations in the ~.pennylane.ops.qubit.attributes.composable_rotations attribute will be merged. Otherwise, only the operations whose names match those in the list will undergo merging.

Returns

The transformed circuit as described in qml.transform.

Return type

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

Example

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

You can apply the transform directly on QNode

@merge_rotations
@qml.qnode(device=dev)
def circuit(x, y, z):
    qml.RX(x, wires=0)
    qml.RX(y, wires=0)
    qml.CNOT(wires=[1, 2])
    qml.RY(y, wires=1)
    qml.Hadamard(wires=2)
    qml.CRZ(z, wires=[2, 0])
    qml.RY(-y, wires=1)
    return qml.expval(qml.Z(0))
>>> circuit(0.1, 0.2, 0.3)
0.9553364891256055

When merging two Rot gates, there are a number of details to consider:

First, the output angles are not always defined uniquely, because Euler angles are not unique for some rotations. merge_rotations makes a particular choice in this case.

Second, merge_rotations is not differentiable everywhere when used on Rot. It has singularities for specific rotation angles where the derivative will be NaN.

Finally, this function can be numerically unstable near singular points. It is therefore recommended to use it with 64-bit floating point precision angles.

For a mathematical derivation of the fusion of two Rot gates, see the documentation of single_qubit_fusion().

You can also apply merge_rotations to a quantum function.

def qfunc(x, y, z):
    qml.RX(x, wires=0)
    qml.RX(y, wires=0)
    qml.CNOT(wires=[1, 2])
    qml.RY(y, wires=1)
    qml.Hadamard(wires=2)
    qml.CRZ(z, wires=[2, 0])
    qml.RY(-y, wires=1)
    return qml.expval(qml.Z(0))

The circuit before optimization:

>>> qnode = qml.QNode(qfunc, dev)
>>> print(qml.draw(qnode)(1, 2, 3))
0: ──RX(1.00)──RX(2.00)─╭RZ(3.00)────────────┤  <Z>
1: ─╭●─────────RY(2.00)─│──────────RY(-2.00)─┤
2: ─╰X─────────H────────╰●───────────────────┤

By inspection, we can combine the two RX rotations on the first qubit. On the second qubit, we have a cumulative angle of 0, and the gates will cancel.

>>> optimized_qfunc = merge_rotations()(qfunc)
>>> optimized_qnode = qml.QNode(optimized_qfunc, dev)
>>> print(qml.draw(optimized_qnode)(1, 2, 3))
0: ──RX(3.00)────╭RZ(3.00)─┤  <Z>
1: ─╭●───────────│─────────┤
2: ─╰X─────────H─╰●────────┤

It is also possible to explicitly specify which rotations merge_rotations should merge using the include_gates argument. For example, if in the above circuit we wanted only to merge the “RX” gates, we could do so as follows:

>>> optimized_qfunc = merge_rotations(include_gates=["RX"])(qfunc)
>>> optimized_qnode = qml.QNode(optimized_qfunc, dev)
>>> print(qml.draw(optimized_qnode)(1, 2, 3))
0: ──RX(3.00)───────────╭RZ(3.00)────────────┤  <Z>
1: ─╭●─────────RY(2.00)─│──────────RY(-2.00)─┤
2: ─╰X─────────H────────╰●───────────────────┤