qml.transforms.pattern_matching_optimization¶
-
pattern_matching_optimization
(tape, pattern_tapes, custom_quantum_cost=None)[source]¶ Quantum function transform to optimize a circuit given a list of patterns (templates).
- Parameters
qfunc (function) – A quantum function to be optimized.
pattern_tapes (list(QuantumTape)) – List of quantum tapes that implement the identity.
custom_quantum_cost (dict) – Optional, quantum cost that overrides the default cost dictionary.
- Returns
the transformed quantum function
- Return type
function
- Raises
QuantumFunctionError – The pattern provided is not a valid QuantumTape or the pattern contains measurements or the pattern does not implement identity or the circuit has less qubits than the pattern.
Example
First let’s consider the following circuit where we want to replace sequence of two
pennylane.S
gates with apennylane.PauliZ
gate.def circuit(): qml.S(wires=0) qml.PauliZ(wires=0) qml.S(wires=1) qml.CZ(wires=[0, 1]) qml.S(wires=1) qml.S(wires=2) qml.CZ(wires=[1, 2]) qml.S(wires=2) return qml.expval(qml.PauliX(wires=0))
Therefore we use the following pattern that implements the identity:
with qml.tape.QuantumTape() as pattern: qml.S(wires=0) qml.S(wires=0) qml.PauliZ(wires=0)
For optimizing the circuit given the following template of CNOTs we apply the
pattern_matching
transform.>>> dev = qml.device('default.qubit', wires=5) >>> qnode = qml.QNode(circuit, dev) >>> optimized_qfunc = pattern_matching_optimization(pattern_tapes=[pattern])(circuit) >>> optimized_qnode = qml.QNode(optimized_qfunc, dev)
>>> print(qml.draw(qnode)()) 0: ──S──Z─╭●──────────┤ <X> 1: ──S────╰Z──S─╭●────┤ 2: ──S──────────╰Z──S─┤
>>> print(qml.draw(optimized_qnode)()) 0: ──S†─╭●────┤ <X> 1: ──Z──╰Z─╭●─┤ 2: ──Z─────╰Z─┤
Note that with this pattern we also replace a
pennylane.S
,pennylane.PauliZ
sequence byAdjoint(S)
. If one would like avoiding this, it possible to give a custom quantum cost dictionary with a negative cost forpennylane.PauliZ
.>>> my_cost = {"PauliZ": -1 , "S": 1, "Adjoint(S)": 1} >>> optimized_qfunc = pattern_matching_optimization(pattern_tapes=[pattern], custom_quantum_cost=my_cost)(circuit) >>> optimized_qnode = qml.QNode(optimized_qfunc, dev)
>>> print(qml.draw(optimized_qnode)()) 0: ──S──Z─╭●────┤ <X> 1: ──Z────╰Z─╭●─┤ 2: ──Z───────╰Z─┤
Now we can consider a more complicated example with the following quantum circuit to be optimized
def circuit(): qml.Toffoli(wires=[3, 4, 0]) qml.CNOT(wires=[1, 4]) qml.CNOT(wires=[2, 1]) qml.Hadamard(wires=3) qml.PauliZ(wires=1) qml.CNOT(wires=[2, 3]) qml.Toffoli(wires=[2, 3, 0]) qml.CNOT(wires=[1, 4]) return qml.expval(qml.PauliX(wires=0))
We define a pattern that implement the identity:
with qml.tape.QuantumTape() as pattern: qml.CNOT(wires=[1, 2]) qml.CNOT(wires=[0, 1]) qml.CNOT(wires=[1, 2]) qml.CNOT(wires=[0, 1]) qml.CNOT(wires=[0, 2])
For optimizing the circuit given the given following pattern of CNOTs we apply the pattern_matching transform.
>>> dev = qml.device('default.qubit', wires=5) >>> qnode = qml.QNode(circuit, dev) >>> optimized_qfunc = pattern_matching_optimization(pattern_tapes=[pattern])(circuit) >>> optimized_qnode = qml.QNode(optimized_qfunc, dev)
In our case, it is possible to find three CNOTs and replace this pattern with only two CNOTs and therefore optimizing the circuit. The number of CNOTs in the circuit is reduced by one.
>>> qml.specs(qnode)()["gate_types"]["CNOT"] 4
>>> qml.specs(optimized_qnode)()["gate_types"]["CNOT"] 3
>>> print(qml.draw(qnode)()) 0: ─╭X──────────╭X────┤ <X> 1: ─│──╭●─╭X──Z─│──╭●─┤ 2: ─│──│──╰●─╭●─├●─│──┤ 3: ─├●─│───H─╰X─╰●─│──┤ 4: ─╰●─╰X──────────╰X─┤
>>> print(qml.draw(optimized_qnode)()) 0: ─╭X──────────╭X─┤ <X> 1: ─│─────╭X──Z─│──┤ 2: ─│──╭●─╰●─╭●─├●─┤ 3: ─├●─│───H─╰X─╰●─┤ 4: ─╰●─╰X──────────┤
See also
Reference:
[1] Iten, R., Moyard, R., Metger, T., Sutter, D. and Woerner, S., 2022. Exact and practical pattern matching for quantum circuit optimization. doi.org/10.1145/3498325