Skip to main content

View on GitHub

Open this notebook in GitHub to run it yourself
Welcome to the Qmod tutorial! Qmod is Classiq’s quantum programming language, embedded in Python (Qmod also has standalone syntax which we will not cover here). Across two notebooks you will build up the core toolkit for designing quantum algorithms at a high level of abstraction, letting the Classiq synthesis engine handle the gate-level implementation for you. Part I (this notebook) covers the language foundations. After a short warmup, six sections each pair a concept with a hands-on exercise:
  1. Quantum Variables and Quantum Types
  2. Output Parameters and State-Preparation Functions
  3. Classical Parameters
  4. Classical Control Flow
  5. Quantum Assignment and Arithmetic
  6. Control Statements
Each section has a short concept explanation followed by an exercise. Complete the code where marked with # TODO, then synthesize and run it. All solutions are collected at the end of the notebook - try each exercise before peeking. Part II then builds on these foundations with six more concepts - the statements and operators used to express real quantum algorithms.
Note: If you are working in your own SDK environment, make sure Classiq is installed (pip install -U classiq) and that you have authenticated once by running authenticate() in a Python session. See the registration and installation guide.

Warmup

  • Quantum Functions and main
The @qfunc decorator designates a quantum function - a function that applies operations to quantum objects. The entry point of a quantum program, that is, the quantum function synthesized into an executable, must be named main. The quantum parameters of main are designated with the Output-modifier, and they are measured when the program is executed. Function main can call other quantum functions. A typical SDK flow is:
  • Compile the high-level model into a gate-level quantum program with synthesize().
  • Visualize the quantum program with show().
  • Execute the program and sample the state prepared in its output variables with sample().
Exercise: No coding required. The cell below prepares and samples a Bell state, one of the most fundamental entangled states, 12(00+11)\frac{1}{\sqrt{2}}(|00\rangle + |11\rangle). Just run it and observe the synthesis -> visualization -> execution flow.
Expected result: roughly 50% of the samples are the bit vector 00 and 50% are 11 - the two qubits are perfectly correlated.

  1. Quantum Variables and Quantum Types
A quantum variable is a reference to a quantum object - the quantum state stored on one or more specific qubits. Its type tells the compiler how to interpret and operate on those qubits:
  • QBit - a single qubit.
  • QNum - a number, supporting arithmetic and comparisons.
You can specify its size, signedness, and fractional digits (e.g. QNum[3, SIGNED, 0]).
  • QArray - an indexable array (e.g. my_qarr[i]) of elements of any quantum type; QArray[QBit] is the most common.
  • QStruct - a struct of named fields (e.g. my_qstruct.f); we’ll return to structs later.
Size and length are optional - leave them unspecified (e.g. QNum, QArray[QBit]) and the compiler infers them when the variable is initialized. Function arguments are automatically cast between quantum types of the same size - e.g. between QArray[QBit] and QNum - so the same function can operate on either. Exercise A: Based on the Bell-state example above, complete create_ghz_state to prepare a 3-qubit GHZ state 12(000+111)\frac{1}{\sqrt{2}}(|000\rangle + |111\rangle), then synthesize, show, and sample it.
Expected result: roughly 50% 000 and 50% 111.
Exercise B: Reuse the same create_ghz_state from Exercise A, but this time call it on a signed quantum number x (QNum[3, SIGNED, 0]) instead of a qubit array. Synthesize and sample it. What values do you expect to measure?
Tip: Thanks to automatic casting between QArray[QBit] and QNum, you don’t need to change create_ghz_state at all. The bit string 111 is interpreted as 1-1 for a signed integer (in two’s-complement representation, the MSB has weight 2n1-2^{n-1}, so 111 =4+2+1=1= -4 + 2 + 1 = -1).
Expected result: roughly 50% for the value 0 and 50% for the value -1.

  1. Output Parameters and State-Preparation Functions
By default, a quantum parameter refers to an object passed into the function and back out to the caller - it is already initialized when entering the function. Declaring a parameter Output makes it output-only: it starts uninitialized and must be initialized inside the function. A variable is initialized by:
  • allocating fresh qubits in the 0|0\rangle state (using allocate())
  • assigning an object in a state specified as an expression (using |=)
  • calling a state-preparation function (a function with an Output parameter)
See also Quantum variables.
Note: All of main’s parameters must be Output - as the entry point, there is nowhere they could have been initialized beforehand.
Classiq provides built-in state-preparation functions. Two closely related ones:
  • prepare_state(probabilities, bound, out) - allocates a fresh object and prepares it in a state whose measurement distribution matches probabilities (out-of-place).
  • inplace_prepare_state(probabilities, bound, target) - applies the same preparation to an already-allocated object target (in-place), assumed to be in the 0|0\rangle state.
(In both cases, bound determines the maximum allowed error.) Exercise A: Load the same distribution dist two ways, to contrast the two functions:
  1. Use prepare_state to load dist into a fresh variable a.
  2. Use inplace_prepare_state to load dist into an already-allocated variable b.
