Pattern collections (to be used with PatternManager)

This page summarizes the different pre-defined pattern collections that can be used to easily construct rewriting plugins

Entangling gates rewriting patterns

These collections are dynamically generated thanks to the collection_from_target() method. They are used by the NISQCompiler to rewrite the output quantum circuits in a target gate set. The generated patterns are detailed below

Targeting CNOT gate
from qat.pbo.collections.standard import collection_from_target
from qat.pbo import PatternManager

collection = collection_from_target("CNOT")
plugin = PatternManager(collections=[collection])

or, equivalently:

from qat.pbo.collections.standard import manager_from_target

plugin = manager_from_target("CNOT")
Generated patterns
SWAP to CNOT
CSIGN to CNOT
Targeting CSIGN gate
from qat.pbo.collections.standard import collection_from_target
from qat.pbo import PatternManager

collection = collection_from_target("CSIGN")
plugin = PatternManager(collections=[collection])

or, equivalently:

from qat.pbo.collections.standard import manager_from_target

plugin = manager_from_target("CSIGN")
Generated patterns
SWAP to CNOT
CNOT to CSIGN
Targeting ZZ gate
from qat.pbo.collections.standard import collection_from_target
from qat.pbo import PatternManager

collection = collection_from_target("ZZ")
plugin = PatternManager(collections=[collection])

or, equivalently:

from qat.pbo.collections.standard import manager_from_target

plugin = manager_from_target("ZZ")
Generated patterns
SWAP to CNOT
CNOT to CSIGN
CSIGN to ZZ
Targeting XX gate
from qat.pbo.collections.standard import collection_from_target
from qat.pbo import PatternManager

collection = collection_from_target("XX")
plugin = PatternManager(collections=[collection])

or, equivalently:

from qat.pbo.collections.standard import manager_from_target

plugin = manager_from_target("XX")
Generated patterns
SWAP to CNOT
CNOT to CSIGN
CSIGN to ZZ
ZZ to XX

Involutions and trivial simplifications

This collection performs trivial simplifications of involutive gates (such as H or CNOT) and gates that trivially reduce to the identity (such as \(RZ(0)\)). All the patterns in this collection are mapped to the empty circuit (and thus any occurence of them will be removed from the circuit).

from qat.pbo.collections import INVOLUTIONS
from qat.pbo import PatternManager


plugin = PatternManager(collections=[INVOLUTIONS])
List of patterns

Gate expansion

This collection aims at expanding some gates into the CNOT + H + RZ + RX gate set. It is used by the NISQCompiler plugin in order to pre-process the input circuit before compiling it.

from qat.pbo.collections import EXPANSION_COLLECTION
from qat.pbo import PatternManager


plugin = PatternManager(collections=[EXPANSION_COLLECTION])
List of patterns
CCNOT
ISWAP
T
D-T
S
D-S
SQRTSWAP
C-PH

Rotation merging

This collection provides patterns that merge consecutive Pauli rotations with the same axis. It is used by the NISQCompiler plugin in order to post-process the input circuit after compiling it.

from qat.pbo.collections import MERGING_PATTERNS
from qat.pbo import PatternManager


plugin = PatternManager(collections=[MERGING_PATTERNS])
List of patterns
Merging RZ gates
Merging RX gates
Merging RY gates

Trivial commutations

These collections commutes local rotations to the left of entangling gates. This is used by the NISQCompiler plugin in order to further reduce the single qubit gate count (in conjunction with the merging patterns from above).

They are stored in a map and supports the following entangling gates: CNOT, CZ/CSIGN, ZZ, and XX

Commutation with CNOT
from qat.pbo.collections import COMMUTATION_COLLECTIONS
from qat.pbo import PatternManager

collection, gate = COMMUTATION_COLLECTIONS["CNOT"]
plugin = PatternManager(collections=[COMMUTATION_COLLECTIONS])

if gate is not None:
    plugin.add_abstract_gate(gate)
List of patterns
Commutation with ZZ
from qat.pbo.collections import COMMUTATION_COLLECTIONS
from qat.pbo import PatternManager

collection, gate = COMMUTATION_COLLECTIONS["ZZ"]
plugin = PatternManager(collections=[COMMUTATION_COLLECTIONS])

if gate is not None:
    plugin.add_abstract_gate(gate)
List of patterns
Commutation with XX
from qat.pbo.collections import COMMUTATION_COLLECTIONS
from qat.pbo import PatternManager

collection, gate = COMMUTATION_COLLECTIONS["XX"]
plugin = PatternManager(collections=[COMMUTATION_COLLECTIONS])

if gate is not None:
    plugin.add_abstract_gate(gate)
List of patterns
Commutation with CSIGN
from qat.pbo.collections import COMMUTATION_COLLECTIONS
from qat.pbo import PatternManager

collection, gate = COMMUTATION_COLLECTIONS["CSIGN"]
plugin = PatternManager(collections=[COMMUTATION_COLLECTIONS])

if gate is not None:
    plugin.add_abstract_gate(gate)
List of patterns

Example

Targeting a particular entangling gate

Say, I need to rewrite a quantum circuit so that it only uses \(XX(\pi/2)\) gates. I’ll create a pattern manager that contains the gate expansion collection and the transpilation pattern targeting the XX gate:

from qat.pbo.collections import EXPANSION_COLLECTION
from qat.pbo.collections.standard import collection_from_target
from qat.lang.AQASM import AbstractGate

from qat.pbo import PatternManager

plugin = PatternManager(collections=[EXPANSION_COLLECTION, collection_from_target("XX")])
plugin.add_abstract_gate(AbstractGate('XX', [], arity=2))

Let us try to compile a QAOA quantum circuit using this manager:

