Quil Specification

Authors: Robert S. Smith; Rigetti & Co. Inc.; and contributors

Language Version: 2021.1 (DRAFT)

Note: This document is in DRAFT STATUS and is for review purposes only. It is being developed by the Quil-Lang group on GitHub.

1. Preamble

1.1. An Introduction to Quil

This is the language specification for Quil, a language for hybrid classical/quantum computations.

Quil is an instruction-based language; each line of a Quil program generally corresponds to a single, discrete action. Despite its resemblance, Quil is not an assembly language.

Note: An assembly language is a textual format for the machine code of a specific computer architecture. Quil is not that, and may be used for a variety of quantum computer architectures.

This is an example Quil program that simulates a coin flip:

H 0
MEASURE 0 ro[0]
Here, we can see the use of both classical data and quantum data. The qubit numbered 0 is prepared in a uniform superposition by the Hadamard gate H, and then measured in the computational basis, depositing the resulting bit into a classic bit register named ro.

The remainder of this document serves as a reference for all Quil language syntax constructs and their associated semantics.

1.2. Annexes

The Quil language is described throughout the ordinarily named sections of this document. There are additional sections, called annexes, which are optional extensions to the Quil language. These extensions may modify the syntax or semantics of the base specification in noted ways.

2. Operational Semantic Devices

2.1. Mathematical Preliminaries

Define \(\mathscr{B}\) to be a Hilbert space isomorphic to \(\mathbb{C}^2\). Some texts refer to this space as a qubit. We may refer to it as a qubit space.

We fix any orthonormal basis of \(V := \mathscr{B}^{\otimes n}\) and call it the computational basis. We use uppercase letters to refer to the vector space and the corresponding lowercase indexed letters with an overbar to refer to the computational basis elements (e.g., \(V\) for the vector space and \(\bar v_{*}\) for the basis elements). We order the computational basis as \(\bar v_0\) to \(\bar v_{2^n-1}\) so the \(k\)th bit of the index (i.e., the coefficient of \(2^k\)) of \(\bar v_{*}\) corresponds to the \(0\)th or \(1\)st basis of the \(k\)th factor of \(\mathscr{B}\) from the right. For example, let \(n=3\), and let the left, middle, and right tensor factors of \(\mathscr{B}\otimes\mathscr{B}\otimes\mathscr{B}\) be called \(R\), \(S\), and \(T\) respectively. Consider the basis element \(\bar v_4\). This index \(4\) is 100 in binary, and thus \(\bar v_4\) corresponds to \(\bar r_1\otimes \bar s_0\otimes \bar t_0\).

Note: Some texts might write \(v_4\) as either \(\vert 001\rangle\) or \(\vert 100\rangle\). We prefer the latter if we are to use Dirac notation.

Given \(V := \mathscr{B}\), we sometimes call \(\bar v_0\) the ground state or zero state, and \(\bar v_1\) the excited state or one state.

Define \(\mathscr{U}(d)\) to be the projective special unitary group of dimension \(d\).

Note: Note that many texts write "PSU" instead of "U". We will often say "unitary" when we really mean "projective special unitary".

2.2. The Quantum Abstract Machine

The semantics of Quil are defined operationally; that is, each instruction of a Quil program is described by a change of some state. This state is described by a mathematical object called the "quantum abstract machine".

A quantum abstract machine or QAM is specified as:

