Note
Click here to download the full example code
Defining Custom Compilers¶
A Compiler
instance contains a list of
Pass
instances. During compilation of a circuit,
each of these passes are executed, in order, to mutate a copy of the circuit. See the
compiler API page for a complete list of
built-in passes and their documentation.
Predefined Pass Lists¶
Several useful combinations of pass classes are provided as members of the
compiler class: HARDWARE_PASSES
,
SIMPLIFY_PASSES
,
RC_PASSES
, and
NATIVE2Q_PASSES
. See their documentation for
further details.
Note that these pass classes need to be instantiated before they can be given to the
compiler constructor. For certain types of advanced usage, directly invoking the
constructor may be the preferred method. However, the compiler also has two static
covenience methods to automate pass instantiation:
from_config()
and
basic()
. The first of these uses a
Config
object to pass relevant gate factories to each pass, and the
second is a further specialization that auto-instantiates these factories based on
a desired entangling gate and mode (which may not be relevant to all passes).
import trueq as tq
# a compiler to simplify circuits
compiler1 = tq.Compiler.basic(passes=tq.Compiler.SIMPLIFY_PASSES)
# a compiler that does randomized compiling
compiler2 = tq.Compiler.basic(passes=tq.Compiler.RC_PASSES)
# a compiler that decomposes two-qubit gates into the specified entangling gate,
# then randomly compiles, and then converts all gates into hardware compatable gates.
# for this example, we use the maximally entangling cross-resonance gate.
passes = (
tq.Compiler.NATIVE2Q_PASSES + tq.Compiler.RC_PASSES + tq.Compiler.HARDWARE_PASSES
)
compiler3 = tq.Compiler.basic(entangler=tq.Gate.rp("ZX", 90), passes=passes)
We demonstrate the last of these compilers by defining the following circuit:
circuit = tq.Circuit(
[{range(4): tq.Gate.h}, {(0, 1): tq.Gate.rp("ZZ", 22), (2, 3): tq.Gate.cz}]
).measure_all()
circuit
Circuit
|
||||
|
(0):
Gate.h
|
(1):
Gate.h
|
(2):
Gate.h
|
(3):
Gate.h
|
|
(0, 1):
Gate(ZZ)
|
(2, 3):
Gate.cz
|
  | |
1
|
(0):
Meas()
|
(1):
Meas()
|
(2):
Meas()
|
(3):
Meas()
|
We see that the compiled circuit contains only CNOT gates, Z rotations, and X90 rotations. Adjacent single qubit gates have been merged together on each qubit.
compiler3.compile(circuit)
Circuit
|
||||
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
|
(0):
sx()
|
(1):
sx()
|
(2):
sx()
|
(3):
sx()
|
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
|
(0):
sx()
|
(1):
sx()
|
(2):
sx()
|
(3):
sx()
|
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
2
|
(0, 1):
entangler()
|
  | ||
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
|
(0):
sx()
|
(1):
sx()
|
(2):
sx()
|
(3):
sx()
|
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
|
(0):
sx()
|
(1):
sx()
|
(2):
sx()
|
(3):
sx()
|
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
3
|
(0, 1):
entangler()
|
(2, 3):
entangler()
|
  | |
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
|
(0):
sx()
|
(1):
sx()
|
(2):
sx()
|
(3):
sx()
|
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
|
(0):
sx()
|
(1):
sx()
|
(2):
sx()
|
(3):
sx()
|
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
1
|
(0):
Meas()
|
(1):
Meas()
|
(2):
Meas()
|
(3):
Meas()
|
Custom Pass Lists¶
Custom lists of passes can be used if none of the predifined lists have the desired
behaviour. We can pass lists of these classes to
from_config()
or
basic()
, as discussed above, or we can use the
compiler constructor directly, as in the following example.
First, we define a device configuration. Here, we use the Berkeley gate as the entangling operation.
b = tq.Gate.from_generators("XX", 90, "YY", 45)
config = tq.Config(
factories=[
tq.config.GateFactory.from_matrix("B", b),
tq.config.GateFactory.from_hamiltonian("x90", [["X", 90]]),
tq.config.GateFactory.from_hamiltonian("z", [["Z", "phi"]]),
],
mode="ZXZXZ",
)
Next, we define a compiler. Unlike,
HARDWARE_PASSES
, this compiler starts by
merging adjacent two-qubit gates. Also, we know that any two qubit gate can be
decomposed into two Berkeley gates interleaved with single qubit gates. We use
tq.compilation.Native2QFastDecomp
fixed at depth=2
to force every
two-qubit gate to decompose into two Berkeley gates, even if only one is required.
decomposer = tq.compilation.Native2QFastDecomp(depth=2, factories=config.factories)
compiler = tq.Compiler(
[
tq.compilation.Merge(n_labels=2),
tq.compilation.Parallel(decomposer),
tq.compilation.Merge(),
tq.compilation.Native1Q(config.factories),
tq.compilation.RemoveEmptyCycle(),
]
)
Out:
Warning: Native2QFastDecomp() was deprecated in version 2.9.0 and will be removed no earlier than version 2.10.0. The more general `NativeDecomp` pass can be used as a drop in replacement.
(/home/user/jenkins/workspace/release trueq/trueq/utils.py:153)
Next, we define a circuit to test this compiler on.
circuit = tq.Circuit(
[
{range(4): tq.Gate.h},
{(0, 1): tq.Gate.rp("ZZ", 22)},
{(0, 1): tq.Gate.cnot, (2, 3): tq.Gate.swap},
]
).measure_all()
circuit
Circuit
|
||||
|
(0):
Gate.h
|
(1):
Gate.h
|
(2):
Gate.h
|
(3):
Gate.h
|
|
(0, 1):
Gate(ZZ)
|
  | ||
|
(0, 1):
Gate.cx
|
(2, 3):
Gate.swap
|
  | |
1
|
(0):
Meas()
|
(1):
Meas()
|
(2):
Meas()
|
(3):
Meas()
|
The resulting compiled circuit is as follows.
compiler.compile(circuit)
Circuit
|
||||
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
|
(0):
x90()
|
(1):
x90()
|
(2):
x90()
|
(3):
x90()
|
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
|
(0):
x90()
|
(1):
x90()
|
(2):
x90()
|
(3):
x90()
|
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
|
(0, 1):
B()
|
(2, 3):
B()
|
  | |
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
|
(0):
x90()
|
(1):
x90()
|
(2):
x90()
|
(3):
x90()
|
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
|
(0):
x90()
|
(1):
x90()
|
(2):
x90()
|
(3):
x90()
|
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
|
(0, 1):
B()
|
(2, 3):
B()
|
  | |
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
|
(0):
x90()
|
(1):
x90()
|
(2):
x90()
|
(3):
x90()
|
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
|
(0):
x90()
|
(1):
x90()
|
(2):
x90()
|
(3):
x90()
|
|
(0):
z(phi)
|
(1):
z(phi)
|
(2):
z(phi)
|
(3):
z(phi)
|
1
|
(0):
Meas()
|
(1):
Meas()
|
(2):
Meas()
|
(3):
Meas()
|
Total running time of the script: ( 0 minutes 0.145 seconds)