from qat.opt import MaxCut
import networkx as nx
from qat.core import Batch

job = MaxCut(nx.generators.erdos_renyi_graph(10, 0.5, seed=0)).to_job("qaoa", 2)  # '2' is the depth
print("Before compilation:", job.circuit.statistics()['gates'])
new_circuit = plugin.compile(Batch(jobs=[job]), None).jobs[0].circuit
print("After compilation:", new_circuit.statistics()['gates'])
Before compilation: {'custom gate': 0, 'H': 10, 'CNOT': 76, 'PH': 38, 'RX': 20}
After compilation: {'custom gate': 0, 'H': 466, 'PH': 190, 'XX': 76, 'RX': 20}

Notice how the number of entangling gates is the same in both circuits, and how the number of single qubit gates exploded.

Removing some H gates

We can prevent this by adding MERGING_PATTERNS and INVOLUTIONS to the list of collections:

from qat.pbo.collections import EXPANSION_COLLECTION
from qat.pbo.collections.standard import collection_from_target
from qat.lang.AQASM import AbstractGate
from qat.pbo.collections import MERGING_PATTERNS, INVOLUTIONS

from qat.pbo import PatternManager

plugin = PatternManager(collections=[EXPANSION_COLLECTION, collection_from_target("XX"), MERGING_PATTERNS, INVOLUTIONS])
plugin.add_abstract_gate(AbstractGate('XX', [], arity=2))

from qat.opt import MaxCut
import networkx as nx
from qat.core import Batch

job = MaxCut(nx.generators.erdos_renyi_graph(10, 0.5, seed=0)).to_job("qaoa", 2)  # '2' is the depth
print("Before compilation:", job.circuit.statistics()['gates'])
new_circuit = plugin.compile(Batch(jobs=[job]), None).jobs[0].circuit
print("After compilation:", new_circuit.statistics()['gates'])
Before compilation: {'custom gate': 0, 'H': 10, 'CNOT': 76, 'PH': 38, 'RX': 20}
After compilation: {'custom gate': 0, 'H': 278, 'PH': 190, 'XX': 76, 'RX': 20}

The number of H gates reduced by quite a lot.

Turning RZ into RX

We can probably do even better by rewriting with the following custom pattern:

\[H\cdot RZ(\theta) \cdot H = RX(\theta)\]
from qat.pbo.collections import EXPANSION_COLLECTION
from qat.pbo.collections.standard import collection_from_target
from qat.lang.AQASM import AbstractGate
from qat.pbo.collections import MERGING_PATTERNS, INVOLUTIONS

from qat.pbo import PatternManager, VAR

plugin = PatternManager(collections=[EXPANSION_COLLECTION, collection_from_target("XX"), MERGING_PATTERNS, INVOLUTIONS])
plugin.add_abstract_gate(AbstractGate('XX', [], arity=2))

theta = VAR()
rx_conj = plugin.new_group()
rx_conj.pattern_to_remove([('H', [0]), ('RZ', [0], theta), ('H', [0])])
rx_conj.pattern_to_remove([('H', [0]), ('PH', [0], theta), ('H', [0])])
rx_conj.add_pattern([('RX', [0], theta)])

from qat.opt import MaxCut
import networkx as nx
from qat.core import Batch

job = MaxCut(nx.generators.erdos_renyi_graph(10, 0.5, seed=0)).to_job("qaoa", 2)  # '2' is the depth
print("Before compilation:", job.circuit.statistics()['gates'])
new_circuit = plugin.compile(Batch(jobs=[job]), None).jobs[0].circuit
print("After compilation:", new_circuit.statistics()['gates'])
Before compilation: {'custom gate': 0, 'H': 10, 'CNOT': 76, 'PH': 38, 'RX': 20}
After compilation: {'custom gate': 0, 'PH': 72, 'RX': 134, 'H': 42, 'XX': 76}

This looks already more reasonnable! Maybe we can go even further ?

Commuting and merging rotations

We can now ask the compiler to commute RX gates to the left of XX gates and then merge consecutive RX gates

from qat.pbo.collections import EXPANSION_COLLECTION
from qat.pbo.collections.standard import collection_from_target
from qat.lang.AQASM import AbstractGate
from qat.pbo.collections import MERGING_PATTERNS, INVOLUTIONS, COMMUTATION_COLLECTIONS

from qat.pbo import PatternManager, VAR

commuting, gate = COMMUTATION_COLLECTIONS["XX"]

plugin = PatternManager(
    collections=[EXPANSION_COLLECTION, collection_from_target("XX"), MERGING_PATTERNS, INVOLUTIONS, commuting]
)
plugin.add_abstract_gate(gate)

theta = VAR()
rx_conj = plugin.new_group()
rx_conj.pattern_to_remove([('H', [0]), ('RZ', [0], theta), ('H', [0])])
rx_conj.pattern_to_remove([('H', [0]), ('PH', [0], theta), ('H', [0])])
rx_conj.add_pattern([('RX', [0], theta)])

from qat.opt import MaxCut
import networkx as nx
from qat.core import Batch

job = MaxCut(nx.generators.erdos_renyi_graph(10, 0.5, seed=0)).to_job("qaoa", 2)  # '2' is the depth
print("Before compilation:", job.circuit.statistics()['gates'])
new_circuit = plugin.compile(Batch(jobs=[job]), None).jobs[0].circuit
print("After compilation:", new_circuit.statistics()['gates'])
Before compilation: {'custom gate': 0, 'H': 10, 'CNOT': 76, 'PH': 38, 'RX': 20}
After compilation: {'custom gate': 0, 'PH': 72, 'RX': 68, 'H': 42, 'XX': 76}