Low-level optimization
The qat.pbo
module provides different tools to optimize a quantum circuit
using rule-based rewriting. This page presents a low level optimization tool,
GraphCircuit
. On this page, only simple patterns are used.
Nevertheless, this tool accepts complex patterns: to learn more about patterns,
please refer to Writing patterns.
The notebook Replacing patterns in a circuit
may help to better understand our syntax to describe a pattern, and how to use
a pattern with GraphCircuit
tool.
This notebook optimizes a circuit by replacing
some patterns by equivalent ones.
Introduction
The rewriting procedure is described via definitions and rewriting rules.
A rule is composed of a left-hand side pattern and a right-hand side pattern.
A pattern is a simple (i.e. small) subcircuit and is defined by a list of
gates (a gate is defined by a tuple composed of a name, a list of qubits
on which the gate acts and [optionally] parameters). The class
GraphCircuit
will look for the left pattern in the
circuit and replace it by the right one.
For instance, the following code is used to remove consecutive H gates:
from qat.pbo import GraphCircuit
from qat.lang.AQASM import Program, H, X
#
# Write circuit
#
# Define initial circuit (X - H - H circuit)
prog = Program()
qubit = prog.qalloc(1)
prog.apply(X, qubit)
prog.apply(H, qubit)
prog.apply(H, qubit)
circ = prog.to_circ()
#
# Optimize circuit
#
# Create a graph object and load circuit
graph = GraphCircuit()
graph.load_circuit(circ)
# Define two patterns
left_pattern = [("H", [0]), ("H", [0])]
right_pattern = []
# Replace left_pattern by right_pattern, i.e. the old one by the new one
graph.replace_pattern(left_pattern, right_pattern)
The pattern left_pattern
describes a pattern composed of two
"H"
gates acting on the same qubit - qubit 0.
The first found subcircuit of the initial circuit matching with
left_pattern
will be replaced by right_pattern
thanks to
the method replace_pattern()
. The pattern can
be found on any qubit of the circuit (not only the qubit 0).
Method replace_pattern()
will replace only the
first occurence of left_pattern
(and return True
if the
pattern has been found). To replace all occurences of left_pattern
,
one can use:
while graph.replace_pattern(left_pattern, right_pattern):
continue
The method to_circ()
can be used to transform
a GraphCircuit
back into a Circuit
.
# Get the optimized circuit
optimized_circ = graph.to_circ()
The function to_circ()
only works if
all gates composing the graph can be cast into a known quantum gate. To add
your own quantum gate, please use add_abstract_gate()
.
See code block below for an example:
from qat.lang.AQASM import Program, X, H
from qat.pbo import GraphCircuit
# Define program
prog = Program()
qbit = prog.qalloc(1)
prog.apply(X, qbit)
prog.apply(H, qbit)
circ = prog.to_circ()
# Replace pattern
left_pattern = [("X", [0]), ("H", [0])]
right_pattern = [("HZ", [0])]
graph = GraphCircuit()
graph.load_circuit(circ)
graph.replace_pattern(left_pattern, right_pattern)
# graph.to_circ() won't work since "HZ" gate is not known
from qat.lang.AQASM import AbstractGate
graph.add_abstract_gate(AbstractGate("HZ", [], 1))
# graph.to_circ() is now working
optimized_circ = graph.to_circ()
In the previous code, a gate "HZ"
is used to replace the \(X - H\)
pattern. The gate "HZ"
is not a common gate, so
GraphCircuit
cannot transform this name into a real quantum
gate. One needs to define an abstract gate called "HZ"
to help
GraphCircuit
to cast "HZ"
into a quantum gate.
Advanced matching
Targeting patterns through their index
All instances of GraphCircuit
are graphs where each node
corresponds to a gate. Each node / gate has an index. Using these indices,
one can select which pattern should be replaced.
The method find_pattern()
must be used to find
patterns in a graph. This function returns a list of indices for this pattern.
When a pattern is composed of several gates, its corresponds to the index of the first
gate composing it.
from qat.lang.AQASM import Program, H
from qat.pbo import GraphCircuit
# Define program
prog = Program()
qbits = prog.qalloc(2)
prog.apply(H, qbits[0]) # Pattern H - H on qubit 0
prog.apply(H, qbits[0])
prog.apply(H, qbits[1]) # Pattern H - H on qubit 1
prog.apply(H, qbits[1])
# Init graph
graph = GraphCircuit()
graph.load_circuit(prog.to_circ())
# Find pattern
graph.find_pattern([("H", [0]), ("H", [0])])
# Possible output -> [1, 3]
Pattern indices can be used, in a few different ways, to select which pattern one may want to replace. See code block below, for instance:
#
# Remove a single pattern
#
# Remove the first found pattern (pattern of index 1)
graph.replace_pattern([("H", [0]), ("H", [0])], [], pos=1)
# Remove the second found pattern (pattern of index 3)
graph.replace_pattern([("H", [0]), ("H", [0])], [], pos=3)
#
# Remove all patterns
#
# Case 1
graph.replace_pattern([("H", [0]), ("H", [0])], [], pos=[1, 3])
# Case 2
graph.replace_pattern([("H", [0]), ("H", [0])], [], pos=all)
Warning
On the previous example, please execute each line separately. If you execute several lines, you may ask the optimizer to remove several times the same pattern: it will not work.
Patterns specific to the beginning/end of a circuit
Not all circuit simplification patterns concern unitary gates in the middle of a circuit. Some patterns may be only valid at the beginning, for instance, because of the specific \(|0\rangle ^ {\otimes n}\) value of all qubits. For instance, when the initial state of a circuit is \(|0\rangle ^ {\otimes n}\), swap gates present at the beginning of the circuit are useless. For example:
from qat.lang.AQASM import Program, H, SWAP
from qat.pbo import GraphCircuit
# Create a circuit
prog = Program()
qbits = prog.qalloc(2)
prog.apply(SWAP, qbits)
prog.apply(H, qbits[0])
prog.apply(SWAP, qbits)
circ = prog.to_circ()
# Remove useless SWAP at the beginning of the circuit
graph = GraphCircuit()
graph.load_circuit(circ)
# Select pattern connected to the beginning of the circuit on qubits
# 0 and 1
graph.replace_pattern([("SWAP", [0, 1])], [], begin=[0, 1])
On this example, only patterns composed of a single "SWAP"
connected
to the beginning of the circuit are removed.
The keyword end
can be used to select patterns connected to the
ending of the circuit.