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 letterq
; - a reference to the measurement bit register, defined by the
qubits
statement to the letterb
; - 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
andfalse
for booleans, and -x
,y
, andz
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 ofx
, wherex
is a real or complex number.exp(x)
: returns ex, wherex
is a real or complex number.log(x)
: returns the natural logarithm ofx
, wherex
is a real or complex number.sin(x)
: returns the sine ofx
, wherex
is a real or complex number.cos(x)
: returns the cosine ofx
, wherex
is a real or complex number.tan(x)
: returns the tangent ofx
, wherex
is a real or complex number.asin(x)
: returns the arcsine ofx
, wherex
is a real or complex number.acos(x)
: returns the arccosine ofx
, wherex
is a real or complex number.atan(x)
: returns the arctangent ofx
, wherex
is a real or complex number.sinh(x)
: returns the hyperbolic sine ofx
, wherex
is a real or complex number.cosh(x)
: returns the hyperbolic cosine ofx
, wherex
is a real or complex number.tanh(x)
: returns the hyperbolic tangent ofx
, wherex
is a real or complex number.asinh(x)
: returns the hyperbolic arcsine ofx
, wherex
is a real or complex number.acosh(x)
: returns the hyperbolic arccosine ofx
, wherex
is a real or complex number.atanh(x)
: returns the hyperbolic arctangent ofx
, wherex
is a real or complex number.abs(x)
: returns the absolute value ofx
, wherex
is an integer or a real number.complex(r, i)
: returns the complex number with real partr
and imaginary parti
, wherer
andi
are real numbers.polar(norm, arg)
: returns the complex number with magnitudenorm
and anglearg
(in radians), wherenorm
andarg
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.