Welcome to libqasm’s documentation!

libqasm is a library for handling cQASM files using C++ or Python.

Context and history

Note

This recounts my (Jeroen van Straten) personal understanding on this matter as of October 19th, 2020. It may not be entirely correct; please amend if need be. But I feel that it’s nevertheless important to provide context on how some of these things came to be.

cQASM 1.0 was originally documented and published by means of an arXiv paper. It “aims to abstract away qubit technology details and guarantee the interoperability between all the quantum compilation and simulation tools supporting this standard.” Nevertheless, the feature set of cQASM 1.0 was very much chosen based on what the QCA group needed at the time, with no regard for generality beyond that; mostly, it was just a replacement for the quantum code frontend baked into the QX simulator. In most of the time since, features were added in an ad-hoc fashion when they became absolutely necessary (such as the arbitrary single-qubit unitary gate, or the shuttle gates added for spin qubits in a fork), awaiting an almost mystical “cQASM 2.0” specification that was supposed to add support for classical control-flow.

This was the state when I joined the QCA team at the end of 2018, and I was tasked to write this cQASM 2.0 specification. Little did I know at the time that various attempts had been made before at this time, and there was also already some working document on Overleaf (unfortunately not publicly accessible for whatever reason) to express and discuss requirements that no one at QCA could tell me about. The specification I wrote independently was ultimately shot down for being far too extensive and ambitious, so in the end, all I could do was add to the Overleaf document. Unfortunately no one has felt responsible to pursue cQASM 2.0 since then, that I know of.

Fast forward to summer 2020, when I was tasked to look into making libqasm’s instruction set configurable, in the same way that OpenQL’s instruction set is specified in the JSON target description file. The idea here is that cQASM could be used as a representation of OpenQL’s internal intermediate representation, as part of OpenQL’s modularization effort. However, since libqasm’s Flex/Bison parser was written with the instruction set hardcoded into it, this automatically required a complete rewrite of the library. Backward compatibility was key here; after all, libqasm is used in many places beyond QX at this time (most importantly, various benchmark suites have been ported to cQASM 1.0, and Quantum Inspire runs on it). This required some special cases in the grammar and a type system with overloading capability, but ultimately this was doable. Significant parts of the new code were copied from what I had already been working on based on my cQASM 2.0 specification, as it made little sense to me to strip everything out, although I intentionally only added language features that would pose no requirements on the programs/libraries using libqasm whatsoever.

The major downside of making the instruction set configurable is that it reduces the cQASM specification to just syntax, while the original intention of cQASM was to define the complete language, including semantics. To mitigate this, libqasm provides methods to construct a “default” parser with the instruction set cQASM understood before this change, and the semantics of this instruction set is documented along with the cQASM 1.0 syntax here. Users of the libqasm library are recommended to just use this default instruction set unless they have a good reason not to do so (like OpenQL).

Not too long after that pull request, extending cQASM’s conditional execution syntax was on the agenda, in order to allow expression of all conditional constructs supported by the QuTech Central Controller using cQASM. This required state variables and dynamically-evaluated expressions. Hence, basic support for this was recently added. Since this is a bit of a paradigm shift, but still entirely backward compatible, I figured it would be a good idea to name this cQASM 1.1.

The idea was to continue with 2.0 after that. The latest spec for this can be found here. However, it was ultimately determined that completing and implementing this specification would take more manpower than we have. Therefore, cQASM 1.2 was created. This update adds crude assignment statements and basic structured control-flow statements. It is, again, backward compatible, save for the newly added keywords.

Structure

cQASM 1.x files consist of a version statement, optionally a qubits statement (mandatory for 1.0, optional for 1.1+), optionally an error_model statement, and zero or more of any combination and ordering of the following statement types:

  • mappings,
  • variables (version 1.1+),
  • subcircuit headers,
  • bundles, and
  • structured control-flow statements (version 1.2+).

A simple algorithm might look like this.

version 1.0
qubits 2

map input = q[0]
map output = q[1]

.initialize
prep_z input | prep_z output
x output
h input | h output

.oracle
cnot input, output

.measure
h input
measure input

File extension

The file extension for cQASM files is preferably .cq. Historically everyone seems to use .qasm, but this conflicts with the OpenQASM format. libqasm doesn’t care, but DQCsim goes as far as to require that the file extension is .cq, as it uses file extensions to disambiguate the input file format (the problem, here, is that it also has an OpenQASM frontend).

Whitespace and comments

cQASM 1.x is newline-sensitive. In most contexts, newlines are used to terminate statements. A semicolon (;) may be used instead of the newline if the code is more readable when two statements occupy the same line. Newlines can also be “escaped” using a backslash (\) immediately in front; this disables the functional behavior of the newline and thus allows statements to be split over a single line.

statement 1
statement 2; statement 3
a very long statement 4,    \
    but a single statement  \
    nontheless.

Besides newlines, cQASM 1.x is not whitespace-sensitive; tabs and spaces can go between any two tokens.

