Skip to content

Qmod Workshop - Part 1: Introduction

View on GitHub

The Classiq platform features a high-level quantum modeling language called Qmod. Qmod is compiled into concrete gate-level implementation using a powerful synthesis engine that optimizes and adapts the implementation to different target hardware/simulation environments.

This workshop demonstrates how to write quantum models using Python embedding of Qmod, available as part of the Classiq Python SDK. You will learn basic concepts in the Qmod language such as functions, operators, quantum variables, and quantum types. You will develop useful building blocks and small algorithms.

The Qmod language reference covers these concepts more systematically and includes more examples.

This workshop consists of step-by-step exercises. It is structured as follows:

  • Part 1: Language Fundamentals - Exercises 1-5

  • Part 2: Higher-Level Concepts - Exercises 6-10

  • Part 3: Execution Flows - Exercises 11-12

The introduction and Part 1 are included in this notebook. Parts 2 and 3 are each in their own notebook. The solutions to the exercises are at the bottom of each notebook.

Preparations

Make sure you have a Python version of 3.8 through 3.12 installed.

Install the Classiq Python SDK using the instructions in Getting Started - Classiq.

Python Qmod Exercises - General Instructions

To synthesize and execute your Qmod code: 1. Make sure you define a main function that calls the functions you create. 2. Construct a representation of your model using create_model by running qmod = create_model(main). 3. Synthesize the model (using qprog = synthesize(qmod)) to obtain the implementation; i.e., a quantum program. 4. Visualize the quantum program (using show(qprog)) or execute it (using execute(qprog). See: Execution - Classiq ). You can also execute it with the IDE after visualizing the circuit.

Preliminary Exercise: From Model to Execution

The following model defines a function that applies X and H gates on a single qubit and subsequently calls it:

from classiq import *


# Define a quantum function using the @qfunc decorator
@qfunc
def foo(q: QBit) -> None:
    X(target=q)
    H(target=q)


# Define a main function
@qfunc
def main(res: Output[QBit]) -> None:
    allocate(1, res)
    foo(q=res)

Create a model from it, then synthesize, visualize, and execute it.

Use the General Instructions above to do so.

from classiq import *

# Your code here:

In Qmod, QBit is the simplest quantum type. In this example, q is a quantum variable of type QBit. Quantum variables abstract away the mapping of quantum objects to qubits in the actual circuit.

See quantum variables.

You will encounter additional quantum types during the workshop.

Part 1: Language Fundamentals

Follow exercises 1 through 5 for the first session of the workshop.

Exercise 1 - Bell Pair

Create a function that takes two single-qubit (QBit) quantum arguments and prepares the bell state on them (Bell state) by applying H on one variable and then using it as the control of a CX function with the second variable as the target. Create a main function that uses this function and has two single-qubit outputs, initialize them to the |0> state (using the allocate function), and apply your function to them.

See functions.

from classiq import *

# Your code here:

qmod = create_model(main)
qprog = synthesize(qmod)
show(qprog)
Opening: https://platform.classiq.io/circuit/b38a2903-cc4a-48a4-94d1-bc5e54c8ff7c?version=0.41.0.dev39%2B79c8fd0855

Use the qubit array subscript (the syntax - variable [ index-expression ]) to change the function from subsection 1 to receive a single quantum variable: a qubit array (QArray) of size 2. Change your main function to declare a single output (also an array of size 2).

from classiq import *

# Your code here:

qmod = create_model(main)
qprog = synthesize(qmod)
show(qprog)
Opening: https://platform.classiq.io/circuit/6e199e06-7b42-4eea-be69-e46486d4329b?version=0.41.0.dev39%2B79c8fd0855

Exercise 2 - Repeat

Use the Qmod repeat statement to create your own Hadamard transform function (and call it my_hadamard_transform). The Hadamard transform function takes a qubit array of an unspecified size as an argument and applies H to each of its qubits.

See classical repeat.

