qml.transforms.split_to_single_terms

split_to_single_terms(tape)[source]

Splits any expectation values of multi-term observables in a circuit into single term expectation values for devices that don’t natively support measuring expectation values of sums of observables.

Parameters

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

Returns

The transformed circuit as described in qml.transform.

Return type

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

Note

This transform doesn’t split non-commuting terms into multiple executions. It is suitable for state-based simulators that don’t natively support sums of observables, but can handle non-commuting measurements. For hardware or hardware-like simulators based on projective measurements, split_non_commuting should be used instead.

Examples:

This transform allows us to transform a QNode that measures multi-term observables into individual measurements, each corresponding to a single term.

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

@qml.transforms.split_to_single_terms
@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) + 0.5 * qml.Y(1) + qml.Z(0)),
           qml.expval(qml.X(1) + qml.Y(1))]

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.RY(x[0], wires=0)
    qml.RX(x[1], wires=1)
    return [qml.expval(qml.X(0) @ qml.Z(1) + 0.5 * qml.Y(1) + qml.Z(0)),
           qml.expval(qml.X(1) + qml.Y(1))]

circuit = qml.transforms.split_to_single_terms(circuit)

Internally, the QNode measures the individual measurements

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

Note that the observable Y(1) occurs twice in the original QNode, but only once in the transformed circuits. When there are multiple expectation value measurements that rely on the same observable, the observable is measured only once, and the result is copied to each original measurement.

While the execution is split into single terms internally, the final result has the same ordering as the user provides in the return statement.

>>> circuit([np.pi/4, np.pi/4])
[0.8535533905932737, -0.7071067811865475]

Internally, this function works with tapes. We can create a tape that returns expectation values of multi-term observables:

measurements = [
    qml.expval(qml.Z(0) + qml.Z(1)),
    qml.expval(qml.X(0) + 0.2 * qml.X(1) + 2 * qml.Identity()),
    qml.expval(qml.X(1) + qml.Z(1)),
]
tape = qml.tape.QuantumScript(measurements=measurements)
tapes, processing_fn = qml.transforms.split_to_single_terms(tape)

Now tapes is a tuple containing a single tape with the updated measurements, which are now the single-term observables that the original sum observables are composed of:

>>> tapes[0].measurements
[expval(Z(0)), expval(Z(1)), expval(X(0)), expval(X(1))]

The processing function becomes important as the order of the inputs has been modified. Instead of evaluating the observables in the returned expectation values directly, the four single-term observables are measured, resulting in 4 return values for the execution:

>>> dev = qml.device("default.qubit", wires=2)
>>> results = dev.execute(tapes)
>>> results
((1.0, 1.0, 0.0, 0.0),)

The processing function can be used to reorganize the results to get the 3 expectation values returned by the circuit:

>>> processing_fn(results)
(2.0, 2.0, 1.0)