How to return circuit to pre-measurement state in order to measure with multiple observables in turn

Hi there, the question that I have is basically an addendum to this earlier one, which involved trying get expectation values with multiple observables in succession: https://discuss.pennylane.ai/t/one-wire-with-multi-observables/1032/2 I am trying to do something similar in order to get the expectation value of a given state with multiple observable independently, so I am wondering if there is a way to reset the circuit to the post-operation but pre-measurement state after each measurement, so that I can apply each observable in turn without having to run the entire circuit operation for each observable. The specs for my installation are

Name: PennyLane
Version: 0.29.1
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/XanaduAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /usr/local/lib/python3.8/dist-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, retworkx, scipy, semantic-version, toml
Required-by: PennyLane-Lightning

Platform info:           Linux-5.4.0-91-generic-x86_64-with-glibc2.29
Python version:          3.8.10
Numpy version:           1.23.5
Scipy version:           1.9.3
Installed devices:
- default.gaussian (PennyLane-0.29.1)
- default.mixed (PennyLane-0.29.1)
- default.qubit (PennyLane-0.29.1)
- default.qubit.autograd (PennyLane-0.29.1)
- default.qubit.jax (PennyLane-0.29.1)
- default.qubit.tf (PennyLane-0.29.1)
- default.qubit.torch (PennyLane-0.29.1)
- default.qutrit (PennyLane-0.29.1)
- null.qubit (PennyLane-0.29.1)
- lightning.qubit (PennyLane-Lightning-0.28.1)

Hey @DarthMalloc!

I think you just need to upgrade to v0.30 and check out the new return type system!

https://docs.pennylane.ai/en/stable/introduction/returns.html

Thank you very much for getting back to me! So you are saying this will allow me to get expectation values for multiple observables without needing to run the circuit all over again each time?

The new QNode return system is great :smile:!

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

@qml.qnode(dev)
def circuit():
    return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliX(0))

print(circuit())
print(dev.num_executions)

'''
(tensor(1., requires_grad=True), tensor(0., requires_grad=True))
1
'''

Thanks for getting back to me! I think this might help somewhat, but I am still not able to get around the issue of commuting errors when certain observables are called in sequence. I think what would really help is if I could remove one observable from the circuit after its corresponding expectation value was calculated, and then calculate the expectation with the next observable without the previous one interfering. Is there a way to do that?

I don’t think that that’s possible currently, but maybe your specific application has a workaround :thinking:. Are you able to provide a code example that doesn’t currently work for you but you would like for it to?

Here is a minimal example of what I am trying to do in terms of expectation value calculations:

import pennylane as qml

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

obs1 = qml.prod(qml.PauliX(3), qml.PauliX(4))
obs2 = qml.prod(qml.PauliY(3), qml.PauliY(4))
obs3 = qml.prod(qml.PauliZ(3), qml.PauliZ(4))

@qml.qnode(dev) 
def circuit(Obs):
    expect = [qml.expval(o) for o in Obs]
    return expect

print(circuit([obs1, obs2, obs3]))
print(dev.num_executions)

When I run it, I get

Traceback (most recent call last):
  File "/usr/local/lib/python3.11/dist-packages/pennylane/tape/tape.py", line 87, in rotations_and_diagonal_measurements
    rotations, diag_obs = qml.pauli.diagonalize_qwc_pauli_words(tape._obs_sharing_wires)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pennylane/pauli/utils.py", line 1231, in diagonalize_qwc_pauli_words
    raise ValueError("This function only supports Tensor products of pauli ops.")
ValueError: This function only supports Tensor products of pauli ops.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/lib/python3.11/idlelib/run.py", line 578, in runcode
    exec(code, self.locals)
  File "/home/justin/qml_expect_test.py", line 14, in <module>
    print(circuit([obs1, obs2, obs3]))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pennylane/qnode.py", line 867, in __call__
    res = qml.execute(
          ^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pennylane/interfaces/execution.py", line 407, in execute
    qml.interfaces.cache_execute(
  File "/usr/local/lib/python3.11/dist-packages/pennylane/interfaces/execution.py", line 204, in wrapper
    res = fn(execution_tapes.values(), **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pennylane/interfaces/execution.py", line 129, in fn
    tapes = [expand_fn(tape) for tape in tapes]
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pennylane/interfaces/execution.py", line 129, in <listcomp>
    tapes = [expand_fn(tape) for tape in tapes]
             ^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pennylane/interfaces/execution.py", line 388, in <lambda>
    expand_fn = lambda tape: device.expand_fn(tape, max_expansion=max_expansion)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pennylane/_device.py", line 696, in expand_fn
    return self.default_expand_fn(circuit, max_expansion=max_expansion)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pennylane/_device.py", line 670, in default_expand_fn
    circuit = circuit.expand(depth=max_expansion, stop_at=self.stopping_condition)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pennylane/tape/qscript.py", line 1079, in expand
    new_script = qml.tape.tape.expand_tape(
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pennylane/tape/tape.py", line 181, in expand_tape
    diagonalizing_gates, diagonal_measurements = rotations_and_diagonal_measurements(tape)
                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pennylane/tape/tape.py", line 98, in rotations_and_diagonal_measurements
    raise qml.QuantumFunctionError(_err_msg_for_some_meas_not_qwc(tape.measurements)) from e
pennylane.QuantumFunctionError: Only observables that are qubit-wise commuting Pauli words can be returned on the same wire, some of the following measurements do not commute:
[PauliX(wires=[3]) @ PauliX(wires=[4]), PauliY(wires=[3]) @ PauliY(wires=[4]), PauliZ(wires=[3]) @ PauliZ(wires=[4])]

Is there a workaround that I might be able to use so that I am not violating commutativity rules? The main thing that I am trying to do is to get the expectation values for all of the observables without having to carry out the main circuit operations more than once.

Okay — there’s a very simple workaround :smiley:. There’s been a bug with qml.prod for a little while, but it’s quite a non-trivial fix. These PRs are open:

You can just bypass qml.prod altogether and it’ll work!

import pennylane as qml

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

obs1 = qml.PauliX(3) @ qml.PauliX(4)
obs2 = qml.PauliY(3) @ qml.PauliY(4)
obs3 = qml.PauliZ(3) @ qml.PauliZ(4)

@qml.qnode(dev) 
def circuit(obs):
    return [qml.expval(ob) for ob in obs]

circuit([obs1, obs2, obs3])
[tensor(0., requires_grad=True),
 tensor(0., requires_grad=True),
 tensor(1., requires_grad=True)]

Sorry about the delay. Thank you very much, that worked and it helps a lot!

1 Like