Set your main function to have a quantum array output of unspecified size. Allocate 10 qubits and then apply your Hadamard transform function.

from classiq import *

# Your code here:

qmod = create_model(main)
qprog = synthesize(qmod)
show(qprog)
Opening: https://platform.classiq.io/circuit/ed23ad8c-19e5-40a6-944d-836360d0b07d?version=0.41.0.dev39%2B79c8fd0855

Note: Quantum variable capture: The repeat statement invokes a statement block multiple times. The statement block is specified using a Python callable, typically a lambda expression. Inside the block you can refer to variables declared in the outer function scope. This concept is called quantum variable capture, and is equivalent to [capture](https://en.wikipedia.org/wiki/Closure_(computer_programming) in classical languages.

See capturing context variables and parameters.

Exercise 3 - Power

Raising a quantum operation to an integer power appears in many known algorithms; for example, in Grover search and Quantum Phase Estimation. In the general case the implementation involves repeating the same circuit multiple times.

Sometimes, however, the implementation of the power operation can be simplified, thereby saving computational resources. A simple example is the operation of rotating a single qubit about the X, Y, or Z axis. In this case the rotation gate can be used once with the angle multiplied by the exponent. A similar example is the function unitary - an operation expressed as an explicit unitary matrix (i.e., all n*n matrix terms are given). Raising the operation can be done by raising the matrix to that power via classical computation.

See power operator.

Use the following code to define the value of a Qmod constant named unitary_matrix as a 4x4 (real) unitary:

from typing import List

import numpy as np

from classiq import *

rng = np.random.default_rng(seed=0)
random_matrix = rng.random((4, 4))
qr_unitary, _ = np.linalg.qr(random_matrix)

unitary_matrix = QConstant("unitary_matrix", List[List[float]], qr_unitary.tolist())
  1. Create a model that applies unitary_matrix on a 2-qubit variable three times (e.g. using repeat).
  2. Create another model that applies unitary_matrix raised to the power of 3 on a 2-qubit variable.
  3. Compare the gate count via the Classiq IDE in both cases.
from classiq import *

# Your code here:

qmod = create_model(main)
qprog = synthesize(qmod)
show(qprog)
Opening: https://platform.classiq.io/circuit/fc6fba17-cf87-4902-ac17-f7354b58032c?version=0.41.0.dev39%2B79c8fd0855

Exercise 4 - User-defined Operators

Create a function that applies a given single-qubit operation to all qubits in its quantum argument (call your function 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: 1. Your function declares a parameter of type qubit array and a parameter of a function type with a single qubit parameter. 2. The body applies the operand to all qubits in the argument.

Now, re-implement my_hadamard_transform from Exercise 2 using this function instead of repeat. Use the same main function from Exercise 2.

from classiq import *

# Your code here:

qmod = create_model(main)
qprog = synthesize(qmod)
show(qprog)
Opening: https://platform.classiq.io/circuit/70e0f2d0-ca10-4850-867d-9d48223008e3?version=0.41.0.dev39%2B79c8fd0855

Exercise 5 - Quantum Conditionals

Exercise 5a - Control Operator

Use the built-in 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.

from classiq import *

# Your code here:

qmod = create_model(main)
qprog = synthesize(qmod)
show(qprog)
Opening: https://platform.classiq.io/circuit/c67d0e63-48c8-4ea1-b200-1206f783076f?version=0.41.0.dev39%2B79c8fd0855

Exercise 5b - Control Operator with Quantum Expressions

The control operator is the conditional application of some operation, with the condition being that all control qubits are in the state |1>. 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.

  1. Declare a QNum output argument using Output[QNum] and name it x.
  2. Use numeric assignment (the |= operator) to initialize it to 9.
  3. Execute the circuit and observe the results.
  4. Declare another output argument of type QBit and perform a control such that if x is 9, the qubit is flipped. Execute the circuit and observe the results. Repeat for a different condition.
from classiq import *

# Your code here:

qmod = create_model(main)
qprog = synthesize(qmod)
show(qprog)
Opening: https://platform.classiq.io/circuit/92c2b325-e166-4042-8b30-0dfd1bab3b86?version=0.41.0.dev39%2B79c8fd0855

Solutions

Exercise 1

# Solution for Exercise 1 part 1:


from classiq import *


@qfunc
def bell(q0: QBit, q1: QBit) -> None:
    H(q0)
    CX(q0, q1)


@qfunc
def main(qubit0: Output[QBit], qubit1: Output[QBit]) -> None:
    allocate(1, qubit0)
    allocate(1, qubit1)
    bell(qubit0, qubit1)


model = create_model(main)
qprog = synthesize(model)
show(qprog)
# Solution for Exercise 1 part 2:


from classiq import *


@qfunc
def bell(q: QArray[QBit, 2]) -> None:
    H(q[0])
    CX(q[0], q[1])


@qfunc
def main(q: Output[QArray[QBit, 2]]) -> None:
    allocate(2, q)
    bell(q)


model = create_model(main)
qprog = synthesize(model)
show(qprog)

Exercise 2

# Solution for Exercise 2:


from classiq import *


@qfunc
def my_hadamard_transform(q: QArray[QBit]) -> None:
    repeat(q.len, lambda i: H(q[i]))


@qfunc
def main(q: Output[QArray[QBit]]) -> None:
    allocate(10, q)
    my_hadamard_transform(q)


model = create_model(main)
qprog = synthesize(model)
show(qprog)

Exercise 3

# Solution to Exercise 3:


from typing import List

import numpy as np

from classiq import *

rng = np.random.default_rng(seed=0)
random_matrix = rng.random((4, 4))
qr_unitary, _ = np.linalg.qr(random_matrix)

unitary_matrix = QConstant("unitary_matrix", List[List[float]], qr_unitary.tolist())


@qfunc
def main(q: Output[QArray[QBit]]) -> None:
    allocate(2, q)
    power(3, lambda: unitary(unitary_matrix, q))


model = create_model(main)
qprog = synthesize(model)
show(qprog)
Opening: https://platform.classiq.io/circuit/671b9800-a1e8-4007-9561-f12c9125cd8e?version=0.41.0.dev39%2B79c8fd0855

Exercise 4

# Solution for Exercise 4:


from classiq import *


@qfunc
def my_apply_to_all(operand: QCallable[QBit], q: QArray[QBit]) -> None:
    repeat(q.len, lambda i: operand(q[i]))


@qfunc
def my_hadamard_transform(q: QArray[QBit]) -> None:
    my_apply_to_all(lambda t: H(t), q)


@qfunc
def main(q: Output[QArray[QBit]]) -> None:
    allocate(10, q)
    my_hadamard_transform(q)


model = create_model(main)
qprog = synthesize(model)
show(qprog)

Exercise 5

# Solution for Exercise 5a:


from classiq import *
from classiq.qmod.symbolic import pi


@qfunc
def my_controlled_ry(control_bit: QBit, target: QBit) -> None:
    control(control_bit, lambda: RY(pi / 2, target))


@qfunc
def main(control_bit: Output[QBit], target: Output[QBit]) -> None:
    allocate(1, control_bit)
    allocate(1, target)
    my_controlled_ry(control_bit, target)


model = create_model(main)
qprog = synthesize(model)
show(qprog)
Opening: https://platform.classiq.io/circuit/d46da5b5-c12b-4675-be22-9410927edf6c?version=0.41.0.dev39%2B79c8fd0855
# Solution for Exercise 5b:


from classiq import *


@qfunc
def main(x: Output[QNum], target: Output[QBit]) -> None:
    x |= 9
    allocate(1, target)
    control(x == 9, lambda: X(target))


model = create_model(main)
qprog = synthesize(model)
show(qprog)
Opening: https://platform.classiq.io/circuit/974241f9-78b1-4490-b0b4-6752735c0642?version=0.41.0.dev39%2B79c8fd0855