Imports and Exports QASM circuit

Notebook Author: Sidhant Saraogi(sid1397@gmail.com)

This notebook introduces the OpenQASM import and export functions. It can also serve as a short introduction to the QASM format. The Quantum Assembly Language(QASM) acts as an intermediate representation for Quantum Circuits. This is one way to export/import from/to with QuTiP. In this way, we can make the QIP module of QuTiP compatible with Qiskit and Cirq.

In [1]:
from qutip_qip.qasm import read_qasm
from qutip import rand_ket, tensor, basis
from qutip_qip.circuit import Measurement
import numpy as np

The process is quite simple and only requires the user to store the .qasm file in an appropriate location and maintain the absolute path of the file. This will reading the file simpler. For this demonstration, we already saved a few qasm circuit examples in the directory qasm_files. You can find more examples at OpenQASM repository Let's start off by reading one of the examples:

In [2]:
path = "qasm_files/swap.qasm"
qasm_file = open(path, "r")
print(qasm_file.read())
// SWAP gate impemented in terms of cx's

OPENQASM 2.0;
include "qelib1.inc";

qreg q[2];

cx q[1], q[0];
cx q[0], q[1];
cx q[1], q[0];

Qasm Import

This QASM file imitates the SWAP gate native to QuTiP in the QASM format. To import it, we use the read_qasm function with the arguments being the file path, the mode which defaults to "qiskit" and the version which defaults to "2.0".

We can check that the circuit indeed implements the swap gate by checking the unitary matrix corresponding to the circuit. This can be done by using the gate_sequence_product function and the propagators function of the QubitCircuit class.

In [3]:
from qutip_qip.operations.gates import gate_sequence_product
from qutip import tensor, basis

qc = read_qasm(path, mode="qiskit", version="2.0")
gate_sequence_product(qc.propagators()) 
Out[3]:
Quantum object: dims = [[2, 2], [2, 2]], shape = (4, 4), type = oper, isherm = True\begin{equation*}\left(\begin{array}{*{11}c}1.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & 1.0 & 0.0\\0.0 & 1.0 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 1.0\\\end{array}\right)\end{equation*}

The mode refers to the internal way in which QuTiP processes the QASM files. With "qiskit" mode, QASM skips the include command for the file qelib1.inc and maps all custom gates defined in it to QuTiP gates without parsing the gate definitions.

Note: "qelib1.inc" is a "header" file that contains some QASM gate definitions. It is available in the OpenQASM repository (as a standard file) and is included with QASM exports by QuTiP (and also by Qiskit/Cirq).

The version refers to the version of the OpenQASM standard being processed. The documentation for the same can be found in the OpenQASM repository. Currently, only OpenQASM 2.0 is supported which is the most popular QASM standard.

QASM Export

We can also convert a QubitCircuit to the QASM format. This can be particularly useful when we are trying to export quantum circuits to other quantum packages such as Qiskit and Cirq. There are three different ways to output QASM files, print_qasm, str_qasm and write_qasm.

In [4]:
from qutip_qip.qasm import print_qasm

print_qasm(qc)
// QASM 2.0 file generated by QuTiP

OPENQASM 2.0;
include "qelib1.inc";

qreg q[2];

cx q[1],q[0];
cx q[0],q[1];
cx q[1],q[0];

Custom Gates

QASM also offers the option to define custom gates in terms of already defined gates using the "gate" keyword. In "qiskit" mode, our QASM interpreter can be assumed to already allow for all the gates defined in the file qelib1.inc provided by the OpenQASM repository.

In the file swap_custom.qasm, we define the swap gate in terms of the pre-defined cx gates.

In [5]:
path = "qasm_files/swap_custom.qasm"
qasm_file = open(path, "r")
print(qasm_file.read())  
// SWAP gate defined as a custom gate.

OPENQASM 2.0;
include "qelib1.inc";

qreg q[2];
creg c[2];

gate swap a, b{
cx b, a;
cx a, b;
cx b, a;
}

swap q[0], q[1]

measure q -> c

Furthermore, the circuit also measures the two qubits q[0] and q[1] and stores the results in the classical registers c[0] and c[1]

In [6]:
qc = read_qasm(path)

We can now run the circuit to confirm that the circuit is correctly loaded and performs the correct operations. To do this, we can use the QubitCircuit.run function with the appropriate input state. In our case, we can take the state |01⟩.

In [7]:
from qutip import tensor, basis

qc.run(tensor(basis(2, 0), basis(2, 1)))
Out[7]:
Quantum object: dims = [[2, 2], [1, 1]], shape = (4, 1), type = ket\begin{equation*}\left(\begin{array}{*{11}c}0.0\\0.0\\1.0\\0.0\\\end{array}\right)\end{equation*}

As predicted the output is the state after swapping which is |10⟩

Measurements and Classical Control

The QASM format also allows for other circuit features such as measurement and control of gates by classical bits. This is also supported by QuTiP. For an example, we can refer to the example of quantum teleportation. A more complete explanation of teleportation can be found in the notebook on quantum teleportation.

In [8]:
path = "qasm_files/teleportation.qasm"
qasm_file = open(path, "r")
qasm_str = qasm_file.read()
print(qasm_str)
OPENQASM 2.0;
include "qelib1.inc";

qreg q[3];
creg c0[1];
creg c1[1];

h q[1];
cx q[1], q[2];
cx q[0], q[1];
h q[0];

measure q[0] -> c1[0]
measure q[1] -> c0[0]

if(c0==1) x q[2]
if(c1==1) z q[2]

We can also read in a QASM file from a string by specifying strmode=True to read_qasm

In [9]:
teleportation = read_qasm(qasm_str, strmode=True)
e:\boxi\onedrive\studium\qutip-project\qutip\qutip\qip\qasm.py:684: UserWarning: Information about individual registers is not preserved in QubitCircuit
  warnings.warn(("Information about individual registers"

Note: The above warning is expected to inform the user that the import from QASM to QuTiP does not retain any information about the different qubit/classical bit register names. This could potentially be an issue when the circuit is exported if the user wants to maintain the consistency.

We can quickly check that the teleportation circuit works properly by teleporting the first qubit into the third qubit.

In [14]:
state = tensor(rand_ket(2), basis(2, 0), basis(2, 0))

initial_measurement = Measurement("start", targets=[0])
_, initial_probabilities = initial_measurement.measurement_comp_basis(state)

state_final = teleportation.run(state)

final_measurement = Measurement("start", targets=[2])
_, final_probabilities = final_measurement.measurement_comp_basis(state_final)

np.testing.assert_allclose(initial_probabilities, final_probabilities)

Note: Custom gates imported in the QASM format cannot be easily exported. Currently, only gates that are defined native to QuTiP can be exported. QuTiP also produces custom gate definitions for gates not provided in the qelib1.inc "header" file. In these cases, QuTiP will add it's own gate definitions directly to the the exported .qasm file but this is restricted only to already gates native to QuTiP. Export from QuTiP handles both gates and measurements. However, it does not allow for export of controlled gates.

In [15]:
from qutip.ipynbtools import version_table
version_table()
Out[15]:
SoftwareVersion
QuTiP4.6.0+c003ff5
Numpy1.20.1
SciPy1.5.3
matplotlib3.3.0
Cython0.29.21
Number of CPUs12
BLAS InfoGeneric
IPython7.16.1
Python3.8.6 | packaged by conda-forge | (default, Oct 7 2020, 18:22:52) [MSC v.1916 64 bit (AMD64)]
OSnt [win32]
Mon Apr 12 19:27:45 2021 W. Europe Daylight Time