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
  • tape (QNode or QuantumTape or Callable) – A quantum circuit 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 circuit as described in qml.transform.

Return type

qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], 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

>>> dev = qml.device('default.qubit', wires=5)

You can apply the transform directly on a QNode. For that, you need first to define a pattern that is to be found in the circuit. We use the following pattern that implements the identity:

ops = [qml.S(0), qml.S(0), qml.Z(0)]
pattern = qml.tape.QuantumTape(ops)

Let’s consider the following circuit where we want to replace a sequence of two pennylane.S gates with a pennylane.PauliZ gate.

@partial(pattern_matching_optimization, pattern_tapes = [pattern])
@qml.qnode(device=dev)
def circuit():
    qml.S(wires=0)
    qml.Z(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.X(0))

During the call of the circuit, it is first optimized (if possible) and then executed.

>>> circuit()
def circuit():
    qml.S(wires=0)
    qml.Z(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.X(0))

For optimizing the circuit given the following template of CNOTs we apply the pattern_matching transform.

>>> 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 by Adjoint(S). If one would like avoiding this, it possible to give a custom quantum cost dictionary with a negative cost for pennylane.PauliZ.

>>> my_cost = {"PauliZ": -1 , "S": 1, "Adjoint(S)": 1}
>>> optimized_qfunc = pattern_matching_optimization(circuit, pattern_tapes=[pattern], custom_quantum_cost=my_cost)
>>> 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.Z(1)
    qml.CNOT(wires=[2, 3])
    qml.Toffoli(wires=[2, 3, 0])
    qml.CNOT(wires=[1, 4])
    return qml.expval(qml.X(0))

We define a pattern that implement the identity:

ops = [
    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]),
]
tape = qml.tape.QuantumTape(ops)

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(circuit, pattern_tapes=[pattern])
>>> 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)()["resources"].gate_types["CNOT"]
4
>>> qml.specs(optimized_qnode)()["resources"].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──────────┤

References

[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