View on GitHub
Open this notebook in GitHub to run it yourself
foo takes the quantum parameter q of type QBit and applies X gate to it, followed by H gate.
Function main declares a single Output parameter q of type QBit. It first allocates a qubit to q in the state , then calls foo to operate on it.
A quantum program qprog is created based on function main, so that it can later be executed.
What results do we expect when executing this quantum program?
-
By calling
allocate,qis initialized in the default state . -
Then,
foois called: -
It applies
X(NOT gate), changingq’s state to . -
Then it applies
H(Hadamard gate), resulting in the superposition .
q is sampled. We can run the following code and make sure that the states and are sampled roughly equally:
Output:
Qmod Fundamentals
The simple model above demonstrates several features that are essential in every Qmod code:The @qfunc Decorator
Qmod is a quantum programming language embedded in Python.
The decorator @qfunc designates a quantum function, so that it can be processed by the Qmod tool chain.
The Python function is executed at a later point to construct the Qmod representation.
This decorator is used for every Qmod function definition.
Function main
A complete Qmod model, that is, a description that can be synthesized and executed, must define a function called main.
Function main is the quantum entry point - it specifies the inputs and outputs of the quantum program, that is, its interface with the external classical execution logic.
Similar to conventional programming languages, function main, can call other functions.
Output variables that are declared in main definition are the ones to be measured when the program is executed.
Working with Quantum Variables
Quantum objects are representations of data (boolean values, numbers, arrays of numbers and so on), that are stored in specific qubits. In Qmod, quantum objects are handled and manipulated using quantum variables. Quantum variables must be declared and initialized explicitly. The model above demonstrates two important kinds of declaration:- Function
foodeclares parameterqthus:q: QBit.
foo expects a pre-existing quantum object.
- Function
maindeclares parameterqthus:q: Output[QBit]. In this case,qis an output-only parameter - it is initialized inside the scope ofmain.
- Using
allocateto initialize the variable to a new object, with all its qubits in the default state. - Using numeric assignment to initialize the variable to an object representing the result of a quantum expression.
- Using functions with output parameters (for example, state preparation).
main must be declared as Output, as main is the entry point of the model (Think about it: where could a variable be initialized before main was called?).
Other functions can declare parameters with or without the Output modifier.
Exercise #0
Rewrite the above model, so thatq is initilized inside foo.
A solution is provided in the end of the notebook.
Hint: it only requires to move one line of code and add the Output modifier in the correct place.
Why does foo need the Output modifier?
In the original model, foo declares q: QBit — a regular parameter.
This tells Qmod that q must already be an initialized quantum object when foo is called; the caller is responsible for creating it.
When we move allocate inside foo, the responsibility shifts: foo is now the one creating q. At the moment foo(q) is called from main, q has not yet been initialized — it is just an unallocated reference.
Passing an uninitialized variable to a regular parameter is not allowed, because Qmod expects a live quantum object there.
The Output modifier changes this contract: q: Output[QBit] tells Qmod that q enters the function uninitialized, and that the function itself is responsible for initializing it (here, via allocate).
This lines up with the call site in main, where q — itself an Output parameter — is still unallocated when foo(q) is invoked.
In short: use Output whenever a function is responsible for creating a quantum variable, rather than receiving one that already exists.
Output:
Quantum Types, Statements and Opertions
Now that we have grasped the principles that are essential for any Qmod code, we can start building up our expressive toolkit, letting us create increasingly sophisticated models. The following exercises introduce some of the most useful variable types, statements and operations that Qmod supports.Exercise #1
- Quantum Arrays
QBit varible type (which is simply a single qubit), it is a good timing to introduce the quantum array type QArray.
In this exercise, we will prepare the famous Bell state into a 2-qubit Quantum array.
Recall that represents the state .
Instructions:
- Declare a quantum variable
qarrof typeQArray, and initilize it by allocating to it 2 qubits. Don’t forget to use theOutputmodifier. - Apply a Hadamard gate on the first qubit of
qarr.
qarr is qarr[0].
- Apply
CX(controlled-NOT gate), with thecontrolparameter beingqarr[0]and thetargetparameter beingqarr[1].
Output:
Exercise #2
- The Repeat Statement
repeat statement to create your own Hadamard Transform - a function that takes a QArray of an unspecified size and applies H to each of its qubits.
Instructions:
- Define a function
my_hadamard_transform:
-
It should have a single
QArrayargumentq. -
Use
repeatto applyHon each ofq’s qubits. -
Note that the
iterationblock of therepeatstatement must use the Pythonlambdasyntax (seerepeatdocumentation).
- define a
mainfunction that initializes aQArrayof length 10, and then passes it tomy_hadamard_transform.
show to let you inspect the resulting circuit - make sure that is applies H to each of q’s qubits.
Output:
Output:
Exercise #3
- Power
unitary_matrix as a 4x4 (real) unitary:
- Create a model that applies
unitary_matrixon a 2-qubit variable three times (e.g. usingrepeat). - Create another model that applies
unitary_matrixraised to the power of 3 on a 2-qubit variable. - Compare the gate count via the Classiq IDE in both cases.
Output:
Output:
Exercise 4
- User-defined Operators
my_apply_to_all).
Such a function is also called an operator; i.e., a function that takes another function as an argument (its operand).
See operators.
Follow these guidelines:
- Your function declares a parameter of type qubit array and a parameter of a function type with a single qubit parameter.
- The body applies the operand to all qubits in the argument (you may use
repeator evenforinsidemy_apply_to_allfor this).
my_hadamard_transform from Exercise 2 so that its body calls my_apply_to_all rather than calling repeat directly.
The goal is that my_hadamard_transform expresses what to do (apply H to all qubits), while my_apply_to_all encapsulates how to iterate.
Use the same main function from Exercise
Output:
Output:
Exercise 5
- Quantum Conditionals
Exercise 5a
- Control Operator
control operator to create a function that receives two single qubit variables and uses one of them to control an RY gate with a pi/2 angle acting on the other variable (without using the CRY function).
See control.
Output:
Output:
Exercise 5b
- Control Operator with Quantum Expressions
control operator is the conditional application of some operation, with the condition being that all control qubits are in the state .
This notion is generalized in Qmod to other control states, where the condition is specified as a comparison between a quantum numeric variable and a numeric value, similar to a classical if statement.
Quantum numeric variables are declared with class QNum.
See numeric types.
- Declare a
QNumoutput argument usingOutput[QNum]and name itx. - Use numeric assignment (the
|=operator) to initialize it to9. - Execute the circuit and observe the results.
- Declare another output argument of type
QBitand perform acontrolsuch that ifxis 9, the qubit is flipped.
Output:
Output:
Exercise 6
- Phase statement
phase statement allows the user to perform the mapping
given a function .
This operation is extremely valuable to algorithms such as Grover’s and QAOA.
See phase.
Exercise 6a
- Phase with arithmetic condition
- Declare a
QNumoutput argument usingOutput[QNum]and name itx. - Allocate 4 qubits to
x. - Perform a hadamard transform in
x. - Using
phase, create a phase according to the rule . - Apply the hadamard transform in
xagain. - Execute the quantum program and analyze the outputs.
Solutions
Solution
- Excercise #0
Output:
Key takeaway: TheOutputmodifier defines the beginning of a quantum variable’s lifecycle. A parameter declared asOutput[T]enters the function uninitialized; the function must allocate it before use. A quantum variable not declared asOutputrequires the caller to pass an already-initialized variable. Movingallocateinto a function therefore requires addingOutputto that parameter.
Solution
- Exercise #1
Output:
Key takeaway:QArrayis the quantum equivalent of a classical array. Individual qubits are accessed by index (qarr[0],qarr[1], …), and any operation can be applied to a specific element. Entanglement — such as the Bell state — arises from combining single-qubit gates (likeH) with two-qubit gates (likeCX).
Solution
- Exercise #2
Output:
Output:
Key takeaway: Therepeatstatement is the standard way to apply an operation to every qubit in aQArray. The iteration body must be a Pythonlambdathat receives the loop index. Because the array size can be left unspecified (QArray[QBit]), functions built withrepeatwork on arrays of any length without modification.
Solution
- Exercise #3
Output:
Key takeaway: Usingpower(n, ...)is more efficient than repeating an operationntimes when the Classiq engine can exploit algebraic structure. For example, raising the unitary matrix to the power classically rather than replicating the circuit gates. This optimization is critical in algorithms such as Grover search and Quantum Phase Estimation, where operations must be applied many times.
Solution
- Exercise #4
Output:
Output:
Alternative Solution
- Exercise #4
Output:
Key takeaway: User-defined operators (functions that accept other functions as arguments) separate what to do from how to iterate.my_apply_to_allencapsulates the looping logic; the caller expresses the intent: applyHto every qubit. Bothrepeatand a classicalforloop are valid iteration mechanisms inside the operator body.
Solution
- Exercise #5
Solution
- Exercise #5a
Output:
Output:
Key takeaway: The control operator lets you condition any quantum operation on a control qubit being in state , without needing a dedicated controlled gate. This is how Qmod builds controlled versions of arbitrary operations.
Solution
- Exercise #5b
Key takeaway: Quantumcontrolgeneralizes beyond single-qubit conditions. AQNumvariable can be compared to a classical integer (e.g.,x == 9), and the resulting boolean expression used directly as the control condition. This is the quantum analog of a classicalifstatement: the controlled operation is applied (or not) depending on the value in the quantum register, across all branches of a superposition simultaneously.
Solution
- Exercise #6a
Output:
Key takeaway: Thephasestatement applies a state-dependent phase to each basis state without changing the measurement probabilities ofxin isolation. When conjugated between Hadamard transforms, phase differences cause constructive and destructive interference that shifts probability weight to specific output states.
Solution
- Exercise #6b
Output:
| x | counts | probability | bitstring | |
|---|---|---|---|---|
| 0 | [0, 0, 0, 1] | 1556 | 0.759766 | 1000 |
| 1 | [1, 0, 1, 0] | 46 | 0.022461 | 0101 |
| 2 | [0, 0, 1, 0] | 42 | 0.020508 | 0100 |
| 3 | [0, 1, 0, 1] | 39 | 0.019043 | 1010 |
| 4 | [1, 0, 1, 1] | 39 | 0.019043 | 1101 |
| 5 | [1, 1, 0, 0] | 38 | 0.018555 | 0011 |
| 6 | [1, 1, 1, 0] | 36 | 0.017578 | 0111 |
| 7 | [0, 0, 1, 1] | 33 | 0.016113 | 1100 |
| 8 | [1, 0, 0, 0] | 31 | 0.015137 | 0001 |
| 9 | [0, 1, 0, 0] | 31 | 0.015137 | 0010 |
| 10 | [1, 0, 0, 1] | 29 | 0.014160 | 1001 |
| 11 | [0, 0, 0, 0] | 28 | 0.013672 | 0000 |
| 12 | [0, 1, 1, 0] | 26 | 0.012695 | 0110 |
| 13 | [1, 1, 0, 1] | 26 | 0.012695 | 1011 |
| 14 | [1, 1, 1, 1] | 25 | 0.012207 | 1111 |
| 15 | [0, 1, 1, 1] | 23 | 0.011230 | 1110 |
Key takeaway: Phase operations andcontrolcan be combined to selectively apply a phase to specific computational basis states. Thehadamard_transformutility appliesHto all qubits of an array in a single call, andcontrolwith a fullQArrayas the operand conditions the operation on all control qubits being in state .