Skip to content

Syntax

To see how the language syntax works, we'll look at some examples.

Example 1 - Bell State

To begin let's consider the Bell state example.

qfunc H(target : qbit);
qfunc CX(control: qbit, target : qbit);

qfunc main(input a: qbit, input b: qbit) {
    H(a);
    CX(a, b);
}

Syntactic Elements

Function Declaration

qfunc H(TARGET : qbit);

A function declaration is a way to tell the parser that “This function exists in the engine. I’m just using it.”

All the built-in functions that exist in the JSON model also exist in the QMOD model, and you can declare them, such as X, Y, etc.

Function Ports

TARGET : qbit

To pass quantum values to/from a function, you need to declare function ports. A port declaration specifies: direction – Optionally one of input (consume the quantum value), output (pass out a new quantum value), or inout (operate on a quantum value, which is also the default). name – The name of the port. type

  • a single qbit is supported (as seen here).
  • an array of qubits with a predetermined size (qbit[size]) or an undetermined size (qbit[]).
  • a numeric scalar type (seen in example 4).

Function Definition

qfunc main(...) {
 ...
}

qfunc defines a function and its implementation. It begins with qfunc to signify it is a quantum function. It contains a name, ports, parameters (see the second example), and a body (defined with {}).

Function Call

CX(a, b);

The function call is also a type of statement. It invokes another defined function or declared functions that can be called. Autocomplete helps you find the functions.

Example 2 - RX

Another example to consider is a quantum program with a single RX with angle 0.5.

qfunc RX<theta: real>(target : qbit);
qfunc allocate<num_qubits: int>(output target : qbit[num_qubits]);


qfunc main() {
    a: qbit;
    allocate<1>(a);
    RX<0.5>(a);
}

Function Parameters

theta : real

When you want a function to receive classical parameters, you need to declare them. A port declaration to a function contains: name – The name of the parameter. classical type – The type of the parameter. Supports:

  • int for integers
  • real for real numbers
  • bool for booleans
  • Pauli for the pauli base enum
  • arrays of scalar types with specified (e.g. int[5]) or unspecified (e.g. real[]) lengths.

Parametric Function Definition

qfunc foo<...>(...) {
 ...
}

Define the classical parameters of a function in the <...> section of the declaration.

Parametric Function Call

RX<0.5>(a);

Pass classical parameters to a function in the <...> section of the call. For now the values may be literals (int or real), or an identifier relating to a classical parameter that is in the scope of the calling function.

Variable Declaration

a: qbit;

Variable declaration is a type of statement. A semi-colon ; is needed at the end of each statement in the function. It declares a variable that can be used throughout the function.

It closely relates to a function port; the only difference is that there is no need for the direction.

Indicate the size of the variable with qbit[size]. The default size of a variable is 1.

Variable Allocation

allocate<1>(a);

A variable starts in an uninitialized state. To initialize it, allocate it qubits using the allocate function with the required number of qubits as a parameter.

Example 3 - Hadamard Transform

The next example creates a generic Hadamard transform function (applying H to all qubits in a quantum variable).

qfunc my_apply_to_all<single_qubit_operand: qfunc (target: qbit)>(q: qbit[]){
  repeat<len(q), lambda<i>(){single_qubit_operand(q[i:i+1]);}>();
}

qfunc my_hadamard_transform(q: qbit[]){
  my_apply_to_all<H>(q);
}

qfunc main(output out: qbit[8]){
  allocate<8>(out);
  my_hadamard_transform(out);
}

Operand Declaration

qfunc my_apply_to_all<single_qubit_operand: qfunc (target: qbit)>(...);

Parameters to functions can also be other functions (operands), thus making the function an operator. An operand declaration is similar to a function declaration but without the function name. Like function declarations, operand declarations can also optionally declare parameters, and must declare ports.

Function Call Operand Passing

my_apply_to_all<H>(...);
repeat<..., lambda<i>(){single_qubit_operand(qbv[i:i+1]);}>();

The value of an operand can be passed in these ways:

  1. Named function: passing the name of a function that has exactly the same declaration as the operand declaration.
  2. Quantum lambda function: passing an anonymous function that is defined inline. This definition optionally has the names of the parameters, and must have the names of the quantum variables used in the body of the function.
    • The quantum variables names must be the same as the names of the ports in the operand declaration.

Quantum Variable Slicing

single_qubit_operand(qbv[i:i+1]);

The actual quantum variable passed to a function can also be a slice of some quantum variable in the caller scope. The slice takes the qubits in the variable from the index in the left (inclusive), up to the index on the right (non-inclusive). Slicing can only be used when passing a value of an port. For getting a single qubit slice the get item syntax (qbv[i]) may be used.

Example 4 - Quantum Arithmetics

Another example for us to discuss is a model describing a quantum program to calculate the result of the expression a + 2*b for a = 3, b = 5.

qfunc main(output res: qnum){
  a: qnum;
  b: qnum;
  prepare_int<3>(a);
  prepare_int<5>(b);
  res = a + 2*b;
}

Quantum Numeric Types

Quantum variables can be used to operate on numeric values, as encoded in the computational basis. QMOD supports numeric encoding of integers and fixed-point numbers using the type qnum. The actual number of qubits used to represent variables depends on the domain of the variable, which is inferred based on the QMOD code

Quantum Arithmetic Expression

res = a + 2*b;

Expressions over quantum variables can be computed and assigned to another quantum variable. An arithmetic expression statement contains the following parts:

  1. A quantum variable to reference the result of the expression on the left-hand side.
  2. An arithmetic assignment operator to describe whether the calculation is done in place or out of place on the target register. The = sign indicates the calculation is done out of place. The ^= sign indicates the calculation is done inplace, and the result is xor-ed to the target variable. Inplace calculation is only supported for outputs of size 1.
  3. An arithmetic expression to calculate, located on the right hand side.

Split Statement

x -> {x0, x1};

This statement splits one quantum variable into two quantum variables, according to their sizes.

Join Statement

{x0, x1} -> x;

This statement joins two quantum variables into one.

Example 5 - Amplitude Encoding

Another example for us to discuss is a model describing a quantum program to do amplitude encoding.

qfunc main() {
   target: qbit;
   x: qnum;
   allocate<8>(x);
   X(x[3]);
   H(x[6]);
   CY(x[3], x[2]);
   target *= sin(x+5);
}

Amplitude Encoding Operation

target *= sin(x+5);

Expressions over quantum variable can be used for encoding the result of f(x) = <expression> into the amplitude of a result variable, such that for an input quantum variable in the state \(ֿ|x\rangle\), you obtain an output qubit in the state \(\sqrt{1-f(x)^2}ֿ|0\rangle+f(x)ֿ|1\rangle\).

An amplitude encoding statement contains the following parts:

  1. A quantum variable to reference the result of the expression on the left-hand side (should be a single qubit variable).
  2. An arithmetic expression to calculate, located on the right hand side (should include only a single fixed variable in the range of 0 to 1).