# Example: Compilation Basics

As a whole, a given quantum circuit can be interpreted as a single unitary operation to be applied to an input state. In practice, circuits are decomposed into a universal set of primitive gates. This decomposition is generally far from unique; in fact, there are an infinite number of ways to decompose a circuit. Often, primitive gates are taken to be a the set of gates which are natively available to a particular quantum processor on which the circuits will be run.

A compiler in this context is a tool that takes a circuit, and returns a specific decomposition of that circuit. Compilers may not be deterministic; there are significant advantages to randomizing over some decompositions (see e.g. Randomized Compiling).

## Converting to a different gateset

A very important class of compilers are what we call transpilers: they are compilers that translate a given circuit decomposition containing a specific gateset into a decomposition that uses a different gateset. This is crucial for running circuits on physical deviced, where the gateset needs to consist solely of gates that are native to the hardware.

Two common issues that a transpiler needs to solve in order to generate a circuit that can be run on a physical device are:

1. Gates inside of theoretical circuits are not directly compatible with the hardware’s native gateset.

2. Gates or circuits are specified as unitary matrices, rather than specific gate decompositions.

If True-Q™ has access to the configuration of the system, it can be used to solve both of these issues. Our Compiler takes a circuit and rewrites it using only the gates and connections specified in a given device configuration which we represent through Config objects.

For example, let’s suppose we have a device that can only perform rotations about the $$X$$ and $$Z$$ axis and a $$CNOT$$ gate. We can define this configuration as follows:

[2]:

import matplotlib.pyplot as plt
import trueq as tq

# Define a device configuration
config = tq.Config.from_yaml(
"""
Mode: ZXZXZ
Gates:
- Z:
Hamiltonian:
- ['Z', 'phi']
- X90:
Hamiltonian:
- ['X', 90]
- CNOT:
Matrix:
- [1, 0, 0, 0]
- [0, 1, 0, 0]
- [0, 0, 0, 1]
- [0, 0, 1, 0]
"""
)


Once the configuration is defined, a transpiler can be constructed using the from_config() method:

[3]:

transpiler = tq.Compiler.from_config(config)


To see how this works in practice, consider the following circuit that contains the non-native SWAP gate:

[4]:

# Define a circuit
circuit = tq.Circuit([{(0, 1): tq.Gate.swap}])
circuit.draw()

[4]:


Using the transpiler, we can convert it into a new circuit:

[5]:

transpiled_circuit = transpiler.compile(circuit)
transpiled_circuit.draw()

[5]:


Notice that all of the gates in the new circuit are contained in the Config object, and that single-qubit gates are decomposed into sequences of the form $$Z(\theta)X(90)Z(\phi)X(90)Z(\gamma)$$, which is consistent with the ‘Mode’ specified in the configuration. Hovering your cursor over each gate will display the rotation angle.

We can verify numerically that this circuit implements the SWAP gate using an ideal simulator and calculating the process infidelity between the original SWAP gate and our transpiled circuit:

[6]:

swap_op = tq.Gate.swap.mat
transpiled_op = tq.Simulator().operator(transpiled_circuit).mat()

print("Process Infidelity: ", tq.math.proc_infidelity(swap_op, transpiled_op))

Process Infidelity:  4.440892098500626e-16


Recall that a process infidelity of zero indicates that the operations are equivalent up to (potentially) a global phase. In this example, our transpiled circuit does indeed introduce a phase difference, which we can examine when looking at the resulting operators more closely:

[7]:

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 3))

tq.plot_mat(swap_op, ax=ax1)
tq.plot_mat(transpiled_op, ax=ax2)

ax1.set_title("Original SWAP gate")
ax2.set_title("SWAP Gate expressed through native gates")

[7]:

Text(0.5, 1.0, 'SWAP Gate expressed through native gates')


For simple device configurations, the from_config() method is a convenient wrapper, but True-Q™ compilations tools are much more versatile. Take a look at the Defining Custom Compilers example for more information.