qml.transforms.diagonalize_measurements

diagonalize_measurements(tape, supported_base_obs=(<class 'pennylane.ops.qubit.non_parametric_ops.PauliZ'>, <class 'pennylane.ops.identity.Identity'>))[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

This transform will raise an error if it encounters non-commuting terms. To avoid non-commuting terms in circuit measurements, the split_non_commuting transform can be applied.

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*Y)+X]
1: ──RX(0.79)─┤ ╰<X@Z> │
2: ───────────┤        ╰Var[(0.50*Y)+X]

becomes

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

The transform diagonalizes observables from the local Pauli basis only, i.e. it diagonalizes X, Y, Z, and Hadamard.

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))]