qml.transforms.split_non_commuting

split_non_commuting(tape)[source]

Splits a qnode measuring non-commuting observables into groups of commuting observables.

Parameters

tape (QNode or QuantumTape or Callable) – A circuit that contains a list of non-commuting observables to measure.

Returns

The transformed circuit as described in qml.transform.

Return type

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

Example

This transform allows us to transform a QNode that measures non-commuting observables to multiple circuit executions with qubit-wise commuting groups:

dev = qml.device("default.qubit", wires=2)

@qml.transforms.split_non_commuting
@qml.qnode(dev)
def circuit(x):
    qml.RX(x,wires=0)
    return [qml.expval(qml.X(0)), qml.expval(qml.Z(0))]

Instead of decorating the QNode, we can also create a new function that yields the same result in the following way:

@qml.qnode(dev)
def circuit(x):
    qml.RX(x,wires=0)
    return [qml.expval(qml.X(0)), qml.expval(qml.Z(0))]

circuit = qml.transforms.split_non_commuting(circuit)

Internally, the QNode is split into groups of commuting observables when executed:

>>> print(qml.draw(circuit)(0.5))
0: ──RX(0.50)─┤  <X>
\
0: ──RX(0.50)─┤  <Z>

Note that while internally multiple QNodes are created, the end result has the same ordering as the user provides in the return statement. Here is a more involved example where we can see the different ordering at the execution level but restoring the original ordering in the output:

@qml.transforms.split_non_commuting
@qml.qnode(dev)
def circuit0(x):
    qml.RY(x[0], wires=0)
    qml.RX(x[1], wires=0)
    return [qml.expval(qml.X(0)),
            qml.expval(qml.Z(0)),
            qml.expval(qml.Y(1)),
            qml.expval(qml.Z(0) @ qml.Z(1)),
            ]

Drawing this QNode unveils the separate executions in the background

>>> print(qml.draw(circuit0)([np.pi/4, np.pi/4]))
0: ──RY(0.79)──RX(0.79)─┤  <X>
1: ─────────────────────┤  <Y>
\
0: ──RY(0.79)──RX(0.79)─┤  <Z> ╭<Z@Z>
1: ─────────────────────┤      ╰<Z@Z>

Yet, executing it returns the original ordering of the expectation values. The outputs correspond to \((\langle \sigma_x^0 \rangle, \langle \sigma_z^0 \rangle, \langle \sigma_y^1 \rangle, \langle \sigma_z^0\sigma_z^1 \rangle)\).

>>> circuit0([np.pi/4, np.pi/4])
[0.7071067811865475, 0.49999999999999994, 0.0, 0.49999999999999994]

Internally, this function works with tapes. We can create a tape with non-commuting observables:

measurements = [qml.expval(qml.Z(0)), qml.expval(qml.Y(0))]
tape = qml.tape.QuantumTape(measurements=measurements)

tapes, processing_fn = qml.transforms.split_non_commuting(tape)

Now tapes is a list of two tapes, each for one of the non-commuting terms:

>>> [t.observables for t in tapes]
[[expval(Z(0))], [expval(Y(0))]]

The processing function becomes important when creating the commuting groups as the order of the inputs has been modified:

measurements = [
    qml.expval(qml.Z(0) @ qml.Z(1)),
    qml.expval(qml.X(0) @ qml.X(1)),
    qml.expval(qml.Z(0)),
    qml.expval(qml.X(0))
]
tape = qml.tape.QuantumTape(measurements=measurements)

tapes, processing_fn = qml.transforms.split_non_commuting(tape)

In this example, the groupings are group_coeffs = [[0,2], [1,3]] and processing_fn makes sure that the final output is of the same shape and ordering:

>>> processing_fn([t.measurements for t in tapes])
(expval(Z(0) @ Z(1)),
expval(X(0) @ X(1)),
expval(Z(0)),
expval(X(0)))

Measurements that accept both observables and wires so that e.g. qml.counts, qml.probs and qml.sample can also be used. When initialized using only wires, these measurements are interpreted as measuring with respect to the observable qml.Z(wires[0])@qml.Z(wires[1])@...@qml.Z(wires[len(wires)-1])

measurements = [
    qml.expval(qml.X(0)),
    qml.probs(wires=[1]),
    qml.probs(wires=[0, 1])
]
tape = qml.tape.QuantumTape(measurements=measurements)

tapes, processing_fn = qml.transforms.split_non_commuting(tape)

This results in two tapes, each with commuting measurements:

>>> [t.measurements for t in tapes]
[[expval(X(0)), probs(wires=[1])], [probs(wires=[0, 1])]]