This forms a \(6\)-tuple \((\Phi, C, G, G', P, \kappa)\). We may refer to such a tuple as a QAM.

3. Structure of a Quil Program

3.1. Meta-Syntax

In order to describe the syntax and semantics of a Quil program, we use a syntax that is similar to extended Backus-Naur form, though we deviate occasionally for convenience. While Quil's grammar is context-free, specifying it is somewhat laborious due to the possibility of identifiers being able to be used in some syntactic constructs and other times not. As such, for syntactic constructs which permit identifiers, we would need one set of productions, and for those which don't, we would need another (nearly identical) set.

In order to avoid this laborious repetition, we write productions of the grammar to always include sometimes forbidden elements, and instead specify in the surrounding text the context in which those elements are or are not allowed.

Note: When writing a recursive-descent parser, one would likely use contextual flags to allow or disallow certain parsing rules, like flags indicating the permission or lack thereof to use identifiers. This leads to considerably shorter and clearer code.

3.2. Syntactic Rudiments

Before proceeding to describe each component of a Quil program, it will be useful to establish a few common pieces of syntax which will be used later.

The Quil language is represented as text. The text must be encoded as UTF-8. The standard language constructs of Quil are all expressible in the ASCII subset of UTF-8, but user programs may use codepoints outside of UTF-8.

Except when noted explicitly, whitespace has no significance and is ignored. Tokens can be separated by arbitrary amounts and kinds of whitespace.

A newline is a single ASCII newline.

Newline ::= ASCII 10

A terminator is used to terminate most components of a Quil program syntactically.

Terminator ::=Newline
| ;
|End of File

An indent is defined as exactly four spaces at the start of a line. Indents in Quil programs can only happen following a newline.

Indent ::= NewlineASCII 324

Note that since indents must follow a newline, we include the newline as a part of the syntax definition of an indent.

Non-negative integers are written as usual. Leading zeros do not change the interpretation of these numeric literals.

Integer ::= [0-9]+

Real numbers are written in usual floating-point number syntax.

Real ::= Integer?.?Integer((e | E)(- | +)?Integer)?

Complex numbers are an extension of this.

Complex ::=Integer
|Real? i
| pi

Strings are characters bounded by double-quotation mark characters '"'. If a double-quotation mark should be used within the string, it must be escaped with a backslash, like so: '\"'. Similarly, if a backslash should be used within a string, it must be escaped, like so: '\\'.

String ::= "([^\"] | \" | \\)*"

Identifiers in Quil are alphanumeric Latin characters, along with hyphens and underscores. Identifiers cannot start or end with a hyphen '-'.

Identifier ::=[A-Za-z_]
| [A-Za-z_][A-Za-z0-9\-_]*[A-Za-z0-9_]

However, the following are not identifiers:

i pi

The following identifiers are reserved (as Quil keywords):


The following identifiers are also reserved (for standard gate definitions):


A formal parameter is an identifier prefixed with a percent sign, with no whitespace in between.

Parameter ::= %Identifier

Multiple parameters may be separated by commas.

Parameters ::= (Parameter(, Parameter)*)?

A formal argument is simply an identifier.

Argument ::= Identifier

Several formal arguments are separated by spaces, unlike parameters.

Arguments ::= Argument+

3.2.1. Arithmetic Expressions

Frequently, various kinds of arithmetic expressions are needed. A simple and not unusual grammar defines these arithmetic expressions.

Depending on the specific grammatical context, arithmetic expressions may or may not include references to formal parameters or memory segments. Below, we define the grammar as including all of these things, but certain contexts may disallow either or both of them.

Precedence of binary operators is defined in the following order of descending tightness. Along with the operators are their associativity directions.

^     RIGHT
* /   LEFT
+ -   LEFT

Expression ::=Expression + Expression
|Expression - Expression
|Expression * Expression
|Expression / Expression
|Expression ^ Expression
Term ::=- Expression
|Identifier ( Expression )
| ( Expression )
|Memory Reference

A constant expression is an arithmetic expression which does not contain any parameters, memory references, or identifiers.

We use comma-separated lists of arithmetic expressions frequently enough to warrant their own production.

Expression List ::= Expression(, Expression)*

3.3. Main Program Elements

A Quil program consists of declarations, directives, and instructions.

Program ::= Program Element*

Program Element ::=Declaration(Newline | End of File)

A declaration typically specifies the existence of a named object, like classical memory registers.

Declaration ::=Gate Definition
|Circuit Definition
|Classical Memory Declaration

A directive specifies information to software processing Quil, such as the INCLUDE directive for including files.

Directive ::=Pragma
|File Include

An instruction is an actual run-time executable effect.

Instruction ::=Gate Application
|Measurement Instruction
|Circuit Application
|Classical Memory Instruction
|Reset Instruction
|Wait Instruction
|Branch Instruction
|No-Operation Instruction
|Halt Instruction


Comments may exist syntactically, but do not change the semantics of the program. Text including and following the '#' character are ignored up to the end of the line.

Comment ::= #[^\n]*

There are no block comments.

4. Quantum Gates

4.1. Qubits and Quantum State

A Quil program manipulates quantum resources called qubits. Qubits are indexed by non-negative integers.

Qubit ::= Integer

Qubit indexes have no significance on their own. Qubits must always be referred to by their index. There is no bound on the number of qubits in a Quil program, and any finite collection of qubits may interact.

Quil has no ways to allocate an unbounded or non-deterministic number of qubits at run-time. The number of qubits used by a program can always be statically determined.

Sometimes, a qubit may instead have a formal argument in its place. This may not be possible in all cases.

Formal Qubit ::=Qubit

4.2. Quantum Gate Definitions

4.2.1. Structure of a Gate Definition

A gate definition allows us to name a unitary operation for subsequent use in a program. A gate definition in general has the following structure:

DEFGATE <name>(<params>) AS <kind>:

Here, the <name> names the gate, the <kind> states how we are defining the gate, and the <body> depends on the kind. For certain gates, <params> specifies parameters to the gate.

Gate Definition ::=Matrix Gate Definition
|Permutation Gate Definition
|Pauli Sum Gate Definition

4.2.2. Definition by Matrix

A gate can be defined by its matrix of complex numbers represented in the computational basis with the aforementioned ordering.

Matrix Gate Definition ::= DEFGATE Identifier((Parameters))?(AS MATRIX)?: Matrix Entries

A gate definitions with no parameters represents a static gate. Otherwise it is a parametric gate.

For readability, matrix entries are typically broken up into lines.

Matrix Entries ::= (IndentMatrix Entry Line)+

Each line contains a list of comma-separated arithmetic expressions, most often simple integers or real numbers.

Matrix Entry Line ::= Expression(, Expression)*

The arithmetic expressions may either be constant or refer to the parameters of the defined gate.

4.2.3. Definition by Permutation

A permutation gate is one that permutes the coefficients of the wavefunction. A permutation \(p\) can be specified mathematically as an ordering of the integers between \(0\) and \(n-1\) written out as a list \[(p_1\;p_2\;\ldots\;p_n).\] Here, \(p\) is a map on vectors such that if \(x\) is a column vector \[(x_1,\ldots,x_n)^{\intercal}\] then \(p(x)\) is a column vector \[(x_{p_1}, x_{p_2}, \ldots, x_{p_n})^{\intercal}.\] We specify permutation gates exactly by these numbers.

Permutation Gate Definition ::= DEFGATE Identifier AS PERMUTATION:IndentPermutation

Here, the permutation is a comma-separated list of non-negative integers.

Permutation ::= Integer(, Integer)+

There must be at least two integers specified, and the number of integers specified must be a power-of-two.

4.2.4. Definition by Pauli Sum

Quantum gates can be defined by an associated Hamiltonian, as in \[U(\mathbf t) = \exp(-i \mathcal H(\mathbf t))\] for a Hermitian operator \(\mathcal H\). Quil allows \(\mathcal H\) to be specified as a Pauli sum, a sum of combinations of Pauli operators.

The syntax is as follows.

Pauli Sum Gate Definition ::= DEFGATE Identifier((Parameters))?Arguments AS PAULI-SUM:Pauli Terms

The collection of Pauli term must be written on their own lines.

Pauli Terms ::= (IndentPauli Term)+

Each Pauli term represents some coefficient multiplied against a tensor product of Pauli operators.

Pauli Term ::= Pauli Word(Expression) Arguments

A PAULI-SUM gate definition is subject to some restrictions:

Pauli Word ::= (I | X | Y | Z)+ Semantics

We describe how one is intended to extract a matrix presentation of an operator from such a Pauli sum, and then we remit the discussion of semantics to that for Matrix Gate Definition.

  1. Pad each Pauli word appearing in the sum with I letters, so that all formal qubits appear in all terms.
  2. Sort the qubit arguments appearing in each term to agree with the qubit argument list in the definition header. Simultaneously, sort the letters appearing in the Pauli word to match.
  3. Using the Quil-standard matrix definitions of I, X, Y, and Z, associate to each Pauli term's Pauli word the ordered tensor product of these basic matrices.
  4. Scale each such matrix by the Pauli term's Expression.
  5. Sum the matrices, multiply by \(i=\sqrt{-1}\), and form the matrix exponential.

See the next sections for an example semantic reduction. Example Syntax

Many standard examples of gates admit short expression in these terms:

    Y(-%theta/2) q
This also includes many standard multi-qubit operators:
    ZZ(-%theta/4) p q
    Z(%theta/4) p
    Z(%theta/4) q

DEFGATE CAN(%alpha, %beta, %gamma) p q AS PAULI-SUM:
    XX(%alpha/4) p q
    YY(%beta/4)  p q
    ZZ(%gamma/4) p q
It also includes some operators encountered in practice, e.g., the following reduction of an Ansatz appearing in the electronic structure simulation problem for \(\mathrm H_2\):
DEFGATE UCC-H2(%theta) p q r s AS PAULI-SUM:
    YXXX(%theta) p q r s Example Semantic Reduction of CPHASE

In the example definition of CPHASE above, these steps proceed as follows:

  1. We replace ZZ p q with ZZ p q (i.e., no change), Z p by ZI p q, and Z q by ZI q p, which each now apply to all the available formal qubits.
  2. We replace ZZ p q by ZZ p q (i.e., no change), ZI p q by ZI p q, and ZI q p by IZ p q, which now all end in p q.
  3. The tensor products associated to these three terms are respectively \[\begin{align*} ZZ &= \left( \begin{smallmatrix}1 \\ & -1 \\ & & -1 \\ & & & 1 \end{smallmatrix} \right) & ZI &= \left( \begin{smallmatrix}1 \\ & 1 \\ & & -1 \\ & & & -1 \end{smallmatrix} \right) & IZ &= \left( \begin{smallmatrix}1 \\ & -1 \\ & & 1 \\ & & & -1 \end{smallmatrix} \right), \end{align*}\] where we have elided zero entries.
  4. After rescaling by the associated expressions, these matrices become \[\left( \begin{smallmatrix}-\theta/4 \\ & \theta/4 \\ & & \theta/4 \\ & & & -\theta/4 \end{smallmatrix} \right) \qquad \left( \begin{smallmatrix}\theta/4 \\ & \theta/4 \\ & & -\theta/4 \\ & & & -\theta/4 \end{smallmatrix} \right) \qquad \left( \begin{smallmatrix}\theta/4 \\ & -\theta/4 \\ & & \theta/4 \\ & & & -\theta/4 \end{smallmatrix} \right).\]
  5. Taking the sum and multiplying by \(i\) yields \[\left( \begin{smallmatrix}i\theta/4 \\ & i\theta/4 \\ & & i\theta/4 \\ & & & -3i\theta/4 \end{smallmatrix} \right),\] and exponentiating yields \[\mathtt{CPHASE}(\theta) = \left( \begin{smallmatrix}e^{i\theta/4} \\ & e^{i\theta/4} \\ & & e^{i\theta/4} \\ & & & e^{-3i\theta/4} \end{smallmatrix} \right).\]
Up to global phase, this is evidently equivalent to the usual AS MATRIX definition (as specified in the next section).

4.3. Standard Gate Definitions

4.3.1. Pauli Gates

\[\begin{align*} \texttt{I} &= \left(\begin{smallmatrix} 1 & 0\\ 0 & 1 \end{smallmatrix}\right) & \texttt{X} &= \left(\begin{smallmatrix} 0 & 1\\ 1 & 0 \end{smallmatrix}\right) & \texttt{Y} &= \left(\begin{smallmatrix} 0 & -i\\ i & 0 \end{smallmatrix}\right) & \texttt{Z} &= \left(\begin{smallmatrix} 1 & 0\\ 0 & -1 \end{smallmatrix}\right) \end{align*}\]

4.3.2. Hadamard Gate

\[\texttt{H} = \tfrac{1}{\sqrt{2}}\left(\begin{smallmatrix} 1 & 1\\ 1 & -1 \end{smallmatrix}\right)\]

4.3.3. Phase Gates

\[\begin{align*} \texttt{PHASE}(\theta) &= \left(\begin{smallmatrix} 1 & 0\\ 0 & e^{i\theta} \end{smallmatrix}\right) & \texttt{S} &= \texttt{PHASE}(\pi/2) & \texttt{T} &= \texttt{PHASE}(\pi/4) \end{align*}\]

4.3.4. Controlled-Phase Gates

\[\begin{align*} \texttt{CPHASE00}(\theta) &= \operatorname{diag}(e^{i\theta},1,1,1) \\ \texttt{CPHASE01}(\theta) &= \operatorname{diag}(1,e^{i\theta},1,1) \\ \texttt{CPHASE10}(\theta) &= \operatorname{diag}(1,1,e^{i\theta},1) \\ \texttt{CPHASE}(\theta) &= \operatorname{diag}(1,1,1,e^{i\theta}) \\ \texttt{CZ} &= \texttt{CPHASE}(\pi) \end{align*}\]

Note that one has the following equivalences in Quil:


4.3.5. Cartesian Rotation Gates

\[\begin{align*} \texttt{RX}(\theta) &= \left(\begin{smallmatrix} \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2}\\ -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{smallmatrix}\right)\\ \texttt{RY}(\theta) &= \left(\begin{smallmatrix} \cos\frac{\theta}{2} & -\sin\frac{\theta}{2}\\ \sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{smallmatrix}\right)\\ \texttt{RZ}(\theta) &= \left(\begin{smallmatrix} e^{-i\theta/2} & 0\\ 0 & e^{i\theta/2} \end{smallmatrix}\right) \end{align*}\]

4.3.6. Controlled-X Gates

\[\begin{align*} \texttt{CNOT} &= \left( \begin{smallmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 1\\ 0 & 0 & 1 & 0 \end{smallmatrix} \right) & \texttt{CCNOT} &= \left( \begin{smallmatrix} 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\ 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1\\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \end{smallmatrix} \right) \end{align*}\]

Note: The gate CCNOT is sometimes known as the Toffoli gate. It is a universal classical logic gate.

Note that one has the following equivalences in Quil:


4.3.7. Swap Gates

\[\begin{align*} \texttt{PSWAP}(\theta) &= \left( \begin{smallmatrix} 1 & 0 & 0 & 0\\ 0 & 0 & e^{i\theta} & 0\\ 0 & e^{i\theta} & 0 & 0\\ 0 & 0 & 0 & 1 \end{smallmatrix} \right)\\ \texttt{SWAP} &= \texttt{PSWAP}(0)\\ \texttt{ISWAP} &= \texttt{PSWAP}(\pi/2)\\ \texttt{PISWAP}(\theta) &= \left( \begin{smallmatrix} 1 & 0 & 0 & 0\\ 0 & \cos(\theta/2) & i\sin(\theta/2) & 0\\ 0 & i\sin(\theta/2) & \cos(\theta/2) & 0\\ 0 & 0 & 0 & 1 \end{smallmatrix} \right)\\ \texttt{CSWAP} &= \left( \begin{smallmatrix} 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\ 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \end{smallmatrix} \right) \end{align*}\]

Note: The gate CSWAP is sometimes known as the Fredkin gate. It is a universal classical logic gate.

Note that one has the following equivalence in Quil:


4.3.8. Other Gates

\[\begin{align*} \texttt{XY}(\theta) &= \exp\left( -i\theta(\texttt{X}^{\otimes 2} + \texttt{Y}^{\otimes 2}) \right) = \texttt{PISWAP}(\theta)\\ \texttt{CAN}(\alpha, \beta, \gamma) &= \exp\left( -i ( \alpha\texttt{X}^{\otimes 2} + \beta\texttt{Y}^{\otimes 2} + \gamma\texttt{Z}^{\otimes 2} \right) \end{align*}\]

Note: Every two-qubit gate can be written in terms of CAN, possibly with up to two preceding and two proceeding arbitrary one-qubit gates.

The definition of CAN can be written as the following Quil matrix definition:

DEFGATE CAN(%alpha, %beta, %gamma):
    (cis((%alpha+%beta-%gamma)/2)+cis((%alpha-%beta+%gamma)/2))/2, 0, 0, (cis((%alpha-%beta+%gamma)/2)-cis((%alpha+%beta-%gamma)/2))/2
    0, (cis((%alpha+%beta+%gamma)/(-2))+cis((%beta+%gamma-%alpha)/2))/2, (cis((%alpha+%beta+%gamma)/(-2))-cis((%beta+%gamma-%alpha)/2))/2, 0
    0, (cis((%alpha+%beta+%gamma)/(-2))-cis((%beta+%gamma-%alpha)/2))/2, (cis((%alpha+%beta+%gamma)/(-2))+cis((%beta+%gamma-%alpha)/2))/2, 0
    (cis((%alpha-%beta+%gamma)/2)-cis((%alpha+%beta-%gamma)/2))/2, 0, 0, (cis((%alpha+%beta-%gamma)/2)+cis((%alpha-%beta+%gamma)/2))/2
It also has a straightforward definition as a PAULI-SUM.

4.4. Quantum Gate Applications

4.4.1. Syntax and Semantics

A gate is applied via the following syntax.

Gate Application ::= Modifier*Identifier(( Expression List? ))?Formal Qubit+

We refer to Section II.A of A Practical Quantum Instruction Set Architecture for extensive and detailed mathematical discussion on the semantics of gate application realized as a matrix operator on the Hilbert space of the QAM.

Quil supports three kinds of unitary modifiers.

Modifier ::=DAGGER
| FORKED(Expression List)

These are described in the next sections.

4.4.2. DAGGER Gate Modifier

The DAGGER modifier represents the adjoint operation or complex-conjugate transpose. Since every gate is a unitary operator, this is just the inverse. For example, if G is a gate described by the one-qubit operator \[\begin{pmatrix} a & b\\ c & d \end{pmatrix}\] then DAGGER G is \[\begin{pmatrix} a^* & c^*\\ b^* & d^* \end{pmatrix}\] where \(z^*\) is the complex-conjugate of \(z\).

Because DAGGER is the inverse, the sequence of Quil instructions

G q1 ... qn
DAGGER G q1 ... qn
acts as an identity gate. As another example, consider the gate PHASE, which is defined as
    1, 0
    0, cis(%alpha)
where \[\operatorname{cis}\alpha := \cos\alpha + i \sin\alpha = e^{i\alpha}.\] Then
is equivalent to
PHASE(-t) q
for all \(\mathtt{t}\in\mathbb{R}\).

4.4.3. CONTROLLED Gate Modifier

The CONTROLLED modifier takes some gate G acting on some number of qubits q1 to qn and makes it conditioned on the state of some new qubit c. In terms of the matrix representation, if c is in the one-state, then G is applied to the remaining qubits; and if c is in the zero-state, no operation is applied. Therefore, an application of the \(n\)-qubit operator G as in

G q1 ... qn
has the controlled variant with CONTROLLED G an (n+1)-qubit operator:
CONTROLLED G c q1 ... qn

For example, the gate CONTROLLED X 1 0 is the familiar controlled-not gate, which can also be written using the standard built-in Quil gate CNOT 1 0.

Specifically, when acting on a gate G that can be represented as an \(N \times N\) matrix \(U\), CONTROLLED G produces a gate G' described by the \(2N \times 2N\) matrix \(C(U)\) such that \(C(U) := I \oplus U\), where \(I\) is the \(N \times N\) identity matrix and \(\oplus\) is a direct sum. For example, if \(U\) is the one-qubit operator \[\begin{pmatrix} a & b \\ c & d \end{pmatrix}\] then \(C(U)\) is \[\begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & a & b \\ 0 & 0 & c & d \\ \end{pmatrix}.\]

4.4.4. FORKED Gate Modifier

Let G be a parametric gate of \(k\) parameters r1 to rk and \(n\) qubits q1 to qn. This is written:

G(r1, ..., rk) q1 ... qn
Next, consider a second set of \(k\) parameters s1 to sk. The FORKED modifier takes such a gate G and allows either set of parameters to be used conditioned on an additional qubit c.
FORKED G(r1, ..., rk, s1, ..., sk) c q1 ... qn
Roughly speaking, in terms of the matrix representation of the operator, this is equivalent to the pseudocode:
if c = 0:
    G(r1, ..., rk) q1 ... qn
else if c = 1:
    G(s1, ..., sk) q1 ... qn

Note: It is very important to note that both CONTROLLED and FORKED are purely quantum, unitary operations. There is no "actual" conditional branching. The use of the above pseudocode is to illustrate how one might write the matrix representation in the standard computational basis.

For example, the built-in gate RX takes a single %theta parameter and acts on a single qubit, like so RX(pi/2) 0. Therefore, FORKED RX(pi/2, pi/4) 1 0 produces a "forked" version of RX, conditioned on qubit 1. If qubit 1 is in the zero-state, this corresponds to RX(pi/2) 0 and to RX(pi/4) 0 if qubit 1 is in the one-state.

In general, when acting on a parametric gate G of \(k\) parameters that can be represented as an \(N \times N\) matrix \[U(p_1,\ldots,p_k),\]FORKED G produces a \(2N \times 2N\) matrix \[F(U)(p_1,\ldots,p_k,p_{k+1},\ldots,p_{2k}) := U(p_1,\ldots,p_k) \oplus U(p_{k+1},\ldots,p_{2k}),\] where \(\oplus\) is the direct sum.

For example, the gate RZ is defined as

DEFGATE RZ(%theta):
    cis(-%theta/2), 0
    0,              cis(%theta/2)
Therefore, FORKED RZ(\(\theta_0\), \(\theta_1\)) 1 0, for real numbers \(\theta_0\) and \(\theta_1\) results in a two-qubit operator that can be described by the matrix \[\begin{pmatrix} \operatorname{cis}(-\theta_0/2) & 0 & 0 & 0 \\ 0 & \operatorname{cis}(\theta_0/2) & 0 & 0 \\ 0 & 0 & \operatorname{cis}(-\theta_1/2) & 0 \\ 0 & 0 & 0 & \operatorname{cis}(\theta_1/2) \\ \end{pmatrix}.\]

4.4.5. Chaining Modifiers

When gate modifiers are chained, they consume qubits left-to-right, so that in the following example, the CONTROLLED modifier is conditioned on qubit 0, FORKED on qubit 1, and the gate G acts on qubit 2.

    |         |          | ^ ^ ^
    |         |          | | | |
    |         |          +-|-|-+
    |         +------------|-+

Note that chaining multiple FORKED modifiers causes the numbers of parameters consumed by the gate to double for each additional FORKED. For example:

RX(pi) 0
FORKED RX(pi, pi/2) 1 0
FORKED FORKED RX(pi, pi/2, pi/4, pi/8) 2 1 0
You can think of that last example as representing the following decision tree, where an edge label like q2=0 means that qubit 2 is in the zero state.
|            FORKED FORKED RX(pi, pi/2, pi/4, pi/8) 2 1 0                    |
|                    /                               \                       |
|                 q2=0                              q2=1                     |
|                  /                                   \                     |
|     FORKED RX(pi, pi/2) 1 0                  FORKED RX(pi/4, pi/8) 1 0     |
|        /              \                         /               \          |
|     q1=0             q1=1                    q1=0              q1=1        |
|      /                  \                     /                   \        |
| RX(pi) 0              RX(pi/2) 0        RX(pi/4) 0              RX(pi/8) 0 |

5. Quantum State Reset

The quantum state may be reset to \(\bar \psi_0\) (i.e., all qubits in the ground state) by issuing a reset instruction.

State Reset Instruction ::= RESET

Instead, one may reset an individual qubit.

Qubit Reset Instruction ::= RESET Formal Qubit

This has the semantics corresponding to the following pseudo-code:

if 1 == MEASURE(qubit) then X(qubit)

These make up the ways the quantum state may be reset.

Reset Instruction ::=State Reset Instruction
|Qubit Reset Instruction

6. Classical Memory

This section explains Quil's classical memory model.

6.1. Design Considerations

This section is descriptive and not normative.

In assembly code, in general, types are considered only at the mnemonic or interpretation level. They're not often a consideration in the code itself. (Though this is not always true, machine codes for dynamic languages included the notion of type checking and type tags at the instruction level.) On modern processor architectures, one has a large random access memory (RAM) which is byte-addressable, and a series of processor registers that hold usually one word's worth of data. (A word is often some multiple of bytes, usually 4 or 8.) A register machine is one that loads values from memory to the registers, does some operation, and often stores the results back into memory.

So far, we've spoken only of bytes or multiples thereof. A byte—or word for that matter—is simply a measure of a number of bits, with no additional attached interpretation. What gives a byte interpretation is the literal machinery attached to the registers in which the bytes are stored. Implicit in the machinery, usually electrical circuitry, is a way of transforming bytes into a new ones. This machinery is invoked with an opcode. Since opcodes relate to physical machinery, opcodes are often only pertinent to a subset of registers that a machine has. From here, we get a usual partitioning of registers: registers that deal with general integer arithmetic, registers that deal with floating point numbers, registers that deal with vectorized low-precision arithmetic, registers that interact with main memory, and so on. The partition isn't always so strict; general purpose registers often are capable of many disparate operations.

RAM more often than not lacks any serious kind of operation except loading and storing. Similarly, cross-register opcodes also deal with the movement of data and not operation on the data contained within. When we want to do something like adding an integer and floating point number, we have to put the integer into a floating point representation, move it from an integer register to a floating point register, and perform the addition across two floating point registers. Some architectures, such as the x87 floating point unit, can perform the representation-changing and loading in a single instruction (e.g., the FILD instruction).

Quil was designed to be an instruction language that doesn't conform to any physical architecture. It was designed to accommodate evolving quantum architectures in terms of their memory models and their native gates. In some sense, Quil can be seen as a portable bytecode of sorts, (currently) without an actual byte-code representation.

The original 2016 Quil paper assumes there is an unbounded classical memory composed of a series of bits, and segments of these bits can be interpreted as a real or complex number. While very simplistic, it has a few flaws:

In the remainder of this section, we describe a replacement for the notion of classical data in Quil. It is similar to C in that we don't select any particular memory model, and require the user to specify what he or she requires in terms of layout. Similarly departing from usual instruction sets, we allow for memory to be interpreted through multiple type lenses. In C, we accomplish this by casting pointers and dereferencing. Since we don't have a notion of pointers, we accomplish this with explicit declaration and aliasing.

With Quil's classical memory model, we can write code which does the following:


# Initialize
MOVE stats 0
MOVE angle 0.0

# Start the angle loop
LABEL @start_angle_loop
LT cond angle 6.283185307179586
JUMP-UNLESS @end cond
# Perform histogram loop, 1000 shots
MOVE count 1000
LABEL @stats_loop
RX(angle) 0
MEASURE 0 measurement
ADD stats measurement
SUB count 1
GT cond count 0
JUMP-WHEN @stats_loop cond
# Calculate next angle
ADD angle 0.3926990816987241   # pi/8
JUMP @start_angle_loop
LABEL @end

This will be roughly equivalent to the following C program:

int count, stats, measurement;
float angle;
stats = 0;
for(angle = 0.0; angle < 6.283185307179586; angle += 0.3926990816987241) {
    for(count = 1000; count > 0; count--) {
        RX(angle) 0
        measurement = MEASURE 0
        stats += measurement

6.2. Types

The supported types are BIT which represents one bit, OCTET which represents 8 bits, INTEGER which represents a machine-sized signed integer, and REAL which represents a machine-sized real number. The formats/layouts of these are specific to the machine being run on.

Base Type ::=BIT

When we speak of size, we mean the number of octets that a type represents. The notion of size is distinct from length, which instead refers to some count of elements of a particular type.

A fixed-length vector of a type is denoted by the type name followed by an integer in brackets. For instance, REAL[5] is a type that represents five real numbers in sequence. The type INTEGER is guaranteed to be large enough to hold a valid length of octets, and is guaranteed to hold at least the values \(-127\) to \(128\).

Type ::=Base Type
|Base Type[Integer]

There are currently no provisions for adding additional types.

6.3. Declaring Memory

Quil doesn't have a notion of allocating memory, but rather the notion of declaring the existence of memory. In the following, we introduce the DECLARE directive, which describes available memory for a program to use.

Some quantum computing architectures might restrict what can be declared, what types can be used, what names can be used, etc. It is recommended to be as liberal as possible in what can be declared, while remaining true to the architectural constraints of the system on which Quil is executed.

The DECLARE directive is used to declare a vector of typed memory. There are three variants: plain declaration, aliased declaration, and aliased declaration with offset.

Classical Memory Declaration ::=Plain Memory Declaration
|Aliased Memory Declaration
|Offset Memory Declaration

6.3.1. Declaring Memory

The simplest kind of memory declaration is a plain one.

Plain Memory Declaration ::= DECLARE IdentifierType

This declares that Identifier designates memory which can hold Type. If Type is a scalar type, then it is assumed to designate a vector of length 1. That is, the following two lines are equivalent:


In the program that would follow either of these declarations, x or equivalently x[0] will refer to an integer quantity.

6.3.2. Declaring Aliased Memory

Aliased Memory Declaration ::= DECLARE Identifier1Type SHARING Identifier2

This declares that Identifier1 designates memory which can hold Type, but Identifier1 shares memory with that which is designated by Identifier2. Here, the total memory size pointed to by Identifier1 shall not exceed the total memory size pointed to by Identifier2.

An implementation is free to reject programs where particular instances of sharing is invalid (e.g., alignment is violated; disparate memories are unshareable; etc.).

6.3.3. Declaring Aliased Memory Declaration with an Offset

Offset Memory Declaration ::= DECLARE Identifier1Type SHARING Identifier2 OFFSET (IntegeriTypei)+

This is similar to the aliased declaration, but it allows Identifier1 to designate memory in the middle of that which is designated by Identifier2. In particular, Identifier1 will point to memory a total of \[\sum_i \langle\texttt{Integer}_i\rangle\cdot \text{sizeof}(\langle\texttt{Type}_i\rangle)\] bits after the start of Identifier2. As with an aliased declaration, the memory at Identifier1 must not overflow the end of Identifier2.

Implementations may enforce alignment by way of erroring if the stated declaration is invalid. Implementations must not round up or down to alignment boundaries.

6.3.4. Portability of Aliased Declarations

Aliased declarations with mixed types require an intimate view of the target architecture. The widths of each data type, which are hitherto unspecified, must be known. For example, the following declarations may not be valid of the size of REAL exceeds the size of INTEGER.

Even if such a declaration is valid, operations on y are not portably specified. For example, continuing the above,
MOVE x 0
EQ b y 0.0
could result in any value for b, depending on the implementation.

An implementation shall describe the bit-level description of the types, the available declarable memories, the limits on the declared memory, alignment requirements, and limits on sharing and offsets.

6.3.5. Duplicate Declaration Identifiers

It is an error to declare the same name more than once.

6.3.6. Examples Register Machine with a Condition Bit

Here we consider a layout for a machine that has one integer register, two real registers, and a condition bit used for doing comparisons and branching.

DECLARE cmp BIT    # cmp for "comparison"

This might be suitable for a very simple quantum control system with a single counter for loops. Memory-Mapped RAM

The following is an example of a memory structure that might be used in a system with a fixed and known memory layout optimized for running QAOA-like circuits.

DECLARE memory OCTET[131072]                              # 128k global memory
DECLARE qaoa-params REAL[32] SHARING memory               # all QAOA params
DECLARE beta REAL[16] SHARING qaoa-params                 # beta params
DECLARE gamma REAL[16] SHARING qaoa-params OFFSET 16 REAL # gamma params
DECLARE ro BIT[16]                                        # readout registers

Here, we have two disjoint memories: the global data memory memory, and the readout memory ro. We see that the global data memory memory is further partitioned into a section qaoa-params specifically for QAOA parameters, which may be useful if you're changing them all at once. Nonetheless, for actual use in Quil code, the actual beta and gamma parameters are carved out of this memory.

This particular scheme may be necessary if software processing Quil does not have any ability generate memory maps automatically. If that functionality were possible, one could simply declare beta, gamma, and ro and let the compilation software take care of mapping that to physical memory. Computing Bits of an Angle

In algorithms like phase estimation, we compute one bit of the result at a time with each measurement. If our INTEGER data type has the standard binary representation, then one can do:

DECLARE unadjusted-theta INTEGER
DECLARE ro BIT[16] SHARING unadjusted-theta
# <phase estimation>
MEASURE 0 ro[0]
MEASURE 1 ro[1]
# ...
MEASURE 15 ro[15]

Here, we have a 16-bit integer unadjusted-theta with the LSB of our estimated phase starting with qubit 0. (This depends on our convention in our implementation of phase estimation.) Since unadjusted-theta and ro are shared, the bits of ro directly affect the bits of our integer. Recalling that phase estimation gives us a bitstring (in this case, an integer between \(0\) and \(2^{16} - 1\)), we must actually adjust it by multiplying by \(2\pi/2^{16}\), which is approximately \(9.587379924285257\times 10^{-5}\).

Since theta and unadjusted-theta have different types, we can't quite yet do this multiplication. We need to convert unadjusted-theta into a REAL representation on which we can do fractional arithmetic. We can do this with CONVERT, which in other languages is known as a cast or coercion.

CONVERT theta unadjusted-theta   # convert INTEGER to REAL
MUL     theta theta 9.587379924285257e-5

Now we can use theta as an argument to an angle if we please. For example, we might do a phase adjustment based off of that angle on qubit 16:

RZ(theta) 16

6.4. Memory Access and Dereferencing

Memory is dereferenced in a Quil program using common array access syntax. In particular, given a name x pointing to memory of type \(T\), and a non-negative integer offset \(n\), the syntax x[\(n\)] refers to the \(n\)th element of type \(T\) indexing off of x[0].

If and only if x was declared with just a single element, then x may be referred to simply by its name with no bracket. In this case, x and x[0] would be equivalent.

Memory Reference ::=Identifier

Note: Note that this memory reference is formal. The lone Identifier in certain contexts may refer to a named argument of, for example, a circuit definition.

Dereferencing with indirection, e.g., something akin to x[y[3]], is supported through the LOAD and STORE instructions. For example,

LOAD t y z[3]          # t := y[z[3]]
LOAD t x t             # t := x[t]

6.5. Classical Instructions

With typed memory comes a bag of new instructions. Classical instructions come in unary (single-argument), binary (double-argument), and ternary (triple-argument) forms. They all share the same syntax.

Classical Memory Instruction ::=Classical UnaryMemory Reference
|Classical BinaryMemory Reference2
|Classical TernaryMemory Reference3

The unary instruction names are:

Classical Unary ::=NOT

The binary instruction names are:

Classical Binary ::=MOVE

The ternary instruction names are:

Classical Ternary ::=LOAD
| EQ
| GT
| GE
| LT
| LE

While the instructions all take memory references, they only take memory references of certain type combinations. Each combination is called an "instruction mode". In the following table, we use the following notation to denote an instruction INSTR and its modes:

# Category of instruction
INSTR   a b             # Pseudocode meaning
        <type1a> <type1b>
        <type2a> <type2b>

The possibilities for <typeXY> are:

Octet literals share the same syntax as integer literals.

We generally follow the dest-src ordering of arguments.

# Move like-typed data to different locations.
# Also allows loading immediate values.
MOVE     a b            # a := b; Store contents of b at a
         <oct> <!int>
         <oct> <oct>
         <int> <!int>
         <int> <int>
         <real> <!real>
         <real> <real>
         <bit> <!int>
         <bit> <bit>

# Exchange the value at two like-typed locations.
EXCHANGE a b            # Exchange contents of a and b; a <=> b
         <oct> <oct>
         <int> <int>
         <real> <real>
         <bit> <bit>

# Perform an indirect load from x offset by n to a.
LOAD     a x n          # a := x[n]
         <oct> <oct*> <int>
         <int> <int*> <int>
         <real> <real*> <int>
         <bit> <bit*> <int>

# Perform an indirect store of a to x offset by n.
STORE    x n a          # x[n] := a
         <oct*> <int> <oct>
         <oct*> <int> <!int>
         <int*> <int> <int>
         <int*> <int> <!int>
         <real*> <int> <real>
         <real*> <int> <!real>
         <bit*> <int> <bit>
         <bit*> <int> <!int>

# Perform a move of differently typed data.
# The data here is interpreted numerically.
CONVERT  a b            # a := (T)b, where T = type-of(a)
         <int> <real>   # - Best integer approximation of a real.
         <int> <bit>    # - Convert 0 or 1 to an integer.
         <real> <int>   # - Best real approximation of an integer.
         <real> <bit>   # - Convert 0 or 1 to a real.
         <bit> <int>    # - 0 if 0, 1 if non-zero.
         <bit> <real>   # - 0 if 0.0, 1 if non-zero

# Logical Operations
NOT      a              # a := ~a

AND      a b            # a := a & b
IOR      a b            # a := a | b
XOR      a b            # a := a ^ b
         <oct> <oct>
         <oct> <!int>
         <int> <int>
         <int> <!int>
         <bit> <bit>
         <bit> <!int>

# Arithmetic Operations
NEG      a              # a := -a

ADD      a b            # a := a + b
SUB      a b            # a := a - b
MUL      a b            # a := a * b
DIV      a b            # a := a / b
         <int> <int>
         <int> <!int>
         <real> <!real>
         <real> <real>

# Comparison
EQ       r a b          # r := (a == b)
GT       r a b          # r := (a > b)
GE       r a b          # r := (a >= b)
LT       r a b          # r := (a < b)
LE       r a b          # r := (a <= b)
         <bit> <bit> <bit>
         <bit> <bit> <!int>
         <bit> <oct> <oct>
         <bit> <oct> <!int>
         <bit> <int> <int>
         <bit> <int> <!int>
         <bit> <real> <real>
         <bit> <real> <!real>

7. Measurement

Measurement is the only way in which the quantum state can affect classical memory. Measurement comes in two flavors: measurement-for-effect and measurement-for-record.

Measurement-for-effect measures a single qubit and discards the result.

Measurement for Effect ::= MEASURE Formal Qubit

Measurement will stochastically project the qubit into either the zero-state or the one-state depending on its probability of such dictated by the wavefunction amplitudes.

Measurement for Record ::= MEASURE Formal QubitMemory Reference

Here, the memory reference must be either of type BIT or INTEGER. In either case, a \(0\) is deposited at the memory location if the qubit was measured to be in the zero-state, and a \(1\) otherwise.

These measurement varieties make up all measurement instructions.

Measurement Instruction ::=Measurement for Effect
|Measurement for Record

Note that there is no way in Quil to measure all qubits simultaneously.

8. Classical Control

8.1. Halting the Program

The program is halted if it is no longer executing. This may happen under one of three conditions:

Halt Instruction ::= HALT

Error conditions may happen, for instance, when a division-by-zero occurs. There may be other ways in which an implementation may error.

8.2. Program Labels and Branching

Run-time control flow is achieved through a variety of branching instructions. Each branching instructions requires a target place in the program to jump to. These target places are denoted by labels:

Jump Target ::= @Identifier

Label ::= LABEL Jump Target

A label (resp. jump target) is said to be at position \(p < \vert P\vert\) if the first instruction that follows the label (resp. jump target's label) is the \(p\)th instruction (zero-indexed). If no instruction follows the label, then it is said to be a halting label at position \(\vert P\vert\).

Each LABEL jump target name must be unique. It is an error to have a duplicate jump target name in a program.

Note: Jump target names may be duplicated across (but not within) DEFCIRCUIT bodies if and only if those names don't appear globally within the program in which the DEFCIRCUIT is expanded. Names may be duplicated because they are made unique when the circuit is expanded.

One may transfer control to the \(p\)th position of a program by using a JUMP instruction targeting a label at position \(p\).

Unconditional Branch Instruction ::= JUMP Jump Target

One may transfer control to the \(p\)th position of a program conditional on a given memory reference using one of the following two instructions:

Conditional Branch Instruction ::=JUMP-WHEN Jump TargetMemory Reference
| JUMP-UNLESS Jump TargetMemory Reference

The JUMP-WHEN (resp. JUMP-UNLESS) instruction branches if and only if Memory Reference references a BIT-typed value that is non-zero (resp. exactly zero).

Together, these form the branching instructions.

Branch Instruction ::=Unconditional Branch Instruction
|Conditional Branch Instruction

9. Other Instructions and Directives

9.1. No-Operation Instruction

The no-operation instruction or NOP instruction is an instruction which does not affect the classical or quantum state of the QAM. It only affects the control state by incrementing the program counter by \(1\).

No-Operation Instruction ::= NOP

9.2. Pragmas

Programs that process Quil code may want to take advantage of extra information provided by the programmer. This is especially true when targeting quantum processors where additional information about the machine’s characteristics affect how the program will be processed. Quil supports a PRAGMA directive to include extra information in a program which does not otherwise affect execution semantics.

Pragma ::= PRAGMA Identifier(Identifier | Integer)*String?

9.3. File Inclusion

One can include a valid Quil file in another valid Quil file by inclusion.

File Include ::= INCLUDE String

Here, String denotes a file name. Implementations processing Quil must support and document individual file names, and may support operating-system-dependent file paths.

10. Circuits

10.1. Circuit Syntax

Circuits in Quil are parameterized templates of instructions that can be filled in with parameters and arguments. Circuit applications within a program are expanded according to the circuit's definition in full before a Quil program is executed.

Note: Circuits are intended to be used more as macros than as specifications for general quantum circuits. Indeed, DEFCIRCUIT is very limited in its expressiveness, only performing argument and parameter substitution. It is included mainly to help with the debugging and human readability of Quil code. Circuits in Quil are more like C preprocessor macros than they are like functions. The QAM has no notion of a circuit as a part of its semantics; circuits are simply notational conveniences.

A circuit is defined with the DEFGATE directive.

Circuit Definition ::= DEFCIRCUIT Identifier((Parameters))?Arguments? : Circuit Instruction*

Within the circuit body, we can write any Quil instruction, allowing for the named parameters and arguments to show up as instruction parameters or arguments.

Circuit Instruction ::= IndentInstructionTerminator

A circuit may be used similarly to a gate:

Circuit Application ::= Identifier(( Expression List? ))?(Formal Qubit | Memory Reference)+

10.2. Circuit Expansion

Circuits are expanded recursively, outside in. Circuits may not be self-recursive or mutually recursive. These circuits are invalid because they exhibit different kinds of non-terminating recursion.




Labels that are declared within the body of a DEFCIRCUIT are unique to each of that circuit's expansions. While it is possible to jump out of a DEFCIRCUIT to a globally declared label, it is not possible to jump inside of one.

Consider the following two DEFCIRCUIT declarations and their instantiations. Note the comments on correct and incorrect usages of JUMP.

    JUMP @GLOBAL   # (A) valid, global label
    JUMP @FOO_A    # (B) valid, local to FOO
    JUMP @BAR_A    # (C) invalid

    JUMP @FOO_A    # (D) invalid

JUMP @FOO_A        # (E) invalid
JUMP @BAR_A        # (F) invalid
Line (A) is valid because it is a jump from a circuit to a global label called @GLOBAL. Line (B) is valid because it is a jump to a label within the same circuit body. Line (C) is invalid because it is erroneously attempting to jump to a different circuit body. Line (D) is invalid for the same reason as line (C); it is an erroneous attempt to jump from one local circuit body to another. Lines (E) and (F) are both invalid because they are attempts to jump into a local circuit definition.

11. History and Changes

11.1. A History of Quil

In 2016, at Rigetti Computing, Quil was defined in an arXiv paper entitled "A Practical Quantum Instruction Set Architecture" by R. Smith, M. Curtis, and W. Zeng.

In 2018, at Rigetti Computing, R. Smith began work to amend Quil to include a memory model. Its definition lived in a new Git repository containing the Quil specification.

In 2019, at Rigetti Computing, Quil's specification was rewritten in Markdown format by S. Heidel and other Rigetti-based contributors for easier consumption. S. Heidel also contributed an ANTLR grammar for Quil.

In 2019, at Rigetti Computing, an extension of Quil for time-domain, pulse-level control was developed by S. Heidel, E. Davis, and other Rigetti-based contributors. This was code-named "Quilt" but was later finalized as "Quil-T". Quil-T lived as a proposed extension (called an "RFC") in the Git repository.

In 2019, at Rigetti Computing, an addition to Quil to allow gates to be defined as exponentiated Pauli sums was developed by E. Peterson. This was code-named "defexpi" but was later finalized as syntax DEFGATE AS PAULI-SUM. This syntax lived as an RFC in the Git repository.

In 2021, R. Smith (whose affiliation since changed to HRL Laboratories) and Rigetti Computing set up the Quil-Lang GitHub organization for shared and collaborative governance of the definition of Quil as well as its de facto standard software tooling.

In 2021, R. Smith rewrote the specification in a custom format to allow rendering as an HTML page. The specification was synthesized from all previous official sources on the language.

Quil's specification, as well as software implementations, have benefited greatly from their international userbase. Quil has also benefited from a diverse ecosystem of other quantum computing languages, such as OpenQASM, Quipper, Q#, and QCL.

11.2. Changes

This document only tracks changes since its conception.

12. Annex T: Pulse-Level Control

This section is about time-domain extensions to Quil, formally known as Annex T of this document, but also known as Quil-T.

12.1. Frames

Frame Identifier ::= String

Frame ::= Qubit+Frame Identifier

A frame encapsulates any rotating frame relative to which control/readout waveforms may be defined. For the purposes of scheduling and execution on possibly heterogenous hardware, frames are specified with respect to a specific list of qubits. Thus, 0 1 "cz" is the "cz" frame on qubits 0 and 1. The order of the qubits matters. In particular, the above frame may differ from 1 0 "cz".

12.1.1. DEFFRAME

Quil-T itself has no built-in frames. Frames must be defined using the DEFFRAME directive.

Frame Definition ::= DEFFRAME Frame(: Frame Specification+)?

Frame Specification ::= IndentFrame Attribute : (Expression | String)

Frame Attribute ::=SAMPLE-RATE

All frames used in a program must have a corresponding top-level definition.

Before execution, a Quil-T program is linked with a specific system of control hardware, and frames are mapped to suitable hardware objects (cf. the HARDWARE-OBJECT frame attribute below). Native or canonical frame definitions may be provided by a hardware vendor. Some examples of Rigetti's canonical frames are listed below, but this is subject to change.

Examples (names only):

"xy"  # eg. for the drive line
"ff"  # eg. for a generic flux line
"cz"  # eg. for a flux pulse for enacting CZ gate
"ro"  # eg. for the readout pulse
"out" # eg. for the capture line Frame Attributes

Frame attributes represent quantities associated with a given frame which need not be specified by the programmer, but which are ultimately required to fully link and execute a Quil-T program on a physical device.

12.2. Waveforms

Waveform ::=Identifier
| flat ( duration: Expression, iq: Expression )
| gaussian ( duration: Expression, fwhm: Expression, t0: Expression )
| draggaussian ( duration: Expression, fwhm: Expression, t0: Expression, anh: Expression, alpha: Expression )
| erfsquare ( duration: Expression, risetime: Expression, padleft: Expression, padright: Expression )

Waveforms are referenced either by name or by a built-in waveform generator.

The built-in waveform generators are:

12.2.1. Defining new waveforms

Sample Rate ::= Real

Waveform Definition ::= DEFWAVEFORM Identifier ( Parameters ) Sample Rate : Matrix Entries

New waveforms may be defined by specifying the sample rate (in Hertz) and listing out all the IQ values as complex numbers, separated by commas. Waveform definitions may also be parameterized, although note that Quil has no support for vector level operations.


DEFWAVEFORM my_custom_waveform 6.0:
    1+2i, 3+4i, 5+6i

DEFWAVEFORM my_custom_parameterized_waveform(%a) 6.0:
    (1+2i)*%a, (3+4i)*%a, (5+6i)*%a

The duration (in seconds) of a custom waveform may be computed by dividing the number of samples by the sample rate. In the above example, both waveforms have a duration of 0.5 seconds.

12.3. Pulses

Pulse ::= PULSE Frame Waveform

Pulses represent the propagation of a specific waveform (either built-in or custom) on a specific frame.


# Simple pulse with previously defined waveform
PULSE 0 "xy" my_custom_waveform

# Pulse with previously defined parameterized waveform
PULSE 0 "xy" my_custom_parameterized_waveform(0.5)

# Pulse with built-in waveform generator
PULSE 0 "xy" flat(duration: 1e-6, iq: 2+3i)

# Pulse on a flux line
PULSE 0 1 "cz" flat(duration: 1e-6, iq: 2+3i)

Each frame has a fixed, hardware-specific sample rate. The behavior of a PULSE instruction with a custom waveform whose sample rate does not match the corresponding frame's sample rate is undefined.

12.4. Frame Mutations

12.4.1. Frequency

Set Frequency ::= SET-FREQUENCY FrameReal

Shift Frequency ::= SHIFT-FREQUENCY FrameReal

Each frame has a frequency which is tracked throughout the program. Initial frame frequencies are specified in the frame definition's INITIAL-FREQUENCY attribute. Subsequent code may update this, either assigning an absolute value (SET-FREQUENCY) or a relative offset (SHIFT-FREQUENCY).

SET-FREQUENCY 0 "xy" 5.4e9
SHIFT-FREQUENCY 0 "ro" 6.1e9

12.4.2. Phase

Set Phase ::= SET-PHASE FrameReal

Shift Phase ::= SHIFT-PHASE FrameExpression

Swap Phases ::= SWAP-PHASES FrameFrame

Each frame has a phase which is tracked throughout the program. Initially the phase starts out as 0. It may be set or shifted up and down, as well as swapped with other frames.

The phase must be a rational real number. There is also support for shifted the phase based on some expression, as long as that expression returns a real number.


SET-PHASE 0 "xy" pi/2

SHIFT-PHASE 0 "xy" -pi
SHIFT-PHASE 0 "xy" %theta*2/pi

SWAP-PHASE 0 "xy" 1 "xy"

12.4.3. Scale

Set Scale ::= SET-SCALE FrameReal

Each frame has a scale which is tracked throughout the program. The scale is initially 1.


SET-SCALE 0 "xy" 0.75

12.5. Capture

Capture ::= CAPTURE FrameWaveformMemory Reference

Raw Capture ::= RAW-CAPTURE FrameExpressionMemory Reference

The capture instruction opens up the readout on a frame and measures its state. An integration waveform will be applied to the raw IQ points and the result is placed in classical memory.

The waveform will define the duration of the capture. The memory reference must be able to store a complex number for each qubit in the frame.

In the case of a raw capture the waveform is replaced with a rational number representing the duration of the capture.


# Simple capture of an IQ point
CAPTURE 0 "out" flat(1e-6, 2+3i) iq

# Raw capture
DECLARE iqs REAL[400] # length needs to be determined based on the sample rate
CAPTURE 0 "out" 200e-6 iqs

The behavior of a CAPTURE instruction with a custom waveform whose sample rate does not match the corresponding frame's sample rate is undefined.

12.6. Defining Calibrations

Calibration Definition ::= DEFCAL Modifier*Identifier ( Parameters ) Qubit+ : Instruction+

Measure Calibration ::= DEFCAL IdentifierQubit?Parameter : Instruction+

Calibrations for high-level gates can be defined by mapping a combination of (gate name, parameters, qubits) to a sequence of analog control instructions.

Calibrations with the same gate name as a built-in gate definition or custom gate definition are assumed to be the same.

Multiple calibration definitions can be defined for different parameter and qubit values. When a gate is translated into control instructions the calibration definitions are enumerated in reverse order of definition and the first match will be taken.

For example, given the following list of calibration definitions in this order:

  1. DEFCAL RX(%theta) %qubit:
  2. DEFCAL RX(%theta) 0:
  3. DEFCAL RX(pi/2) 0:
The instruction RX(pi/2) 0 would match (3), the instruction RX(pi) 0 would match (2), and the instruction RX(pi/2) 1 would match (1).

The same system applies for MEASURE. Although MEASURE cannot be parameterized, it takes only a single qubit as input, and it has an additional (optional) parameter for the memory reference into which to store the result.


# Simple non-parameterized gate on qubit 0
    PULSE 0 "xy" gaussian(duration: 1, fwhm: 2, t0: 3)

# Parameterized gate on qubit 0
DEFCAL RX(%theta) 0:
    PULSE 0 "xy" flat(duration: 1e-6, iq: 2+3i)*%theta/(2*pi)

# Applying RZ to any qubit
DEFCAL RZ(%theta) %qubit:
    SHIFT-PHASE %qubit "xy" %theta

# Measurement and classification
    DECLARE iq REAL[2]
    CAPTURE 0 "out" flat(1e-6, 2+3i) iq
    LT %dest iq[0] 0.5 # thresholding

Quil supports arbitrarily chained gate modifiers. As such, calibration definitions may also incorporate gate modifiers, with the convention that a calibration definition matches a gate application only if the modifiers match exactly. Thus in

    # ...

    # ...
the first calibration definition matches T 0, the second matches DAGGER T 0, and neither match DAGGER DAGGER T 0.

12.7. Timing and Synchronization

Delay ::= DELAY Qubit+Frame Identifier*Expression

Fence ::= FENCE Qubit*

Delay allows for the insertion of a gap within a list of pulses or gates with a specified duration in seconds.

If frame names are specified, then the delay instruction affects those frames on those qubits. If no frame names are specified, all frames on precisely those qubits are affected.

Note: Note: this excludes frames which intersect the specified qubits but involve others. For example, DELAY 0 1.0 delays one qubit frames on 0, such as 0 "xy", but leaves other frames, such as 0 1 "cz", unaffected.

Fence ensures that all operations involving the specified qubits that follow the fence statement happen after all operations involving the specified qubits that preceed the fence statement. If no qubits are specified, the FENCE operation implicitly applies to all qubits on the device.


X 0
X 1 # This X gate will be applied to qubit 1 AFTER the X gate on qubit 0

# Simple T1 experiment
X 0
DELAY 0 100e-6
MEASURE 0 ro[0]