`qml.prod` vs direct operators product

When creating multi-product of observables, I found something acting out of my expectation. I think it should be clear to just demonstrate it with some codes below:

Case 1

before = qml.PauliX(wires=0)
after = before @ qml.PauliZ(wires=2)

print(f"Before = {before}")
print(f"After = {after}")
Before = PauliX(wires=[0])
After = PauliX(wires=[0]) @ PauliZ(wires=[2])

It works fine as my expectation, before is still before.

Case 2

before = qml.PauliX(wires=0) @ qml.PauliY(wires=1)
after = before @ qml.PauliZ(wires=2)

print(f"Before = {before}")
print(f"After = {after}")
Before = PauliX(wires=[0]) @ PauliY(wires=[1]) @ PauliZ(wires=[2])
After = PauliX(wires=[0]) @ PauliY(wires=[1]) @ PauliZ(wires=[2])

Now before is not before anymore, but seems to be contaminated.

Case 3

If we write a for loop to see the behavior in more qubits:

from functools import reduce
for num_wires in range(1, 5):
    before = reduce(lambda x, y: x @ y, [qml.PauliX(wires=i) for i in range(num_wires)])
    after = before @ qml.PauliZ(wires=num_wires)

    print(f"`before` with {num_wires} wires:")
    print(f"Before = {before}")
    print(f"After = {after}")
    print()
`before` with 1 wires:
Before = PauliX(wires=[0])
After = PauliX(wires=[0]) @ PauliZ(wires=[1])

`before` with 2 wires:
Before = PauliX(wires=[0]) @ PauliX(wires=[1]) @ PauliZ(wires=[2])
After = PauliX(wires=[0]) @ PauliX(wires=[1]) @ PauliZ(wires=[2])

`before` with 3 wires:
Before = PauliX(wires=[0]) @ PauliX(wires=[1]) @ PauliX(wires=[2]) @ PauliZ(wires=[3])
After = PauliX(wires=[0]) @ PauliX(wires=[1]) @ PauliX(wires=[2]) @ PauliZ(wires=[3])

`before` with 4 wires:
Before = PauliX(wires=[0]) @ PauliX(wires=[1]) @ PauliX(wires=[2]) @ PauliX(wires=[3]) @ PauliZ(wires=[4])
After = PauliX(wires=[0]) @ PauliX(wires=[1]) @ PauliX(wires=[2]) @ PauliX(wires=[3]) @ PauliZ(wires=[4])

We see that when before is a product of more than 1 observables, before will be contaminated from other behavior.

Solution

Instead, if we change the syntax from x @ y to qml.prod(x, y), it works correctly.

for num_wires in range(1, 5):
    before = reduce(lambda x, y: x @ y, [qml.PauliX(wires=i) for i in range(num_wires)])
    after = qml.prod(before, qml.PauliZ(wires=num_wires))

    print(f"`before` with {num_wires} wires:")
    print(f"Before = {before}")
    print(f"After = {after}")
    print()
`before` with 1 wires:
Before = PauliX(wires=[0])
After = PauliX(wires=[0]) @ PauliZ(wires=[1])

`before` with 2 wires:
Before = PauliX(wires=[0]) @ PauliX(wires=[1])
After = (PauliX(wires=[0]) @ PauliX(wires=[1])) @ PauliZ(wires=[2])

`before` with 3 wires:
Before = PauliX(wires=[0]) @ PauliX(wires=[1]) @ PauliX(wires=[2])
After = (PauliX(wires=[0]) @ PauliX(wires=[1]) @ PauliX(wires=[2])) @ PauliZ(wires=[3])

`before` with 4 wires:
Before = PauliX(wires=[0]) @ PauliX(wires=[1]) @ PauliX(wires=[2]) @ PauliX(wires=[3])
After = (PauliX(wires=[0]) @ PauliX(wires=[1]) @ PauliX(wires=[2]) @ PauliX(wires=[3])) @ PauliZ(wires=[4])

Conclusion

Iā€™m not sure whether this is an intended design that we should use qml.prod(x, y) instead of x @ y, maybe I have miss somewhere in the document. If there is some detail discussion about this, it would be nice for me to take a look, thanks in advance.

PennyLane version

Name: PennyLane
Version: 0.33.1
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/PennyLaneAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /Users/yianchen/.pyenv/versions/3.9.12/lib/python3.9/site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml, typing-extensions
Required-by: PennyLane-Lightning

Platform info:           macOS-12.6-x86_64-i386-64bit
Python version:          3.9.12
Numpy version:           1.23.5
Scipy version:           1.11.4
Installed devices:
- default.gaussian (PennyLane-0.33.1)
- default.mixed (PennyLane-0.33.1)
- default.qubit (PennyLane-0.33.1)
- default.qubit.autograd (PennyLane-0.33.1)
- default.qubit.jax (PennyLane-0.33.1)
- default.qubit.legacy (PennyLane-0.33.1)
- default.qubit.tf (PennyLane-0.33.1)
- default.qubit.torch (PennyLane-0.33.1)
- default.qutrit (PennyLane-0.33.1)
- null.qubit (PennyLane-0.33.1)
- lightning.qubit (PennyLane-Lightning-0.33.1)

Thanks for the post @Yian_Chen .

You can change the behaviour of the operator dunder methods (@) by running qml.operation.enable_new_opmath(). This will give you sums, products, and scalar products instead of Tensor and Hamiltonian.

You can see the documentation for this function: qml.operation.enable_new_opmath ā€” PennyLane 0.34.0 documentation

We hope to change the default behavior in the near future.

1 Like

Hi @Christina , thank you, this works for me :slight_smile: