Annotations
Qubit Classification
This section provides definitions of key terms to ensure consistent usage throughout the document.
- node
The smallest model element in LEQO, representing a logical operation with defined inputs and outputs; each node has an associated snippet that implements its behavior.
- snippet
An OpenQASM code fragment that serves as a concrete implementation attached to a node, defining quantum operations on specified qubits.
- backend
The LEQO component responsible for compiling, optimizing, and executing snippets, transforming high-level models into executable quantum instructions.
In LEQO’s OpenQASM snippet-based design, a set of semantic labels is introduced to characterize how qubits are utilized and managed, enabling more effective reasoning about the qubit lifecycle and the data flow between snippets.
Name |
Description |
---|---|
Linking Qubit |
A qubit annotated with |
Clean Ancilla |
A qubit declared in the current snippet without any annotation, that has already been initialized to |
Dirty Ancilla |
A qubit used for temporary computation that may be entangled or hold an unknown state.
Declared with |
Name |
Description |
---|---|
Linking Qubit |
A qubit annotated with |
Reusable Ancilla |
A qubit explicitly reset to |
Uncomputable Ancilla |
A qubit annotated with |
Entangled Ancilla |
A qubit that is neither annotated with |
Annotation Usage Overview
A developer can create mappings between a concrete implementation (openqasm) and a program modeled in the frontend by using various openqasm3 annotations.
Annotations can be emulated in openqasm2 by using special comments.
Warning
The whole line will be interpreted like an annotation. Therefore you cannot use inline-comments on annotations!
Input
Input qubits, also referred to as linking qubits, act as entry points for quantum states transferred between snippets (see Linking Qubit definition). They may exist in arbitrary states, including entangled configurations.
One input is defined as a single qubit register (QubitDeclaration
) with a single @leqo.input
annotation.
The annotation specifies the index of the corresponding input.
Inputs can be of arbitrary size (See Memory Layout)
- Inputs have to be defined as contiguous memory
One
QubitDeclaration
corresponds to one input
- Input indices must be selected from a contiguous range of integers starting at 0
No skips, no duplicates
Inputs may be declared anywhere in code
Input annotations may only appear on a
QubitDeclaration
Input annotations may only appear once per statement
1// Qubit array
2@leqo.input <<InputIndex>>
3qubit[<<length>>] someName;
1// Single qubit
2@leqo.input <<InputIndex>>
3qubit someName;
<<InputIndex>>
is replaced with the index of an input (positive integer literal)
1// Example
2@leqo.input 0
3qubit someName;
Warning
input cannot be used as an identifier as it is a keyword in OpenQASM3.
OpenQASM 2
Input annotations in OpenQASM 2 are expressed using comment syntax:
1// @leqo.input 0
2qreg someName;
3
4// @leqo.input 1
5qreg someOtherName[3];
Note
All constraints mentioned above for OpenQASM 3 also apply here
Memory Layout
There is no explicit limit to the size (qubit count) of an input. However, the number of physical qubits on a real device is limited. See Reusable Ancillae for workarounds.
The annotated input size must match the input size of the corresponding node from the visual model. Inputs are expected to be Little Endian.
The backend actively ensures that input qubits are initialized.
All other qubits can be assumed to be |0⟩
.
Note
The specification allows implementors of openqasm3 to initialize qubits to an undefined state.
However, in practice major implementations (e.g. IBM) initialize qubits to |0⟩
.
In the future, it is planned to allow to input less qubits than specified using the annotation. In this case the backend would fill the lowest bytes with the actual input and ensure the upper bytes are initialized to zero:
Example input register of size 7 0
1
2
3
4
5
6
p[0]
p[1]
p[2]
p[3]
p[4]
p[5]
p[6]
p[0]
p[1]
|0⟩
|0⟩
|0⟩
|0⟩
|0⟩
Output
Output qubits, also referred to as linking qubits, act as exit points for quantum states transferred between snippets (see Linking Qubit definition). They may exist in arbitrary states, including entangled configurations.
One output is defined as a single alias (AliasStatement
) with a single @leqo.output
annotation.
The annotation specifies the index of the corresponding output.
One qubit may only be used in one output at most
Outputs may be concatenated from multiple non-contiguous blocks of memory.
- Output indices must be selected from a contiguous range of integers starting at 0
No skips, no duplicates
Outputs may be declared anywhere in code
Outputs may be used like any other alias
Output annotations may only appear above a
AliasStatement
pointing to qubitsOutput annotations may only appear once per statement
1@leqo.output <<OutputIndex>>
2let someOutput = <<Expression>>;
<<OutputIndex>>
is replaced with the index of an output (positive integer literal)
1// Example
2qubit[10] a;
3qubit[4] b;
4
5@leqo.output 0
6let output1 = a[1:2:3] ++ b[{1,2,3}];
Note
Even if the ouput alias is not used in code, an alias must be defined to mark qubits as linking qubits. The identifier is insignificant.
Warning
output cannot be used as an identifier as it is a keyword in OpenQASM3.
OpenQASM 2
OpenQASM 2 does not support aliases. As a result, output annotations must include both the annotation and alias in comments:
1qreg a[4];
2qreg b[3];
3
4// @leqo.output 0
5// let outputQubits = a[0:2] ++ b[{1, 2}];
Note
All constraints mentioned above for OpenQASM 3 also apply here
Reusable Ancillae
If the programmer manually resets a qubit they can mark it as reusable. To do so, one can declare an alias to the reusable qubits.
Reusable ancillae may not be marked as output
Reusable annotated aliases may be declared anywhere in code
Reusable annotated aliases may be used like any other alias
Reusable annotations may only appear above a
AliasStatement
pointing to qubitsReusable annotations may only appear once per statement
Reusable annotations mark qubits that are no longer entangled and reset to
|0⟩
This actions has to be manually implemented by the user and guarantees that the backend is free to reuse the qubit
1@leqo.reusable
2let reusable1 = <<Expression>>;
1// Example
2@leqo.reusable
3let reusable1 = a[0];
Note
Even if the reusable alias is not used in code, an alias must be defined to mark qubits as reusable. The identifier is insignificant.
OpenQASM 2
As with outputs, reusable annotations must be fully written as comments:
1// @leqo.reusable
2// let ancillaOut = a[0];
Note
All constraints mentioned above for OpenQASM 3 also apply here
Entangled & Dirty Ancillae
If qubits are used within a snippet and are not annotated with either @leqo.output
or @leqo.reusable
,
they are classified as entangled ancillae.
When those same qubits are explicitly used as input qubits in later snippets, they are regarded as dirty ancillae.
Dirty ancillae may exist in an arbitrary quantum state, potentially entangled with other qubits,
and must be explicitly annotated with @leqo.dirty
to signify their intentional reuse as inputs in a snippet.
To use dirty ancillae within a snippet, the programmer must explicitly opt in by annotating the qubit declaration with @leqo.dirty
.
The
@leqo.dirty
annotation follows the same implementation rules as input definitions, but omits indexing, as defined in input definition
Warning
The state of a dirty ancilla qubit can be altered temporarily but must be restored at the end of a snippet. Therefore measuring or uncompute operations on a dirty qubit are not permitted.
1// Single dirty ancilla
2@leqo.dirty
3qubit singleDirtyAncilla;
1// Dirty ancilla array
2@leqo.dirty
3qubit[<<length>>] dirtyAncillaArray;
OpenQASM 2
Dirty qubits in OpenQASM 2 follow the same pattern with standalone comments:
1// @leqo.dirty
2qreg dirtyTemp[2];
Note
All constraints mentioned above for OpenQASM 3 also apply here
Uncomputation
When modified clean ancillae or linking qubits can be uncomputed, the programmer may provide an explicit uncomputation block to allow for safe reuse of those qubits.
This is done using the @leqo.uncompute
annotation, which defines a scoped region that is disabled by default via an if (false)
statement.
The compiler may override this statement to true
if uncomputation of the associated qubits is determined to be necessary.
A qubit annotated with @leqo.reusable
within such a block is referred to as an uncomputable ancilla.
The
@leqo.uncompute
annotation must appear directly above anif (false)
statement with a block body that must not be followed by anelse
statement@leqo.uncompute
annotations may appear multiple times in a program, each time referring to different uncomputation logicNested
@leqo.uncompute
if-blocks are not allowedA
@leqo.uncompute
if-block must be declared at global scopeAll
@leqo.uncompute
blocks together must reverse all transformations on the associated qubit, removing entanglement and restoring each to the|0⟩
state@leqo.uncompute
blocks only operate on existing variables, qubits or selfdeclared aliasesA
@leqo.uncompute
if-block must declare the uncomputed ancillae as reusable qubits by using the corresponding@leqo.reusable
annotation
Warning
Qubits previously annotated with @leqo.dirty
must not be uncomputed.
This constraint is not enforced by the backend; it is the user’s responsibility to ensure compliance.
Violating this rule may lead to undefined behavior or incorrect program semantics.
1@leqo.uncompute
2if (false) {
3 h reusable1; // some uncompute operation
4
5 @leqo.reusable
6 let reusable1 = dirtyAncilla1;
7}
OpenQASM 2
An uncompute block is marked explicitly, as shown below:
1// @leqo.uncompute start
2h reusable1; // some uncompute operation
3
4// @leqo.reusable
5// let reusable1 = dirtyAncilla1;
6// @leqo.uncompute end
Note
A uncompute block must start with // @leqo.uncompute start
and end with // @leqo.uncompute end