qml.transforms.diagonalize_measurements

diagonalize_measurements(tape, supported_base_obs=(<class 'pennylane.ops.qubit.non_parametric_ops.PauliZ'>, <class 'pennylane.ops.identity.Identity'>), to_eigvals=False)[source]

Diagonalize a set of measurements into the standard basis. Raises an error if the measurements do not commute.

See the usage details for more information on which measurements are supported.

Parameters
  • tape (QNode or QuantumScript or Callable) – The quantum circuit to modify the measurements of.

  • supported_base_obs (Optional, Iterable(Observable)) – A list of supported base observable classes. Allowed observables are qml.X, qml.Y, qml.Z, qml.Hadamard and qml.Identity. Z and Identity are always treated as supported, regardless of input. If no list is provided, the transform will diagonalize everything into the Z basis. If a list is provided, only unsupported observables will be diagonalized to the Z basis.

Returns

The transformed circuit as described in qml.transform.

Return type

qnode (QNode) or tuple[List[QuantumScript], function]

Note

An error will be raised if non-commuting terms are encountered. To avoid non-commuting terms in circuit measurements, the split_non_commuting transform can be applied.

This transform will diagonalize what it can, i.e., qml.X, qml.Y, qml.Z, qml.Hadamard, qml.Identity, or a linear combination of them. Any unrecognized observable will not raise an error, deferring to the device’s validation for supported measurements later on. Lastly, if diagonalize_measurements produces additional gates that the device does not support, the decompose() transform should be applied to ensure that the additional gates are decomposed to those that the device supports.

Examples:

This transform allows us to transform QNode measurements into the measurement basis by adding the relevant diagonalizing gates to the end of the tape operations.

from pennylane.transforms import diagonalize_measurements

dev = qml.device("default.qubit")

@diagonalize_measurements
@qml.qnode(dev)
def circuit(x):
    qml.RY(x[0], wires=0)
    qml.RX(x[1], wires=1)
    return qml.expval(qml.X(0) @ qml.Z(1)), qml.var(0.5 * qml.Y(2) + qml.X(0))

Applying the transform appends the relevant gates to the end of the circuit to allow measurements to be in the Z basis, so the original circuit

>>> print(qml.draw(circuit, level=0)([np.pi/4, np.pi/4]))
0: ──RY(0.79)─┤ ╭<X@Z> ╭Var[𝓗(0.50)]
1: ──RX(0.79)─┤ ╰<X@Z> │
2: ───────────┤        ╰Var[𝓗(0.50)]

becomes

>>> print(qml.draw(circuit)([np.pi/4, np.pi/4]))
0: ──RY(0.79)──H────┤ ╭<Z@Z> ╭Var[𝓗(0.50)]
1: ──RX(0.79)───────┤ ╰<Z@Z> │
2: ──Z─────────S──H─┤        ╰Var[𝓗(0.50)]
>>> circuit([np.pi/4, np.pi/4])
(0.5, 0.75)

The transform diagonalizes observables from the local Pauli basis only, i.e. it diagonalizes X, Y, Z, and Hadamard. Any other observable will be unaffected:

measurements = [
    qml.expval(qml.X(0) + qml.Hermitian([[1, 0], [0, 1]], wires=[1]))
]
tape = qml.tape.QuantumScript(measurements=measurements)
tapes, processsing_fn = diagonalize_measurements(tape)
>>> tapes[0].operations
[H(0)]
>>> tapes[0].measurements
[expval(Z(0) + Hermitian(array([[1, 0], [0, 1]]), wires=[1]))]

The transform can also diagonalize only a subset of these operators. By default, the only supported base observable is Z. What if a backend device can handle X, Y and Z, but doesn’t provide support for Hadamard? We can set this by passing supported_base_obs to the transform. Let’s create a tape with some measurements:

measurements = [
    qml.expval(qml.X(0) + qml.Hadamard(1)),
    qml.expval(qml.X(0) + 0.2 * qml.Hadamard(1)),
    qml.var(qml.Y(2) + qml.X(0)),
]
tape = qml.tape.QuantumScript(measurements=measurements)
tapes, processing_fn = diagonalize_measurements(
    tape,
    supported_base_obs=[qml.X, qml.Y, qml.Z]
)

Now tapes is a tuple containing a single tape with the updated measurements, where only the Hadamard gate has been diagonalized:

>>> tapes[0].measurements
[expval(X(0) + Z(1)), expval(X(0) + 0.2 * Z(1)), var(Y(2) + X(0))]