Skip to content

Design - Quantum Variables and Functions

In Qmod there are three types of quantum objects which are represented by quantum variables: 1. Qubit (quantum bit)

* Written as `QBit` in the Python SDK
* Written as `qbit` in the native syntax
  1. Qubit Array

    A sequence of qubits, should be thought of as a quantum register with definite number of qubits.

    • Written as QArray in the Python SDK
    • Written as qbit[] in the native syntax
  2. Quantum Number

    A qubit array (quantum register) that represents numbers. The numbers can be positive and negative integers, and it can be fixed point numbers (e.g. \(-5.25\)). The quantum number object has a definite number of qubits (as every qubit array), and it has 2 properties for its numeric representation: if the number is signed or not (only positive or it represents negative numbers as well) and how many fractional digits it has (where is the decimal point).

    • Written as QNum in the Python SDK
    • Written as qnum in the native syntax

As explained in the Design page, every quantum variable needs to be declared and initialized before it is used.

Concrete Example

Let's get started and understand these quantum variables through a concrete example.

Our task is to design a quantum algorithm that flips the most significant bit (MSB) of a quantum number and to verify this.

What is the MSB? In a binary representation, every number can be represented by a bit string (a sequence of 0's and 1's). For example, the number $6$ can be represented by the bit string $110$. How come? In order to understand it we move bit by bit and sum up the multiplication of the bit value times $2$ to the power of the bit position. We start with the right most bit called the least significant bit (LSB) since its position is the 0th position and sum $0\times 2^0 =0$ plus $1\times 2^1=2$ for the middle bit plus $1\times 2^2=4$ for the left most bit - the most significant bit (MSB) since its contribution to the sum is the most significant.

How to approach that? We will have a function called flip_msb that will receive a Qubit Array, and we will flip its MSB. This function will be called from the main function with some Quantum Number, and then we will verify we receive the correct number using a Qubit indicator.

The flip_msb function receives a QArray variable named reg (shortcut for register), and it flips its MSB using the X gate. This gate operates on the qubit at the last position of the Qubit Array reg where the counting starts from \(0\). One can see the property len of the register reg is used as part of the function.

from classiq import QArray, X, qfunc


@qfunc
def flip_msb(reg: QArray):
    X(reg[reg.len - 1])

An important thing to note is that the variable reg is not initialized within the scope of the function flip_msb. This can be seen by the lack of the output declaration in the scope of the function, and it means that reg is initialized before it is called in the function.

Now to the main function.

We have only one variable as an argument for the function which is the indicator if we manage to flip the MSB or not. This is a Qubit variable named indicator.

Within the function itself we declare and initialize a Quantum Number named x with \(4\) qubits. A general initialziation with allocate initializes the Quantum Number to the state \(|0\rangle = |0000\rangle\) (this is true for Qubits and Qubit Arrays as well). The function flip_msb acts on the Quantum Number x in order to flip its MSB and to create the state \(|1000\rangle = |8\rangle\).

NOTE As we don't declare if the Quantum Number is signed or not, or how many fractional digits it has, it is just initialized with no fractional digits and is not signed (see [`allocate_num`](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/quantum-types/) for initializing a Quantum Number with these options).
from classiq import Output, QBit, QNum, allocate


@qfunc
def main(indicator: Output[QBit]):

    x = QNum("x")
    allocate(4, x)
    flip_msb(x)

    indicator |= x == 8

Finally, we initialize the indicator qubit with the numeric assignment of the expression \(x==8\). That is, if indeed we manage to flip the MSB of x and to transform it to the state |8\rangle then the state od the qubit indicator will be equal \(|1\rangle\) otherwise it will equal $|0\rangle (i.e. indicating if we manage to complete the flip_msb operation or not).

`flip_msb` receives a Quantum Number or a Quantum Array? One might notice that the function `flip_msb` is called with a Quantum Number `x` from the `main` function, whilst it is declared with a quantum array named `reg`. This demonstrates that quantum numbers can be cast to quantum arrays if they have the same number of qubits. In `main` we are intrested in the manupulation of an arithmetic quantum object - a quantum number, whilst in the function `flip_msb` we are just interested in treating the quantum number as a qubit array, without its numeric description, and to apply a specific gate on one of its qubits.

That's it! Now we want to check if indeed we managed to flip the MSB. The desired quantum program can be synthesized and viewed:

from classiq import create_model, show, synthesize

quantum_program = synthesize(create_model(main))
show(quantum_program)
Opening: https://platform.classiq.io/circuit/12d860cd-2de0-4ffa-9d81-8a1da9254c7f?version=0.41.0.dev39%2B79c8fd0855

And then the quantum program can be executed to receive the results:

from classiq import execute

job = execute(quantum_program)
results = job.result()[0].value.parsed_counts
print(results)
[{'indicator': 1.0}: 1000]

And indeed we managed to flip the MSB as indicated by the indicator variable.

Summary - Quantum Variables and Functions Guidelines

The following summarizes the main takeaways from the example in terms of quantum variables and functions:

  • There are 3 types of quantum objects in Qmod: a qubit, a qubit array, and a quantum number. Objects can be cast from one to another (as x being cast from quantum number in the main function to qubit array in the flip_msb function)
  • Quantum objects are represented by quantum variables. Each variable needs to be declared and initialized before it is used. The initialization can be done in several ways, here we see two options: with the allocate function (the initialization of x) or with a numeric assignment (the initializaiton of indicator).
  • Quantum variables that are arguments of a function can be declared with the Output modifier and initialized within the scope of a function (like indicator in the main function). Without the Output modifier they must be initialized outside the scope of the function (like reg in the flip_msb) function.
  • Quantum variables can be declared and initialized within a function (like x in the main function)
  • The quantum program is always generated from the main function, even when further quantum functions are used. The execution results of the quantum program are interperted only for the arguments of the main function (like for indicator in the above example)
from classiq import write_qmod

write_qmod(create_model(main), "quantum_variables_and_functions")