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
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])
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])
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])
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
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:
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}