Table of Contents
Introduction
Over the past two artcles we have covered what quantum computing is and how qubits are acted upon by quantum gates. However, this alone is not enough to perform quantum computations. What is needed is a way to put quantum gates together to performed specific functions (i.e. calculations). This is the purpose of quantum circuits.
At its most basic, a quantum circuit is a description of the order quantum gates are applied to one or more qubits in order to execute a specific function. For example, if you want to do a apply a CNOT gate to the second qubit, after flipping the first qubit, the circuit needs to show a Pauli-X Gate being applied to the first qubit, afterwhich a CNOT gate is applied to the second qubit. Doing these operations in reverse order would produce completely different results.
In order to assist in designing quantum circuits, a simple programming language has been developed for describing quantum cirucits - OpenQASM (Open Quantum ASseMbly). The language can be used to describe any quantum gate as well as specify which qubits are acted upon by which gates and in what order. After an overview of quantum circuits, we will delve into the OpenQASM language and how to write programs in it.
Quantum Circuits
Above is a basic quantum circuit with three qubits and several gates. Although circuit diagrams may vary, depending how they are drawn, the majority, if not all, contain the following key elements:
- Qubits: Each horizontal line in the circuit represents a specific qubit. When drawn horizontally, time flows from left to right; when drawn vertically, time flows from top to bottom (unless specified otherwise).
- Identifier/Starting State: To the left of each qubit line (or the top when drawn vertically) there is usually an identifier. For example \(q_1\), \(q_2\), \(q_3\), etc. would present qubits 1, 2, 3, etc. Unless specified otherwise, all qubits are assumed to be starting in state \(|0\rangle\). Sometimes instead of q, c is used. In these cases, the c refers to a classical bit instead of a qubit. Also, instead of an identified, a starting state can be listed. In the above diagram, the first qubit is starting in state \(|x_0\rangle\), the second qubit is in state \(|x_1\rangle\), and so on.
- Identifier/Ending State: You will notice in the diagram that to the right of each line is a different state than what is listed to the left. In this case, \(|y_0\rangle\), \(|y_1\rangle\) and \(|y_2\rangle\) represents the states of each qubit after the circuit has finished running.
- Gates: A quantum circuit could not do anything without gates. Each of the square boxes represents a gate, with the text inside describing what kind of gate (for example H is a Hadamard gate). When a gate has a line connecting to another qubit, it is a controlled gate, like a CNOT gate, with the qubit the box is on being the qubit being acted on and the line connecting to the controlling qubit.
That is really all there is to a quantum circuit. Follow the flow of time, left-to-right or top-to-bottom, and modify each qubits state according to the gate that is operating on the qubit. If two gates, on two separate qubit lines, are lined up vertically (or horizontally when the diagram itself is veritcal), it means that the gates modify each qubit at the same time.
OpenQASM
Although quantum circuit diagrams are good to visually see how a quantum circuit is operating, it is not very useful to program a quantum circuit with, and is not efficient for simulating quantum circuits. This is where OpenQASM shines. It is a relatively simple programming language that can be written on a classical computer to either generating a quantum circuit diagram, be used to program a quantum circuit (as we will see if Part IV), or even be used as an interpreted language to simulate quantum circuits on classical computers.
Below is the grammar for an OpenQASM program. A copy of this grammar may also be found in Appendix A of the OpenQASM Specification. Don't worry if you do not understand it right away. We will go over the main parts of an OpenQASM and describe how a program is written. Please note that in the grammar description, bolded text (e.g. \(\mathbf{OPENQASM}\)), puncuation (e.g. commas, semicolons, square brackets, etc.) and generally anything not enclosed within angled brackets (i.e. \(\langle\rangle\)) are string literals and would be typed into an OpenQASM program exactly as presented. This should become clearer once we start writting demo OpenQASM programs.
OpenQASM Grammar
\(\langle\)mainprogram\(\rangle\) | \(\vDash\) | \(\mathbf{OPENQASM}\langle\)real\(\rangle\mathbf{;}\langle\)program\(\rangle\) |
\(\langle\)program\(\rangle\) | \(\vDash\) | \(\langle\)statement\(\rangle\vert\langle\)program\(\rangle\langle\)statement\(\rangle\) |
\(\langle\)statement\(\rangle\) | \(\vDash\) | \(\langle\)decl\(\rangle\) |
| | \(\vert\langle\)gatedecl\(\rangle\langle\)goplist\(\rangle\mathbf{\}}\) |
| | \(\vert\langle\)gatedecl\(\rangle\mathbf{\}}\) |
| | \(\vert\mathbf{opaque\langle}\)id\(\rangle\langle\)idlist\(\rangle\mathbf{;}\) |
| | \(\vert\mathbf{opaque\langle}\)id\(\rangle\mathbf{( )}\langle\)idlist\(\rangle\mathbf{;}\vert\mathbf{opaque}\langle\)id\(\rangle\mathbf{(}\langle\)idlist\(\rangle\mathbf{)}\langle\)idlist\(\rangle\mathbf{;}\) |
| | \(\vert\langle\)qop\(\rangle\) |
| | \(\vert\mathbf{if ( }\langle\)id\(\rangle\mathbf{==}\langle\)nninteger\(\rangle\mathbf{)}\langle\)qop\(\rangle\) |
| | \(\vert\mathbf{barrier}\langle\)anylist\(\rangle\mathbf{;}\) |
\(\langle\)decl\(\rangle\) | \(\vDash\) | \(\mathbf{qreg}\langle\)id\(\rangle\mathbf{[}\langle\)nninteger\(\rangle\mathbf{];}\vert\mathbf{creg}\langle\)id\(\rangle\mathbf{[}\langle\)nninteger\(\rangle\mathbf{];}\) |
\(\langle\)gatedecl\(\rangle\) | \(\vDash\) | \(\mathbf{gate}\langle\)id\(\rangle\langle\)idlist\(\rangle\mathbf{\{}\) |
| | \(\vert\mathbf{gate}\langle\)id\(\rangle\mathbf{()}\langle\)idlist\(\rangle\mathbf{\{}\) |
| | \(\vert\mathbf{gate}\langle\)id\(\rangle\mathbf{(}\langle\)idlist\(\rangle\mathbf{)}\langle\)idlist\(\rangle\mathbf{\{}\) |
\(\langle\)goplist\(\rangle\) | \(\vDash\) | \(\langle\)uop\(\rangle\) |
| | \(\vert\mathbf{barrier}\langle\)idlist\(\rangle\mathbf{;}\) |
| | \(\vert\langle\)goplist\(\rangle\langle\)uop\(\rangle\) |
| | \(\vert\langle\)goplist\(\rangle\mathbf{barrier}\langle\)idlist\(\rangle\mathbf{;}\) |
\(\langle\)qop\(\rangle\) | \(\vDash\) | \(\langle\)uop\(\rangle\) |
| | \(\vert\mathbf{measure}\langle\)argument\(\rangle\mathbf{->}\langle\)argument\(\rangle\mathbf{;}\) |
| | \(\vert\mathbf{reset}\langle\)argument\(\rangle\mathbf{;}\) |
\(\langle\)uop\(\rangle\) | \(\vDash\) | \(\mathbf{U ( }\langle\)explist\(\rangle\mathbf{)}\langle\)argument\(\rangle\mathbf{;}\) |
| | \(\vert\mathbf{CX}\langle\)argument\(\rangle\mathbf{,}\langle\)argument\(\rangle\mathbf{;}\) |
| | \(\vert\langle\)id\(\rangle\langle\)anylist\(\rangle\mathbf{;}\vert\langle\)id\(\rangle\mathbf{( )}\langle\)anylist\(\rangle\mathbf{;}\) |
| | \(\vert\langle\)id\(\rangle\mathbf{(}\langle\)explist\(\rangle\mathbf{)}\langle\)anylist\(\rangle\mathbf{;}\) |
\(\langle\)anylist\(\rangle\) | \(\vDash\) | \(\langle\)idlist\(\rangle\vert\langle\)mixedlist\(\rangle\) |
\(\langle\)idlist\(\rangle\) | \(\vDash\) | \(\langle\)id\(\rangle\vert\langle\)idlist\(\rangle\mathbf{,}\langle\)id\(\rangle\) |
\(\langle\)mixedlist\(\rangle\) | \(\vDash\) | \(\langle\)id\(\rangle\mathbf{[}\langle\)nninteger\(\rangle\mathbf{]}\vert\langle\)mixedlist\(\rangle\mathbf{,}\langle\)id\(\rangle\) |
| | \(\vert\langle\)mixedlist\(\rangle\mathbf{,} \langle\)id\(\rangle\mathbf{[}\langle\)nninteger\(\rangle\mathbf{]}\) |
| | \(\vert\langle\)idlist\(\rangle\mathbf{,} \langle\)id\(\rangle\mathbf{[}\langle\)nninteger\(\rangle\mathbf{]}\) |
\(\langle\)argument\(\rangle\) | \(\vDash\) | \(\langle\)id\(\rangle\vert\langle\)id\(\rangle\mathbf{[}\langle\)nninteger\(\rangle\mathbf{]}\) |
\(\langle\)explist\(\rangle\) | \(\vDash\) | \(\langle\)exp\(\rangle\vert\langle\)explist\(\rangle\mathbf{,}\langle\)exp\(\rangle\) |
\(\langle\)exp\(\rangle\) | \(\vDash\) | \(\langle\)real\(\rangle\vert\langle\)nninteger\(\rangle\vert\mathbf{pi}\vert\langle\)id\(\rangle\) |
| | \(\vert\langle\)exp\(\rangle+\langle\)exp\(\rangle\vert\langle\)exp\(\rangle-\langle\)exp\(\rangle\vert\langle\)exp\(\rangle*\langle\)exp\(\rangle\) |
| | \(\vert\langle\)exp\(\rangle/\langle\)exp\(\rangle\vert-\langle\)exp\(\rangle\vert\langle\)exp\(\rangle)^\(\langle\)exp\(\rangle\) |
| | \(\vert\mathbf{(}\langle\)exp\(\rangle\mathbf{)}\vert\langle\)unaryop\(\rangle\mathbf{(}\langle\)exp\(\rangle\mathbf{)}\) |
\(\langle\)unaryop\(\rangle\) | \(\vDash\) | \(\mathbf{sin}\vert\mathbf{cos}\vert\mathbf{tan}\vert\mathbf{exp}\vert\mathbf{ln}\vert\mathbf{sqrt}\) |
The unlisted productions \(\langle\)id\(\rangle\), \(\langle\)real\(\rangle\), and \(\langle\)nninteger\(\rangle\) are defined by the following regular expressions:
$ \mathbf{id} := \mathbf{[a-z][A-Za-z0-9\_]*} \\ \mathbf{real} := \mathbf{([0-9]+\backslash.[0-9]*|[0-9]*\backslash.[0-9]+)([eE][-+]?[0-9]+)?} \\ \mathbf{nninteger} := \mathbf{[1-9]+[0-9]*|0} $
Beginning an OpenQASM Program
An OpenQASM program has elements of both C and Assembly. The first non-comment line of a program must be OPENQASM M.m; where M is the major version and m is the minor version. As of this time the current version is 2.0 and everything in this article is in reference to this version. For all OpenQASM programs:
- The OPENQASM M.m; statement can only appear one time and must be the first, non-comment statement.
- Statements are separated by ; (like C).
- Whitespace is ignored.
- Comments begin with a pair of forward slashes (i.e. //) and end with a new line.
- Identifiers (such as the name of a gate) must start with a lowercase letter and con contain alphanumeric characters and underscores.
- The language is case-sensitive.
- The statement include "filename"; continues parsing filename and treats the contents of the file as if its text were pasted at the location of the include statement. The path of filename is specified relative to the current working directory
The only type of storage in V2 are classical and quantum registers. These are one-dimensional array of bits and qubits, respectively. A quantum register is defined using qreg name[size]; and a classical register is defined using creg name[size].. To refer to a specific bit/qubit, the label name[j] is used where \(j \in {0,1,...,size(name)-1}\). All qubits are initialized to \(\vert 0 \rangle\) and all classical bits are initialized to 0.
Built-In Gates
For IBM Quantum Experience platform, only two gates are implmented; the universal U gate and the controlled-not, C-NOT gate. However, you may use the statement include qelib1.inc to include a pre-built file which contains the definitions of many more gates. These additional gates include the Pauli gates, rotation gates, identity gate, Toffoli gate, Hadamard gate, etc. To see a full list of the gates implemented in this file, please see pages 10-12 of the specification.
The C-NOT gate we learned about in the last part of this series. However, the U gate is new. It is a single qubit, three paramater gate. All the gates in the qelib1.inc file are built up from the controlled-not gate and this gate. The matrix definition for this gate is:
$\begin{aligned} U(\theta, \phi, \lambda) = R_z(\phi)R_y(\theta)R_z(\lambda) = \begin{pmatrix} e^{-i(\phi+\lambda)/2}cos(\theta/2) & -e^{-i(\phi-\lambda)/2}sin(\theta/2) \\ e^{i(\phi-\lambda)/2}sin(\theta/2) & e^{i(\phi+\lambda)/2}cos(\theta/2) \end{pmatrix} \end{aligned}$
For users familiar with group theory, U specifies any element of SU(2).
Where:
$ R_y(\theta) = exp(-i\theta Y/2) \\ R_z(\phi) = exp(-i\theta Z/2) $
and
$ \theta \in [0,4\pi) \\ \phi \in [0, 4\pi) \\ \lambda \in [0, 4\pi) $
The values for theta, phi, and lambda are given by parameter expressions constructed using in-fix notation. For IBM Quantum Experience this includes scientific notation, real arithmetic, logarithmic, trigometric, and exponential functions, square roots, and the built-in constant \(\pi\). All real numbers use double precision floating point numbers. Unfortunately, the current version of OpenQASM does not have a way of computing parameters based on measurement outcomes.
A gate can be applied to either a quantum register or to a specific qubit. The statement U(theta, phi, lambda) a; means apply U(theta, phi, lambda) a[j]; for each index j in register a. However, U(theta, phi, lambda) a[0]; means to apply U(theta, phi, lambda) to only qubit 0 in register a.
Custom Gates
New gates must be defined before they are used. Gate definitions can be viewed as macros that are expanded at run-time. The basic format of a gate is as follows:
gate name(parameters) qargs
{
body
}
where parameters is an optional comma-separated list of variable parameters. The argument list, qargs is a comma-separated list of qubit arguments. Both the parameters names and qubit argements are identifies. The parameters variables are optional and if there are none, the parentheses can be excluded as well. However, at least one qubit argument is required as a gate must act on at least one qubit Also, a gate cannot call itself (i.e. recursion is not allowed).
As an example, lets create a gate that basically does nothing to a single qubit register. We will call this gate g and it can be written as:
gate g a
{
U(0, 0, 0) a;
}
An important thing to note is that the qargs arguments cannot be indexed within the body of a gate definition. That is, while the above is a valid gate definition, the following is not:
gate g a
{
U(0, 0, 0) a[0];
}
Assuming we have a quantum register that was definied as qreg qr0[1], the gate we just defined can be called two different ways. First, we can call it using just the quantum register name: g qr0. In this case, the gate would be applied to all qubits in this registered, which is a total of two for qr0. Second, we can call the gate with a specific qubit, such as g qr0[1]. In this case, the gate would only be applied to the second qubit of the qr0 register but not the first.
Statements Overview
Below is a table that provides a quick overview of the various statements that can make up an OpenQASM program. A copy of this table can be found on Page 9 of the OpenQASM specification:
Statement | Description | Example |
OPENQASM 2.0; | Denotes a file in OpenQASM format | OPENQASM 2.0; |
qreg name[size]; | Declares a named qubit register | qreg q[5]; |
creg name[size]; | Declares a named bit register | creg c[5]; |
include "filename" | Open and parse another source file | include "qelib1.inc"; |
gate name(params) qargs { body } | Declares a unitary gate | |
opaque name(params) qargs | Declares an opaque gate | |
// comment text | Comment line | // oops! |
U(theta, phi, lambda) qubit|qreg; | Apply the built-in single qubit gate(s) | U(pi/2, 2*pi/3, 0) q[0]; |
CX qubit|qreg, qubit|qreg; | Apply the built-in CNOT gate(s) | CX q[0], q[1]; |
measure qubit|qreg -> bit|creg; | Make a measurement(s) in the Z basis | measure q -> c; |
reset qubit|qreg; | Prepare qubit(s) in the \(\vert 0 \rangle\) state | reset q[0]; |
gatename(params) qargs; | Apply a user-defined unitary gate | crz(pi/2) q[1],q[0]; |
if(creg==int) qop; | Conditionally apply a quantum operation | if (c==5) CX q[0],q[1]; |
barrier qargs | Prevent transformations acros this source line | barrier q[0],q[1]; |
Other Statements
Besides built-in and custom gates, OpenQASM also provides other statements that can be used to affect how a quantum circuit operates.
Reset
The reset qubit|qreg; statement resets a specific qubit or an entire quantum register to the \(\vert 0 \rangle\) state.
The reset qubit|qreg; statement corresponds to a partial trace over those qubits (i.e. discarding them) before replacing them with \(\vert 0 \rangle \langle 0 \vert\).
Conditional If
The if statement is the only classically-controlled quantum operation that is currently available in OpenQASM. The if statement executes a quantum operation based upon the value of a classical register. The classical register is interpreseted as an integer value using the bit at index zero as the low order bit. For example, if the following line of code was in an OpenQASM program:
if(c==3) U(theta, phi, lambda) q[0];
The U gate operation would only be applied to the q[0] qubit if the classical register c was equal to the integer value 3.
Measure
The measure statement is used to actual retrieve the values of quantum operations. The statement measure qubit|qreg -> bit|creg; measures the qubit(s) in the Z-basis and records the result by overwritting the bit(s). Both arguements must be a register type or both must be a bit type. In the case where both arguments are register types, and have the same size, the statement measure q -> c; means do measure q[j] -> c[j] for each index j in register q.
Barrier
The barrier statement prevents optimizations from reordering gates across its source line. For example, if we had the following code:
CX r[0],r[1];
h q[0];
h s[0];
barrier r,q[0];
h s[0];
CX r[1],r[0];
CX r[0],r[1];
The barrier statement would provent an attempt to combine the CNOT gates but will allow the pair of h s[0]; gates to cancel.
Opaque
The opaque statement is used when a gate may be implemented physically but does not have a specified definition. In this case, you define an "opaque" gate declaration. The syntax for an opaque gate declaration is the same as for a regular gate declaration but without a body.
Program Examples
Below is three examples of OpenQASM programs that will demonstrate how to do a coin flip, generate a random number, and perform a majority function on three qubits. Each example includes the full OpenQASM program followed by a description of how it works. I recommend saving at least the first two examples to text files as we will actual simulate them, as well as run them on a real quantum computer, in Part IV of this series.
Coin Flip Examples
OPENQASM 2.0;
include "qelib1.inc"
qreg q[0];
creg c[0];
h q;
measure q -> c;
The above OpenQASM program performs a bit flip opertion. The first three lines are a comment dscribing what the program does, the version statement, and the include statement to include all the predefined gates on the IBM Quantum Experience platform. The qelib1.inc file must be included so that we can use the Hadamard (h) gate.
The next two lines define a one qubit register (q) and a one bit register (c). The next line applies the Hadamard gate to the q register and the last line measures the q register and places its value in the c register.
In Part II of this series we learned that the Hadamard gate is represented by the following matrix:
$\begin{aligned} H = \frac{1}{\sqrt{2}}\begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix} \end{aligned}$
and that it maps the \(\vert 0\rangle\) state (which the qubit was first initialized to) to \(\frac{\vert 0\rangle + \vert 1\rangle}{\sqrt{2}}\). This means that the qubit has an equal probability of either being \(\vert 0\rangle\) or \(\vert 1\rangle\) when measured on the last line. This program is also better than any classical computer program because classical computer are deterministic and cannot ever be truely random. This is not so for a quantum computer. The measurement on the last line is a truely random quantum process and should return \(\vert 0\rangle\) and \(\vert 1\rangle\) the same number of times over enough runs.
Random Number Example
OPENQASM 2.0;
include "qelib1.inc"
qreg q[4];
creg c[4];
h q;
measure q -> c;
As you can see, this program is almost identical to the coin flip example. The only difference is that we have increased the quantum and classical registers to five qubits and classical bits, respectively. When the Hadamard gate is applied to the quantum register, it applies the gate to each qubit individually. Therefore, each qubit has an equal probability of being \(\vert 0\rangle\) or \(\vert 1\rangle\), thereby randomly generating a number between 0 and 31. Again, since this will be run on a quantum processor, it is a true random number generator.
We could have made the registers as large as we want to, but the IBM Quantum Experience platform has a limit on the number of qubits it can simulate and the real processor only has five qubits. Therefore I have limited the register sizes to five qubits/bits so that we can run the program without any changes on the quantum computer in Part IV.
Conclusion
In this part we learned how to draw and intepret quantum circuit diagrams as well as write OpenQASM programs. In the next part, Part IV - IBM Quantum Experience, we will run the example programs presented in this section. They will be executed as a simulation as well as on a real quantum processor so that we can confirm that they are working successfully.
References
History
June 26, 2017: First version