PennyLaneAI / pennylane

PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Train a quantum computer the same way as a neural network.
https://pennylane.ai
Apache License 2.0
2.27k stars 585 forks source link

[BUG] pattern_matching_optimization fails for some templates #6135

Open gideonuchehara opened 3 weeks ago

gideonuchehara commented 3 weeks ago

Expected behavior

with qml.tape.QuantumTape() as temp_go:
    qml.CNOT([1, 2])
    qml.CNOT([0, 1])
    qml.CNOT([2, 3])
    qml.CNOT([1, 2])

    qml.CNOT([2, 3])
    qml.CNOT([1, 3])
    qml.CNOT([0, 2])
    qml.CNOT([0, 1])
    qml.expval(qml.PauliZ(0)@qml.PauliZ(1)@qml.PauliZ(2)@qml.PauliZ(3))

The above quantum tape is a matching template for the circuit below

dev = qml.device('default.qubit', wires=4)
@qml.qnode(device=dev)
def go_circuit():
    qml.CNOT([1, 2])
    qml.CNOT([0, 1])
    qml.CNOT([2, 3])
    qml.CNOT([1, 2])
    return qml.expval(qml.PauliZ(0)@qml.PauliZ(1)@qml.PauliZ(2)@qml.PauliZ(3))

The expected behaviour is that the result of applying pattern_matching_optimization to go_circuit() should be temp_go.

Actual behavior

The actual behaviour is that applying pattern_matching_optimization to go_circuit() does not return the temp_go, instead it returns the original circuit go_circuit().

Additional information

No response

Source code

from functools import partial
import pennylane as qml
from pennylane.transforms import pattern_matching_optimization

dev = qml.device('default.qubit', wires=4)
@qml.qnode(device=dev)
def go_circuit():
    qml.CNOT([1, 2])
    qml.CNOT([0, 1])
    qml.CNOT([2, 3])
    qml.CNOT([1, 2])
    return qml.expval(qml.PauliZ(0)@qml.PauliZ(1)@qml.PauliZ(2)@qml.PauliZ(3))

qml.draw_mpl(go_circuit)()

dev = qml.device('default.qubit', wires=4)
@qml.qnode(device=dev)
def temp_go():

    qml.CNOT([1, 2])
    qml.CNOT([0, 1])
    qml.CNOT([2, 3])
    qml.CNOT([1, 2])

    qml.CNOT([2, 3])
    qml.CNOT([1, 3])
    qml.CNOT([0, 2])
    qml.CNOT([0, 1])
    return qml.expval(qml.PauliZ(0)@qml.PauliZ(1)@qml.PauliZ(2)@qml.PauliZ(3))

qml.draw_mpl(temp_go)()

with qml.tape.QuantumTape() as temp_go:
    qml.CNOT([1, 2])
    qml.CNOT([0, 1])
    qml.CNOT([2, 3])
    qml.CNOT([1, 2])

    qml.CNOT([2, 3])
    qml.CNOT([1, 3])
    qml.CNOT([0, 2])
    qml.CNOT([0, 1])
    qml.expval(qml.PauliZ(0)@qml.PauliZ(1)@qml.PauliZ(2)@qml.PauliZ(3))

optimized_go_qfunc = pattern_matching_optimization(pattern_tapes=[temp_go])(go_circuit)
optimized_go_qnode = qml.QNode(optimized_go_qfunc, dev)

qml.draw_mpl(optimized_go_qnode)()

Tracebacks

No response

System information

Name: PennyLane
Version: 0.34.0
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: /home/gideonuchehara/anaconda3/envs/phd-env/lib/python3.10/site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml, typing-extensions
Required-by: PennyLane-Lightning, PennyLane-Lightning-GPU, PennyLane-qiskit

Platform info:           Linux-6.8.0-40-generic-x86_64-with-glibc2.35
Python version:          3.10.9
Numpy version:           1.26.2
Scipy version:           1.11.4
Installed devices:
- default.gaussian (PennyLane-0.34.0)
- default.mixed (PennyLane-0.34.0)
- default.qubit (PennyLane-0.34.0)
- default.qubit.autograd (PennyLane-0.34.0)
- default.qubit.jax (PennyLane-0.34.0)
- default.qubit.legacy (PennyLane-0.34.0)
- default.qubit.tf (PennyLane-0.34.0)
- default.qubit.torch (PennyLane-0.34.0)
- default.qutrit (PennyLane-0.34.0)
- null.qubit (PennyLane-0.34.0)
- lightning.gpu (PennyLane-Lightning-GPU-0.30.0)
- qiskit.aer (PennyLane-qiskit-0.28.0)
- qiskit.basicaer (PennyLane-qiskit-0.28.0)
- qiskit.ibmq (PennyLane-qiskit-0.28.0)
- qiskit.ibmq.circuit_runner (PennyLane-qiskit-0.28.0)
- qiskit.ibmq.sampler (PennyLane-qiskit-0.28.0)
- lightning.qubit (PennyLane-Lightning-0.34.0)

Existing GitHub issues

gideonuchehara commented 3 weeks ago

@glassnotes

albi3ro commented 2 weeks ago

Thanks for opening this issue @gideonuchehara . Hopefully I am understanding the problem correctly, but I think I understand what's happening.

In optimized_go_qnode, you are expecting the match for the first half of the template temp_go to be replaced by the second half of temp_go?

I think the problem is that replacing four cnots with a different four cnots doesn't actually optimize the circuit at all. We only replace the match if it results in a reduction in the size of the circuit.

If we update `go_circuit to have one additional CNOT:

dev = qml.device('default.qubit', wires=4)
@qml.qnode(device=dev)
def go_circuit():
    qml.CNOT([1, 2])
    qml.CNOT([0, 1])
    qml.CNOT([2, 3])
    qml.CNOT([1, 2])
    qml.CNOT([2, 3])
    return qml.expval(qml.PauliZ(0)@qml.PauliZ(1)@qml.PauliZ(2)@qml.PauliZ(3))

The optimized circuit optimized_go_qnode = pattern_matching_optimization(go_circuit, pattern_tapes=[temp_go]) does end up with a circuit with three cnots.

Note that the template should not contain any measurements, and that pennylane now has a new syntax for applying transforms to qnodes.