qml.transforms.unitary_to_rot

unitary_to_rot(tape)[source]

Quantum function transform to decomposes all instances of single-qubit and select instances of two-qubit QubitUnitary operations to parametrized single-qubit operations.

Single-qubit gates will be converted to a sequence of Y and Z rotations in the form \(RZ(\omega) RY(\theta) RZ(\phi)\) that implements the original operation up to a global phase. Two-qubit gates will be decomposed according to the pennylane.transforms.two_qubit_decomposition() function.

Warning

This transform is not fully differentiable for 2-qubit QubitUnitary operations. See usage details below.

Parameters

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

Returns

The transformed circuit as described in qml.transform.

Return type

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

Example

Suppose we would like to apply the following unitary operation:

U = np.array([
    [-0.17111489+0.58564875j, -0.69352236-0.38309524j],
    [ 0.25053735+0.75164238j,  0.60700543-0.06171855j]
])

The unitary_to_rot transform enables us to decompose such numerical operations while preserving differentiability.

def qfunc():
    qml.QubitUnitary(U, wires=0)
    return qml.expval(qml.Z(0))

The original circuit is:

>>> dev = qml.device('default.qubit', wires=1)
>>> qnode = qml.QNode(qfunc, dev)
>>> print(qml.draw(qnode)())
0: ──U(M0)─┤  <Z>
M0 =
[[-0.17111489+0.58564875j -0.69352236-0.38309524j]
[ 0.25053735+0.75164238j  0.60700543-0.06171855j]]

We can use the transform to decompose the gate:

>>> transformed_qfunc = unitary_to_rot(qfunc)
>>> transformed_qnode = qml.QNode(transformed_qfunc, dev)
>>> print(qml.draw(transformed_qnode)())
0: ──RZ(-1.35)──RY(1.83)──RZ(-0.61)─┤  <Z>

This decomposition is not fully differentiable. We can differentiate with respect to input QNode parameters when they are not used to explicitly construct a \(4 \times 4\) unitary matrix being decomposed. So for example, the following will work:

U = scipy.stats.unitary_group.rvs(4)

def circuit(angles):
    qml.QubitUnitary(U, wires=["a", "b"])
    qml.RX(angles[0], wires="a")
    qml.RY(angles[1], wires="b")
    qml.CNOT(wires=["b", "a"])
    return qml.expval(qml.Z("a"))

dev = qml.device('default.qubit', wires=["a", "b"])
transformed_qfunc = qml.transforms.unitary_to_rot(circuit)
transformed_qnode = qml.QNode(transformed_qfunc, dev)
>>> g = qml.grad(transformed_qnode)
>>> params = np.array([0.2, 0.3], requires_grad=True)
>>> g(params)
array([ 0.00296633, -0.29392145])

However, the following example will not be differentiable:

def circuit(angles):
    z = angles[0]
    x = angles[1]

    Z_mat = np.array([[np.exp(-1j * z / 2), 0.0], [0.0, np.exp(1j * z / 2)]])

    c = np.cos(x / 2)
    s = np.sin(x / 2) * 1j
    X_mat = np.array([[c, -s], [-s, c]])

    U = np.kron(Z_mat, X_mat)

    qml.Hadamard(wires="a")

    # U depends on the input parameters
    qml.QubitUnitary(U, wires=["a", "b"])

    qml.CNOT(wires=["b", "a"])
    return qml.expval(qml.X("a"))