Single-line comments use the hash (#) prefix as in Python and most other scripting languages. Block comments use /* */ as in C and various C-like languages. Like spaces and tabs, block comments can go between any two tokens.

statement 1 # I'm a comment
statement /* I'm a much
longer comment */ 2

cQASM 1.x files are not case-sensitive.

h q[1]
H Q[1] # this means the same thing

Keywords

cQASM 1.x recognizes the following keywords. These words cannot be used to name mappings or variables.

break  cond  continue  else  for  foreach  if
map  repeat  set  qubits  until  var  while

Note

All gates, axes (x, y, z), the qubit register (q), and the bit register (b) used to be keywords. This is no longer the case, so you can freely rename a qubit to for instance b now. Be aware, however, that this shadows the bit register, meaning that you won’t be able to use it anymore later. The type system is such that the operands of a gate are used to disambiguate the gate; the gate can thus not be used to disambiguate between b as in a mapping to a qubit and b as in the bit register. The same applies for q, x, y, and z (the latter three only if you use a gate that takes an axis operand). You can work around this by first remapping the entire register or the axis to a longer word, for instance using map qubits = q.

Statements

A cQASM 1.x file ultimately consists of a number of statements. Several kinds of statements are defined. They are all listed in this section.

Version statement

The first statement in every cQASM file must be a version statement. It specifies which version of the cQASM language is to be used when parsing. It has the form

version <period-separated-integers>

For example, the version statement for a cQASM 1.0 file must be

version 1.0

cQASM 1.1 adds support for variables and dynamic expressions. In order to use these features, you have to select at least version 1.1:

version 1.1

cQASM 1.2 adds support for assignment statements and structured control-flow. In order to use these features, you have to select at least version 1.2:

version 1.2

Qubits statement

This statement declares the desired size of the qubit register (q) and associated measurement bit register (b). It has the form:

qubits <positive-integer-constant>

It must be the second statement in the cQASM file if specified. The statement is mandatory in cQASM 1.0, but optional for cQASM 1.1+. The latter allows variables to be used to replace the qubits statement, avoiding the implicit link between qubits and measurement bit storage that is not universal among control architectures.

Error model statement

Optionally, an error model statement may be used to select and configure an error model for the targeted simulator. It has the following form:

error_model <name>, <comma-separated-operands>

Available error models must be registered with libqasm while the parser is constructed when using the new API, but for the old API, QX’s error models is built in. In this case, the only value for <name> is depolarizing_channel, and zero or more real numbers may be specified as arguments. Refer to QX’s documentation for information about what these arguments do.

Historical

The original API of libqasm allowed the user to specify the error model anywhere in the program, and performed no check to ensure that it would only be specified once. Ultimately, the final error model specified determines the error model for the program. For backward-compatibility reasons, this behavior is mimicked by the new API.

Error model definitions may be annotated.

Variable statement (1.1+)

This statement declares one or more variables. It is only supported for targets that actually support variables. It has the following form:

var <comma-separated-names>: <type>

where <type> may be qubit, bool, bit (synonym for bool), int, real, or complex.

Variable statements may appear anywhere in the program. the variable will be usable from its declaration onwards. It is legal, if perhaps ill-advised, to make multiple variables of the same name; they will be interpreted as distinct variables, where references to that variable name simply refer to the latest declaration.

The initial value of a variable is undefined.

Note

Because variables can be declared anywhere, they can also be declared within the code blocks of cQASM 1.2+’s structured control-flow statements. In this case, the variable can only be accessed from within the code block. Typically, the variable will behave like a global, i.e. it will retain the value of the previous loop iteration for instance, but implementations are not required to do this! Again: the initial value of a variable is undefined.

Variable statements can be annotated.

Map statement

This statement maps an expression to a word, allowing the expression to be used later by simply using the word, like an alias or a macro. It has the following equivalent forms:

map <expression>, <alias>
map <alias> = <expression>

Here, <alias> is a word consisting of letters, digits, and underscores, not starting with a digit. <expression> is any expression of any data type. Name resolution for any mappings in the expression occurs in the context of the map statement, but the expression itself is only evaluated when the alias is used.

Map statements can be annotated.

Note

Map statements are NOT assignment statements. They are functionally equivalent to a lambda without arguments in Python or (more or less) preprocessor definitions in C; the expression is not evaluated by the map statement, but where the mapping is used. For example, in the following algorithm (assuming perfect qubits and measurement),

prep_z q[0]
measure q[0]

map q0_measured_zero = !b[0]
cond (q0_measured_zero) x q[0]

measure q[0]
cond (q0_measured_zero) x q[0]

the first X gate would be executed, because b[0] is false/zero, thus !b[0] is true. But q0_measured_zero has not assumed the value true; it retains the complete expression. Therefore, the second X gate would NOT be executed, as the second measurement flips the state of b[0]. The position of the map statement doesn’t matter, as long as it’s before the first line where q0_measured_zero is used.

Historical

Mappings are called such because originally they were only used to allow users to rename the numbered qubits and measurement bits in the qubit register to some user-specified name to make programming algorithms in cQASM more ergonomic. When expressions were added however, it made sense to extend the definition to any kind of expression, allowing it to be reused for all the things listed above.

Subcircuit headers

Subcircuit header statements can be used to divide an algorithm up into logical subsections, and may also be used to specify that a part of the algorithm must be repeated a constant number of times. They have the following forms:

.<name>
.<name>(<repeat-count>)

Here, <name> is a word consisting of letters, digits, and underscores, not starting with a digit. <repeat-count> is a constant positive integer. The subcircuit header signifies that all bundles up to the next subcircuit header or the end of the file belong to a subcircuit named <name>, and that that subcircuit must be evaluated <repeat-count> times. The repeat count is implicitly one when not specified.

Subcircuit headers may be annotated.

Bundles and instructions

The algorithm is ultimately described using bundles, defined to be one or more instructions issued simultaneously. They are either specified using a pipe-separated list (|) of one or more instructions on a single line, or using a multiline curly-bracket-delimited list ({ and }) of one or more pipe-separated lists of one or more instructions. For example,

<insn-a> | <insn-b> | <insn-c>
# In both the above and below case, all three instructions start simultaneously.
{
    <insn-a> | <insn-b>
    <insn-c>
}

The instruction format is documented in the next section.

Note

Instructions are not statements; only bundles are. A single instruction on its own line is simply a bundle with only one instruction in it.

Both the individual instructions in a bundle and the bundle as a whole can be annotated. The former takes precedence; therefore, annotating a bundle can only be done using the curly-bracket notation.

If-else chain (1.2+)

In cQASM 1.2+, branch-based conditional blocks can be constructed using if-else chains. They have the following syntax.

# Simple if statement.
if (<condition>) {
    <statements>
}

# If-else statement.
if (<condition>) {
    <statements>
} else {
    <statements>
}

# If-elif-else statement. You can repeat as many "else if" blocks as you
# like.
if (<condition>) {
    <statements>
} else if (<condition>) {
    <statements>
} else {
    <statements>
}

The shown newlines are optional. The {s and }s surrounding the code blocks are mandatory.

Note

The {} blocks do not imply parallel execution in this context.

The conditions must evaluate to a boolean.

C-style for loop (1.2+)

In cQASM 1.2+, C-style for loops can be written as follows.

for (<initialize>; <condition>; <update>) {
    <statements>
}

<initialize> is optional. If specified, it must be of the form <name> = <expression>, representing an initializing assignment statement. It is executed at the start of the loop.

Note

Unlike C, it is not possible to declare a new variable as part of <initialize>.

<condition> must be an expression that evaluates to a single boolean. It is evaluated at the start of each loop iteration. If it yields true, iteration continues; if it yields false, execution continues after the for loop.

<update> is optional. If specified, it must be of the form <name> = <expression>, representing an assignment statement. It is executed at the end of each loop iteration. It is intended to be used to update the loop variable.

The loop body may include continue and break statements.

Note

The {} block for the loop body does not imply parallel execution.

Foreach loop (1.2+)

In cQASM 1.2+, a loop that iterates over a range of integer values can be written as follows.

foreach (<name> = <from> .. <to>) {
    <statements>
}

<name> must be an integer variable, and <from> and <to> must constant-propagate to integer literals; that is, both integers must be known at compile-time. The loop body will be executed for all values in the specified inclusive range. If <from> is less than <to>, <name> will be incremented by one after each iteration. If <from> is greater than <to>, <name> will be decremented by one after each iteration.

Behavior is undefined if <name> is reassigned from within the loop body.

The loop body may include continue and break statements.

Note

The {} block for the loop body does not imply parallel execution.

While loop (1.2+)

In cQASM 1.2+, a loop that iterates while a condition is true can be written as follows.

while (<condition>) {
    <statements>
}

<condition> must be an expression that evaluates to a boolean. It is evaluated at the before a new iteration. If it evaluates to true, iteration will continue. Otherwise, execution will continue after the while loop.

The loop body may include continue and break statements.

Note

The {} block for the loop body does not imply parallel execution.

Repeat-until loop (1.2+)

In cQASM 1.2+, a loop that iterates until a condition is true can be written as follows.

repeat {
    <statements>
} until (<condition>)

<condition> must be an expression that evaluates to a boolean. It is evaluated at the end of each iteration. If it evaluates to false, iteration will continue. Otherwise, execution will continue after the repeat-until loop.

The loop body may include continue and break statements.

Note

The {} block for the loop body does not imply parallel execution.

Break statement (1.2+)

In cQASM 1.2+, the innermost loop can be terminated at any time by means of a break statement. The syntax is simply:

break

It is illegal to use a break statement outside of the context of a structured loop (for, foreach, while, or repeat-until). A subcircuit with a repetition count does not qualify as a loop in this context.

Continue statement (1.2+)

In cQASM 1.2+, the current loop iteration can be stopped at any time by means of a continue statement. Execution will continue as if the end of the loop body had been reached. The syntax is simply:

continue

It is illegal to use a continue statement outside of the context of a structured loop (for, foreach, while, or repeat-until). A subcircuit with a repetition count does not qualify as a loop in this context.

Instructions

Instructions, also called gates, are used to represent the operations that must be performed in parallel or in sequence to form the desired algorithm. In cQASM 1.x, they follow an assembly-like syntax:

<name> <comma-separated-operands>

Note the lack of parentheses around the operands.

Note

libqasm does not specify an exact instruction set or semantics for instructions. This is to provide compatibility with the configurable instruction set in OpenQL. However, for many of cQASM’s use cases, it makes sense to have a standardized instruction set (historically, this instruction set was in fact baked into the language). This standardized instruction set is specified at the bottom of this section.

Assignment instruction

Special syntax is available for the set instruction, namely:

set <expr> = <expr>

This intends to make variable assignments more readable.

Note

In cQASM 1.0 and 1.1, set behaves like a normal instruction with two operands. That means it must be defined as part of the instruction set via the API prior to parsing if the target supports it. In cQASM 1.2+, set is a special instruction that requires no definition; cQASM 1.2+ readers are expected to support set instructions in all cases.

Goto instruction (1.2+)

In cQASM 1.2+, unstructured control-flow can be represented using (non-repeating) subcircuits as labels and goto instructions. The syntax for a goto instruction is:

goto <name>

<name> must match the name of exactly one subcircuit header in the program.

Note

goto is a special instruction that bypasses the type system in order to correctly resolve the subcircuit name. It is always defined, regardless of whether a goto instruction is added to the instruction set via the API. In file versions older than 1.2, goto behaves like a normal instruction.

Note

Conditional branches can be represented using the notation for conditional execution as defined in the next section.

Conditional instructions

Instructions can be made conditional; that is, the instruction should only execute if a certain classical boolean condition evaluates to true at runtime. This can be used to alter the behavior of an algorithm based on previous measurement results, for example to apply error correction. An instruction can be made conditional in two ways:

cond (<condition>) <name> <operands>
c-<name> <condition>, <operands>

Both forms are equivalent. The latter mostly exists for backward-compatibility, as the former is considered more readable.

Whenever multiple measurement bits in the b register are selected for a condition simultaneously, the states of these bits must be implicitly combined using an AND gate at runtime.

Note

You cannot specify multiple condition bits by comma-separating the bits. You have to use the slice notation on the bit register accordingly if you want this. For example,

# c-x b[0], b[1], q[2]      <- not legal!
c-x b[0,1], q[2]          # <- correct variant.

Note

While syntactically identical, specification of multiple condition bits for a gate is completely unrelated to the single-gate-multiple-qubit notation described in the next subsection. Do not confuse the two! The reason for this oddity is, as usual, a historical one.

Note

Selecting multiple bits at once is only supported within the condition of a gate, or as a gate operand via single-gate-multiple-qubit notation. It is illegal in all other contexts.

Single-gate-multiple-qubit

To more ergonomically specify many of the same gate operating in parallel on multiple qubits, each qubit argument of a gate may be given more than one qubit through the slice notation for the q register. The individual qubits are then broadcast to individual gates. For example,

x q[0:3]
# is functionally equivalent to
x q[0] | x q[1] | x q[2] | x q[3]

For multi-qubit gates, the slice sizes for each qubit operand must match;

# cnot q[0], q[1,2] <- not legal, q[0] is not broadcast

Note that this never makes any sense for qubits, as a qubit can only be used once within a group of parallel gates, and single-gate-multiple-qubit notation asserts that the gates start simultaneously.

Warning

The original libqasm API asserts uniqueness of the qubits in the slices by sorting the list of indices and silently removing duplicates. This is very much a bug:

cnot q[1,2], q[3, 0]
# becomes equivalent to...
cnot q[1], q[0] | cnot q[2], q[3]
# in the old API, but becomes
cnot q[1], q[3] | cnot q[2], q[0]
# in the new API (as you should expect)!

This behavior was kept in place in the original libqasm API for backward compatibility, but the new API doesn’t do the sort. In general, it is best to avoid single-gate-multiple-qubit notation for multi-qubit gates.

Default instruction set

Whenever libqasm’s original API is used or the new API is instructed to use the default instruction set, libqasm determines the supported set of instructions and (implicitly) their semantics and, to some extent, timing behavior. The instruction set is as follows.

x <qubit>

The Pauli-X gate is a single-qubit rotation through π radians around the X-axis.

y <qubit>

The Pauli-Y gate is a single-qubit rotation through π radians around the Y-axis.

z <qubit>

The Pauli-Z gate is a single-qubit rotation through π radians around the Z-axis.

i <qubit>

The identity gate leaves the state of a qubit unchanged. It thus acts as a no-operation gate, which may be useful for certain simulation error models.

h <qubit>

The Hadamard gate is used to create a superposition of the two basis states.

x90 <qubit>

The x90 gate is a single-qubit rotation through 1/2 π radians around the X-axis.

mx90 <qubit>

The mx90 gate is a single-qubit rotation through negative 1/2 π radians around the X-axis.

y90 <qubit>

The y90 gate is a single-qubit rotation through 1/2 π radians around the Y-axis.

my90 <qubit>

The my90 gate is a single-qubit rotation through negative 1/2 π radians around the Y-axis.

s <qubit>

The S gate is a single-qubit rotation through 1/2 π radians around the Z-axis.

sdag <qubit>

The S-dagger gate is a single-qubit rotation through negative 1/2 π radians around the Z-axis.

t <qubit>

The T gate is a single-qubit rotation through 1/4 π radians around the Z-axis.

tdag <qubit>

The T-dagger gate is a single-qubit rotation through negative 1/4 π radians around the Z-axis.

rx <qubit>, <angle>

Performs an arbitrary rotation around the X axis on the given qubit. The angle is specified in radians.

ry <qubit>, <angle>

Performs an arbitrary rotation around the Y axis on the given qubit. The angle is specified in radians.

rz <qubit>, <angle>

Performs an arbitrary rotation around the Z axis on the given qubit. The angle is specified in radians.

u <qubit>, <matrix>

The U gate applies an arbitrary single-qubit unitary gate to the given qubit. The matrix must be a complex 2-by-2 unitary matrix, though libqasm does not assert the unitary condition of the matrix.

cnot <qubit>, <qubit>

Applies a CNOT (controlled X) gate on the given qubits. The first qubit is the control, the second qubit is the target.

cz <qubit>, <qubit>

Applies a controlled phase (controlled Z) gate on the given qubit pair.

swap <qubit>, <qubit>

Swaps the state of the given two qubits.

cr <qubit>, <qubit>, <angle>

Applies a controlled phase (controlled Z) gate with the given rotation angle in radians on the given qubit pair. The first qubit is the control qubit, the second is the target.

cr <qubit>, <qubit>, <k>

Applies a controlled phase (controlled Z) gate with the given rotation angle on the given qubit pair. The rotation angle is π/2k radians. The first qubit is the control qubit, the second is the target.

toffoli <qubit>, <qubit>, <qubit>

Applies a Toffoli gate (controlled X with two control qubits) on the given qubits. The first two qubits are the control qubits, the third is the target.

prep <qubit>

Prepares the given qubit in the Z basis (|0>). Synomym for prep_z.

prep_x <qubit>

Prepares the given qubit in the X basis.

prep_y <qubit>

Prepares the given qubit in the Y basis.

prep_z <qubit>

Prepares the given qubit in the Z basis (|0>). Synomym for prep.

measure <qubit>

Measures the given qubit in the Z basis. |0> results in false, |1> results in true. If the qubit is part of the qubit register, the measurement is stored in the accompanying measurement bit; if it is a variable, the result is discarded. Synonym for measure_z.

measure_x <qubit>

Measures the given qubit in the X basis. If the qubit is part of the qubit register, the measurement is stored in the accompanying measurement bit; if it is a variable, the result is discarded.

measure_y <qubit>

Measures the given qubit in the Y basis. If the qubit is part of the qubit register, the measurement is stored in the accompanying measurement bit; if it is a variable, the result is discarded.

measure_z <qubit>

Measures the given qubit in the Z basis. |0> results in false, |1> results in true. If the qubit is part of the qubit register, the measurement is stored in the accompanying measurement bit; if it is a variable, the result is discarded. Synonym for measure.

measure_all

Measures all qubits in the qubit register in the Z basis, and stores the results in the measurement bit register. |0> results in false, |1> results in true. This instruction cannot share a bundle with other instructions.

measure_parity <qubit>, <axis>, <qubit>, <axis>

Refer to section IV-A of the arXiv paper.

skip <integer>

Skip the specified number of cycles. The bundle following the skip will start the given amount plus one cycle after the bundle preceding the skip. For example:

x q[0]  # starts in cycle i
skip 3  # starts in cycle i+1
x q[0]  # starts in cycle i+4

This instruction cannot share a bundle with other instructions.

wait <integer>

Wait for all previous instructions to finish, then wait the given number of cycles before starting the next bundle. Also known as a barrier. In essence, this acts as a no-op instruction with the specified duration in cycles, that requires access to all qubits, bits, and variables.

Note

When OpenQL is used, you should use wait instructions rather than skip to introduce delays. The scheduler will ignore any other timing semantics in your program, including whether you placed instructions in a bundle or not. The timing of the algorithm after scheduling will be represented using skip instructions and bundles exclusively. Nevertheless, the wait instructions will remain, so the requested (minimum) delays are also represented, so running the scheduler again would not break the program. For example,

x q[0]
wait 3
x q[1]

may compile into

x q[0]  # starts in cycle 0
wait 3  # starts in cycle 1
skip 2  # starts in cycle 2
x q[1]  # starts in cycle 4

not <bit-ref>

Inverts the state of the given measurement bit register or classical bit variable.

display [bit-ref]

Meta-instruction for simulators, telling the simulator to print the bit and qubit state for the given bit reference, or for all qubits if the optional reference is omitted. This instruction cannot share a bundle with other instructions.

display_binary [bit-ref]

Meta-instruction for simulators, telling the simulator to print the given measurement bit state, or the state of all measurement bits in the register if the optional reference is omitted. This instruction cannot share a bundle with other instructions.

reset-averaging [qubit]

Meta-instruction for simulators, telling the simulator to reset internal averaging counters for all measurements performed up to that point for the given qubit(s), or all qubits in the register if no reference is specified. This instruction cannot share a bundle with other instructions.

Historical

This instruction uses a dash in the name instead of an underscore for some reason, requiring a special case in the tokenizer (after all, it would be a subtraction otherwise). It is not possible to define custom instructions with dashes in them other than exactly reset-averaging.

load_state <filename>

Meta-instruction for simulators, telling the simulator to load the qubit register state from the given filename. The filename is to be specified as a string literal. This instruction cannot share a bundle with other instructions.

Expressions

All operands in cQASM 1.x can in principle be arbitrary expressions, as long as the type system (explained in the next section) considers them valid within context. The primary use cases of expressions in cQASM are indexing into the qubit and measurement bit registers, writing things more intuitively through constant propagation (for instance by scaling a matrix using scalar-matrix multiplication to make it unitary), and combining measurement results together for gate conditions. This chapter lists the available types of expressions.

Number literals

cQASM 1.x supports unsigned integer literals in decimal format only. Floating point literals use a period (.) decimal separator. The integer part may be omitted if zero, but the fractional part may not be; for example, .1 is legal, but 0. is not. Power-of-ten exponent notation is supported; for example, 1.0e3 is the same as 1000.0.

Negative numbers are expressed by applying the unary negation operator on a positive number literal (this of course looks the same as a negative number literal, but this is how it works internally). Complex numbers are expressed using sums and products of real numbers and the imaginary unit (im, a named constant).

Matrix literals

Matrix literals are lists of (constant) numbers enclosed within square brackets ([ and ]). Separation of elements along the minor axis is done using commas (,), while separation of elements along the major axis is done using newlines or semicolons. An optional newline (or semicolon) may appear between the opening bracket and the first element, and/or between the last element and the closing bracket.

[1, 2, 3]     # 3-entry row vector
[1; 2; 3]     # 3-entry column vector
[1, 2; 3, 4]  # 2x2 matrix
[
    1, 2
    3, 4
]  # alternative notation for the same 2x2 matrix

cQASM 1.x only supports real- and complex-valued matrices.

String and JSON literals

String literals are enclosed in double quotation marks on either end (") as in C. Newlines embedded in the string become part of the string unless escaped with a backslash (\). Escape sequences exist for tab (\t), newline (\n), single quote (\'), double quote (\") and backslash (\\).

JSON objects can be represented as-is, except the outermost curly brackets must be replaced by {| and |} to disambiguate between bundle notation and a JSON literal. The contents of the JSON object support everything that the JSON specification supports, but libqasm makes no attempt to parse or validate the contents as such.

Note that these types of literals are primarily intended for use in annotations and meta-instructions such as a print statement for simulation, as they don’t map cleanly to hardware.

Mappings, references, and named literals

Any word consisting of letters, numbers, and/or underscores (_) and not starting with a number that appears where an expression is expected and isn’t a function name, may be any of the following things:

  • a user-specified mapping, defined by a preceding map statement;
  • a reference to the qubit register, defined by the qubits statement to the letter q;
  • a reference to the measurement bit register, defined by the qubits statement to the letter b;
  • a reference to a user-specific variable, defined by a var statement (if supported by the target);
  • one of the following named constants: - pi for 3.1415…, - eu for 2.7182…, - im for the imaginary unit, - true and false for booleans, and - x, y, and z for axes;
  • a target-specific reference.

Internally, all these things are actually mappings. The latter two are called “initial mappings;” they are defined using libqasm’s API during construction of the parser object.

Indexation and slicing

The qubit and measurement bit registers behave like one-dimensional vectors that can be indexed and sliced. The notation is <register>[<index-list>], where <index-list>> is a comma-separated list of integer indices or ranges. Range notation specifically consists of a colon-separated (:) pair of integers representing the lower and upper limit of an inclusive, ascending range; 1:4 is for instance equivalent to 1,2,3,4.

The result of the index operator behaves as a new reference to a qubit or bit register, but of the size equal to the number of indices in the slice (it can thus also be larger than the original register if indices are repeated) and with the elements in a potentially different order. The indices must be constant, and indexation/slicing is only supported on qubit and measurement bit registers (not on matrices).

Functions

Functions have the form <name>(<comma-separated-arguments>); you should recognize this notation from basically every other programming language out there. They can be used for constant propagation, or (when supported by the target) to describe classical operation on data. cQASM 1.x does not provide a way to define functions in the cQASM file; instead, they are defined by the program or library using libqasm prior to parsing. This program or library is free to define whatever functions they want, but a default set is provided by libqasm as a sort of standard library. This library currently includes the following:

  • sqrt(x): returns the square root of x, where x is a real or complex number.
  • exp(x): returns ex, where x is a real or complex number.
  • log(x): returns the natural logarithm of x, where x is a real or complex number.
  • sin(x): returns the sine of x, where x is a real or complex number.
  • cos(x): returns the cosine of x, where x is a real or complex number.
  • tan(x): returns the tangent of x, where x is a real or complex number.
  • asin(x): returns the arcsine of x, where x is a real or complex number.
  • acos(x): returns the arccosine of x, where x is a real or complex number.
  • atan(x): returns the arctangent of x, where x is a real or complex number.
  • sinh(x): returns the hyperbolic sine of x, where x is a real or complex number.
  • cosh(x): returns the hyperbolic cosine of x, where x is a real or complex number.
  • tanh(x): returns the hyperbolic tangent of x, where x is a real or complex number.
  • asinh(x): returns the hyperbolic arcsine of x, where x is a real or complex number.
  • acosh(x): returns the hyperbolic arccosine of x, where x is a real or complex number.
  • atanh(x): returns the hyperbolic arctangent of x, where x is a real or complex number.
  • abs(x): returns the absolute value of x, where x is an integer or a real number.
  • complex(r, i): returns the complex number with real part r and imaginary part i, where r and i are real numbers.
  • polar(norm, arg): returns the complex number with magnitude norm and angle arg (in radians), where norm and arg are real numbers.
  • real(c): returns the real part of the given complex number.
  • imag(c): returns the imaginary part of the given complex number.
  • arg(c): returns the argument of the given complex number in radians.
  • norm(c): returns the norm of the given complex number.
  • conj(c): returns the conjugate of the given complex number.

Functions operating on matrices are not yet part of the standard library, because there’s quite a bit of manual labor involved with implementing those. Please make an issue in the libqasm GitHub project if you would like to have them.

cQASM 1.0 does not support functions evaluated at runtime; only constant propagation is supported. cQASM 1.1 adds support for such dynamic expressions, but the default set of functions is still constant-propagation-only.

Unary, binary, and ternary operators

cQASM 1.x supports various unary and binary operators, as well as the ternary conditional operator from C. Internally, these are just syntactic sugar for functions named operator followed by the prefix or infix symbols representing the operator. The full list, along with precedence rules, is listed below. Deviations from the C standard are specified.

Operator Description Precedence and associativity
-x Negation 1, ←
!x Logical NOT
~x Bitwise NOT
x ** y Exponentiation (Python) 2, ←
x * y Multiplication 3, →
x / y True division (Python)
x // y Flooring division (Python)
x % y Modulo (Python)
x + y Addition 4, →
x - y Subtraction
x << y Shift left 5, →
x >> y Arithmetic shift right (Java)
x >>> y Logical shift right (Java)
x < y Less than 6, →
x <= y Less than or equal
x > y Greater than
x >= y Greater than or equal
x == y Equality 7, →
x != y Inequality
x & y Bitwise AND 8, →
x ^ y Bitwise XOR 9, →
x \| y Bitwise OR (*) 10, →
x && y Logical AND 11, →
x ^^ y Logical XOR (*) 12, →
x \|\| y Logical OR 13, →
x ? y : z Ternary conditional 14, ←

The arithmetic operators deviating from C were taken from Python because Python’s behavior is more intuitive when doing high-level math. Integer division and accompanying modulo/remainder follow Python’s behavior as well (sign of remainder = sign of divider), because the round-to-zero behavior in the C standard is more historical than it is useful.

The right-shift operators follow Java, because (like Java) cQASM doesn’t have unsigned integers. Therefore, disambiguation of logical (shift in zeros) vs. arithmetic (shift in sign bit) for right-shift has to be done using the operator rather than the operand types.

The | operator (bitwise OR) requires further explanation in cQASM 1.x, because it conflicts with the | separator in bundle notation. To avoid breaking anything, the | operator is illegal as argument to a gate without at least one level of parentheses surrounding it; | always defaults to separating gates in a bundle otherwise. This applies even when the resulting parse tree would be invalid; this is a limitation of LALR(1) parsers such as Bison, the one used by libqasm.

The ^^ operator is typically missing from languages, because one of the primary purposes of it is “short-circuiting” of operand evaluation, and an XOR gate always requires both operands to be evaluated. Nevertheless, there are several arguments for including it anyway, even in C. Not mentioned there is that it is also a natural expansion of the operator precedence table. Worth noting also is that side effects of expressions are not first-class citizens in cQASM as they are in C (libqasm makes no assertion about whether they can have side effects or not), so the short-circuiting argument is moot anyway. Finally, the existence of the expression doesn’t hurt anyone; it would be invalid syntax would it not exist, and the program/library using libqasm is free to not register any implementation for operator^^, thus disabling it entirely.

cQASM 1.0 does not support operations evaluated at runtime; only constant propagation is supported. cQASM 1.1 adds support for such dynamic expressions, but the default set of operations is still constant-propagation-only.

Grouping parentheses

Parentheses (( and )) may be placed around expressions to order expressions differently than what the precedence rules would result in, or to disambiguate between the pipe (|) used for separating instructions in bundle notation and the bitwise OR operator.

Type system

During semantic analysis, libqasm assigns data types to expressions trees in depth-first order. This type system knows the following types:

  • qubits;
  • bits (a.k.a. booleans);
  • axes (X, Y, or Z);
  • integers (64-bit signed in libqasm, may be less in target);
  • real scalars (IEEE 754 double precision in libqasm, may be different in target);
  • complex scalars (2x IEEE 754 double precision for real and imaginary components in libqasm, may be different in target);
  • real vectors and matrices (IEEE 754 double precision per element in libqasm, may be different in target);
  • complex vectors and matrices (2x IEEE 754 double precision for real and imaginary components for each element in libqasm, may be different in target);
  • strings (libqasm makes no assertions about encoding); and
  • JSON strings.

Types are used in semantic analysis to check whether the operands for instructions, functions, and operators make sense (for example, X 1 makes no sense; you can’t apply an X gate to an integer), and in some cases are used to disambiguate between multiple overloads of the same gate or function.

Historical

The above used to be hardcoded in the grammar. That is, X 1 wouldn’t parse not because of a type system, but because the X keyword had to be followed by a single qubit. This is a bad idea for many reasons, such as readability of the grammar specification, maintainability, inability to redefine X to refer to a qubit using a mapping, and lack of configurability of the instruction set as done in OpenQL. It is, however, easy to write things down this way that are difficult to express with a type system. Combined with the backward compatibility requirement when instruction set configurability became important, this relatively extensive type system had to be introduced.

Constant propagation and coercion

While doing type analysis, libqasm also performs constant propagation where applicable. This means that the expression 1 + 3 is completely equivalent to just 4. Wherever constants are expected, you can still use operators and functions with constant propagation rules to help make the code clearer.

libqasm also evaluates promotion rules during this process. Specifically;

  • If an integer constant is found but a real number is expected, the integer is implicitly converted to a real number.

  • If an integer or real number constant is found but a complex number is expected, the integer or real number is implicitly converted to a complex number.

  • If a constant real matrix is found but a complex matrix is expected, and the real matrix has dimensions matching the ones expected for the complex matrix, the real matrix is converted to a complex matrix.

  • If a constant real row vector with 2n2 entries is found but a complex matrix of size n by n is expected, the elements in the vector are interpreted as real-imaginary pairs in row-major order, and the value is converted accordingly. For example:

    [1, 2, 3, 4, 5, 6, 7, 8]
    # is promoted to
    [
        1 + 2*im, 3 + 4*im
        5 + 6*im, 7 + 8*im
    ]
    # whenever a 2x2 complex matrix is expected.
    

    This rule exists only for backward compatibility for the one-qubit U gate.

Note that all promotion rules only apply to constants. When a target supports dynamic evaluation, it must provide functions to convert between types if the target supports this, and the user must explicitly use these functions.

Annotations

Annotations may be used to attach arbitrary data to various constructs in a cQASM 1.x file, in order to communicate things to the target that cQASM 1.x does not know about. For example, you might want to annotate a qubit with its specific decoherence time, or a gate with additional error information. The intention of annotations is that the program is still valid and sensible if all annotations are ignored.

Annotations have the following forms:

<object-to-annotate> @<interface>.<operation>
<object-to-annotate> @<interface>.<operation>()
<object-to-annotate> @<interface>.<operation>(<comma-separated-operands>)

Here, <interface> and <operation> are two identifiers (words consisting of letters, digits, and underscores, not starting with a digit) that specify the high-level behavior of the annotation. The first should ideally be named the same as the program or library that the annotation targets, while the second should signify what the annotation means. libqasm makes no assertion about the number of operands and their types; it is up to the target to provide error checking for this. A target that supports a certain value for <interface> should throw an error when it encounters an annotation with that interface but an unknown operation, as this is likely to indicate that the cQASM file was written for a newer version of the target.

Annotatable objects can take any number of annotations by simply chaining annotations. For example:

x q[1] @sim.model("high-accuracy") @insn.duration(10)

The following constructs can be annotated in cQASM 1.x:

  • error models;
  • instructions;
  • bundles;
  • subcircuit headers;
  • mappings;
  • and variables (version 1.1+).

C++ API

The C++ API documentation is generated by Doxygen.

Python API

Besides the intended usage from C++, libqasm also has a (comparatively simplistic) Python interface, installable via the libqasm PyPI package or using pip3 install -v -e . (or equivalent) from the root directory of the repository. This exposes two modules, libQasm and cqasm.v1. The former exists for backward compatibility with the original Python API, and supports only cQASM 1.0 and the default instruction set. The latter supports up to cQASM 1.2, mirroring the C++ API.

Documentation is unfortunately scarce, as the Python interface has not been a priority compared to the C++ interface. The new API is rather straightforward, however. This should hopefully get you started:

import cqasm.v1
analyzer = cqasm.v1.Analyzer('1.2')
result = a.analyze_string('version 1.2; qubits 3; x q[0]')

The argument passed to the Analyzer() constructor specifies the API version that your application supports. This version follows the cQASM file versions. This ensures that you don’t get problems when a new version of libqasm comes out that supports a newer version; if the user then passes a file that’s too new, they get an error message stating the maximum version, rather than a cryptic error down the line when your application receives something from libqasm that it doesn’t understand.

The Analyzer() has an optional second argument, too. This is a boolean, named without_defaults, defaulting to False. When set to True, the process that registers the default instruction set and error models with the analyzer is skipped. You can then add instructions to the instruction set using the register_instruction() method:

Analyzer.register_instruction(
    name,
    param_types='',
    allow_conditional=True,
    allow_parallel=True,
    allow_reused_qubits=False,
    allow_different_index_sizes=False
)

The param_types argument specifies the allowed operand types for the instruction. Each character represents an operand:

  • Q = qubit;
  • B = assignable bit/boolean (measurement register);
  • b = bit/boolean;
  • a = axis (x, y, or z);
  • i = integer;
  • r = real;
  • c = complex;
  • u = complex matrix of size 4^n, where n is the number of qubits in the parameter list (automatically deduced);
  • s = (quoted) string;
  • j = json.

The rest of the parameters should be fairly obvious.

It is also possibly to disable the default instruction set and not add any instructions. In this case – rather than rejecting all cQASM files – libqasm will accept any instruction with any operand list, allowing you to do instruction set management yourself.

You can register error models in much the same way, if you want:

Analyzer.register_error_model(
    name,
    param_types=''
)

Besides analyze_string(data), the Analyzer class also has analyze_file(filename), parse_string(data), and parse_file(filename) variants. The difference between *_string and *_file should be obvious; the difference between analyze_* and parse_* is that the former runs the complete analysis process, while the latter gives you the raw parse result. You’ll almost always want the former.

The result of these functions is either a list of error messages or a tree structure. The tree structure is quite involved, but also very easy to traverse once you have one in Python. Simply print the value to get a multiline, pretty-printed representation of the (sub)tree. Tree access (or manipulation, if you want) is then done via the usual Python operators (. for field access, [] for indexing into lists, and so on).

Some additional information may be available via the C++ documentation here. Note however that some stuff here may be inaccurate or incomplete, as it only documents the C++ portion, and some Python stuff is added on top to make the interface more Pythonic. This unfortunately does not have generated documentation at this time.