Skip to content

Design - Quantum Operations

Quantum computing resembles classical computing in some aspects, and is substantially different in other aspects. One of the key advantages of Qmod is that it captures the core concepts which are uniquely quantum in a simple and natural way. This begins with the quantum objects and variables described in the previous tutorial (see Quantum Variables and Functions) and continues with the meaningful native quantum operations.

Simply put, quantum operations are functions of functions applied on quantum objects that are very common in quantum computing, hence receive a special place in the Qmod language. More accurately, these are built-in statements in the Qmod language. A statement is a building block of a programming language, such that programming languages are composed from statements. For example, if and for loops are common statements in programming languages like in Python.

There are a few quantum operation statements in Qmod, and in the following we focus on arguably the most useful one - control. It applies a specified quantum function conditioned on some state (value) of a given quantum variable. Other quantum operations are invert, power, and within_apply, and one can see all the quantum operations here.

Let's understand it through a concrete example.

Concrete Example

Our task is to first prepare a quantum number x in a superposition of all possible integers between \(0\) to \(15\), i.e. \(|x\rangle = \frac{1}{\sqrt{16}}(|0\rangle + |1\rangle + \dots + |15\rangle)\). Then to prepare another quantum number y that is in the state \(|17\rangle\) only if \(|x\rangle\) is in the state \(|15\rangle\), otherwise the state of y should be \(|0\rangle\). Mathematically, the task is to prepare the following state: \(\begin{equation} |x\rangle|y\rangle = \frac{1}{\sqrt{16}}(|0\rangle|0\rangle+|1\rangle|0\rangle+\dots+|14\rangle|0\rangle+|15\rangle|17\rangle). \end{equation}\)

How to approach this task? We have already seen that we can create a uniform superposition with the hadamard_transform. Then, conditioned on the value of \(|x\rangle\) being \(|15\rangle\), we will prepare the state of \(|y\rangle\) to be \(|17\rangle\) with the inplace_prepare_int function.

We shell start by creating our own function called apply_control that received two quantum numbers x and y that are already initialized, and conditioned on the values of x being \(15\), it prepares the state of y to the integer value \(17\) using the inplace_prepare_int function.

What's the difference between `inplace_prepare_int` and `prepare_int`? The function `prepare_int` initializes a specific quantum variable to a specific integer value. On the other hand, the function `inplace_prepare_int` receives an initialized quantum variable, and assumed its value is $0$, it prepares its state to the desired integer values. Note that for all the `prepare` functions there is the option to use also `inplace_prepare` in case you want to apply the function on an already initialized quantum variable.
from classiq import QNum, control, inplace_prepare_int, qfunc


@qfunc
def apply_control(x: QNum, y: QNum):
    control(ctrl=(x == 15), operand=lambda: inplace_prepare_int(17, y))

In the Python syntax, control is a Python function with two arguments. The ctrl argument is the condition for which the operand will be applied. The operand argument is the specific operation/function we want to apply given the condition is satisfied. The way to pass a function as an argument of another function is with the lambda: keyword. So anytime you pass a function as argument in the Python SDK use the prefix lambda:.

More on `lambda:` The `lambda:` keyword is used to define inline functions.

In the Native syntax, control is embedded within the Qmod language such that the condition is specified with the () parenthesis, and the operand that needs to be applied is specified within the scope of the control, i.e. within the {} parenthesis.

Now for the main function. At the end, we want to evaluate the execution results of both x and y so both of them will be arguments of the main function. x is initialized with \(4\) qubits since it needs to be in a superposition of \(16 (=2^4)\) states, and y is initialized with \(5\) qubits since this is the minimal number of qubits that is needed for representing the number \(17\) (\(ceiling(log_2(17))=5\)).

from classiq import Output, allocate, hadamard_transform


@qfunc
def main(x: Output[QNum], y: Output[QNum]):
    allocate(4, x)
    allocate(5, y)

    hadamard_transform(x)
    apply_control(x, y)

After the initialization, hadamard_transform is applied on x in order to prepare the initial superposition, and then the apply_control function we defined earlier is applied on both x and y.

That's it! The desired quantum program can be now 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/ce544494-280d-4c50-b991-66b86a27e617?version=0.41.0.dev39%2B79c8fd0855

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

from classiq import execute

job = execute(quantum_program)
results = job.result()[0].value.parsed_counts
print(results)
[{'x': 13.0, 'y': 0.0}: 80, {'x': 14.0, 'y': 0.0}: 70, {'x': 8.0, 'y': 0.0}: 70, {'x': 15.0, 'y': 17.0}: 67, {'x': 5.0, 'y': 0.0}: 66, {'x': 12.0, 'y': 0.0}: 65, {'x': 4.0, 'y': 0.0}: 64, {'x': 6.0, 'y': 0.0}: 63, {'x': 1.0, 'y': 0.0}: 61, {'x': 7.0, 'y': 0.0}: 59, {'x': 3.0, 'y': 0.0}: 59, {'x': 10.0, 'y': 0.0}: 59, {'x': 0.0, 'y': 0.0}: 57, {'x': 2.0, 'y': 0.0}: 56, {'x': 11.0, 'y': 0.0}: 54, {'x': 9.0, 'y': 0.0}: 50]

We can see that we receive \(16\) possible values for x and y, and in all the pairs of values \(y=0\) besides when \(x=15\) as desired!

Summary - Quantum Operations

The following summarizes the main takeaways from the example in terms of quantum operations:

  • Quantum operations receive both quantum objects and quantum functions as inputs, and they apply the quantum functions on the quantum objects according to the nature of the quantum operation. In the above example, the control operation applies the inplace_prepare_int function on the quantum variable y conditioned on the value of the quantum variable x being \(15\).

  • In the Native syntax, the quantum operations are statements of the Qmod language and have a special syntax. In the above example, the syntax for control is control(){;} where the condition is given in the parentheses and the operation/function that is applied is within the curly brackets.

  • In the Python SDK, the quantum operations are written just like any other Python function. The arguments of the quantum operation that are functions by themselves must be passed with the lambda: keyword. In the above example, the operand argument of the control function is a function by itself (inplace_prepare_int), hence it is prefixed with lambda:.

  • Other quantum operations are: power - raising a unitary to some power, invert - applying the inverse of a unitary, within_apply - applying two unitaries \(U\) and \(V\) in the following way: \(UVU^\dagger\). See more detailed description of all the quantum operators here.

from classiq import write_qmod

write_qmod(create_model(main), "quantum_operations")