dist has 8 entries, so each variable holds 3 qubits.
Expected result: The probability of measuring 000 on both a and b is the highest - 0.3×0.30.3 \times 0.3, and the probability of measuring 111 on both should be the lowest - 0.02×0.020.02 \times 0.02.
Exercise B: The cell below fails - the variable q is never initialized. Run it to see the error, then fix it such that prep is responsible for initializing q.

  1. Classical Parameters
Qmod functions can take classical parameters, in addition to quantum parameters. You already used them in Exercise A of Section 2 - the parameters of prepare_state(). Classical parameters are declared with Qmod classical types
  • CInt, CReal, CArray, etc.
Classical parameters can be used in Qmod expressions and statements. The size of quantum variables (e.g. my_qnum.size) and the length of arrays (e.g. my_qarr.len) are also classical expressions in Qmod. When main has a classical parameter, its value is left symbolic throughout the compilation process, and is only determined upon execution. This is useful for hybrid quantum-classical algorithms.
Note: Qmod classical types (CInt, CReal, etc.) remain symbolic through compilation. Parameters declared with plain Python types (int, float) also work, and can be used in arbitrary Python expressions. But they can’t serve as execution parameters, and they might hurt compilation performance. See also generative descriptions.
Exercise A: Define the function rotate to rotate qubit ind of qarr about the X axis by turns, given as a fraction of the full 2π2\pi rotation. (Here the classical parameter turns is used inside an expression, not passed directly.)
Expected result: Note how the parameter turns under rotate remains symbolic in the quantum program visualization.
Exercise B: Rewrite function main from Exercise A such that it takes two execution parameters, alpha and beta, for the rotation of the two qubits, instead of hard-coding them inside main.

  1. Classical Control Flow
Much like other programming languages, Qmod supports loops and conditional statements: repeat, foreach, and if_. To specify the body of the loop, or the branches of the conditional, you typically use lambda expressions. In repeat and foreach, the iteration variable is the lambda parameter. power is a unique form of a loop statement with no iteration variable, and stronger optimizations for some special cases. For example, the following uses repeat to apply a Hadamard gate to each qubit in a qubit array (the Hadamard transform):
Qmod also provides this as the built-in function hadamard_transform, which we’ll use in later sections.
Note: Python for and if statements can be used inside Qmod functions. But these Python statements cannot use Qmod (symbolic) variables. They generate a flat unrolled description, which can impact compilation performance.
Exercise: Rewrite rotate from Section 3 so that it takes an array of turn-fractions and rotates the ith qubit of qarr by the ith value (you can assume that the arrays are of the same length).

  1. Quantum Assignment and Arithmetic
Qmod lets you write ordinary arithmetic expressions over QNum variables; the compiler synthesizes them into reversible quantum circuits automatically. There are three flavors of assignment:
  • out-of-place: res |= expr allocates res and sets it to the result of expr.
Used to initialize a fresh variable.
  • in-place-add: x += expr adds expr into an already-initialized x.
  • in-place-xor: x ^= expr XORs expr onto an already-initialized x.
When applied to variables in superposition, the arithmetic is carried out coherently over every basis state. Exercise (out-of-place): Put two 2-qubit unsigned integers x and y into uniform superposition, then use |= to compute both their sum total = x + y and their product prod = x * y. Synthesize and sample.
Expected result: x and y are each uniform over 0-3; each row shows total = x + y (0-6) and prod = x * y (0-9) for that pair of values.
Exercise (in-place): Initialize acc to the fixed value 4, then add a superposed y into it in place with +=. Notice how acc becomes correlated with y.
Expected result: acc = 4 + y, i.e. 4, 5, 6, and 7 - each paired with the y that produced it.

  1. Control Statements
The control operator conditionally applies a quantum operation. In its basic form the condition is that all control qubits are 1|1\rangle, but Qmod generalizes this: the condition can be any Boolean expression over quantum numeric variables - comparisons (==, <, >=, …) and logical operators (&, |). It resembles a classical if statement, except the operation is applied in superposition over the states that satisfy it. For example, the following applies H to q only when the control qubit flag is 1|1\rangle:
Tip: control also accepts an optional else_block= that applies when the condition is False. And the condition can involve real-valued QNums too (e.g. x < 0.5), not just integers.
Exercise: Put a 3-qubit number x into uniform superposition, then flip the target qubit to 1|1\rangle only for the basis state where x equals 5.
Expected result: x is uniform over 0-7; target is 1 only in the row where x == 5, and 0 everywhere else.

Solutions

Try each exercise before checking the solution below.

Solution 1

  • Quantum Variables and Quantum Types
**Exercise A
  • GHZ on a qubit array**
**Exercise B
  • GHZ on a signed integer**

Solution 2

  • Output Parameters and State-Preparation Functions
Exercise A - prepare_state vs. inplace_prepare_state
Exercise B - fixing the uninitialized variable

Solution 3

  • Classical Parameters
Exercise A - a classical expression in a rotation
Exercise B - execution parameters

Solution 4

  • Classical Control Flow

Solution 5

  • Quantum Assignment and Arithmetic
Out-of-place assignment
In-place assignment

Solution 6

  • Control Statements