Skip to content

Defining and Calling Simple Functions

Use classiq to define a QMOD function by writing a Python function decorated with the @QFunc decorator:

from classiq import QFunc, QParam, QVar, H, PHASE


@QFunc
def foo(n: QParam[int], qv: QVar[1]) -> None:
    H(target=qv)
    PHASE(theta=n * pi, target=qv)

The @QFunc decorator relies on Python's type-hint mechanism to create a corresponding QMOD function definition from the specified Python function.

Use the QParam type-hint to specify a classical parameter. Note it symbolically represents a classical parameter of a quantum function, and cannot be used in an arbitrary Python expression (but only as a parameter to other QParam arguments of QFunc objects). QParam inherits from the sympy.Symbol class, and thus can be used in a Sympy expression context.

Use the QVar type-hint to specify an inout port of the function (with the number of qubits in the brackets). To specify an input-only or an output-only port, use InputQVar or OutputQVar respectively. The size in qubits can also depend on previously defined classical parameters, for example:

from classiq import QFunc, QParam, QVar


@QFunc
def foo(n: QParam[int], qbv: QVar["n"]) -> None:
    pass

Inside the body of the @QFunc, you can call other @QFuncs, which results in quantum function calls in the body of the resulting QMOD definition. You can also introduce local quantum-variables, by instantiating a QVar object and specifying its QMOD name. You need to allocate the quantum variable using builtin functions allocate or STATE_PREPARATION before you pass it to a function with an input or an inout port. In the next example, the local variable a_var must be allocated prior to applying X on it, since it is passed to an inout port. In contrast, the local variable b_var is passed without allocation, because prepare_state has an output-only port and allocates the variable inside.

from classiq.pyqmod import allocate, QFunc, QVar, create_model, prepare_state, CX


@QFunc
def main() -> None:
    a_var = QVar("a_var")
    allocate(1, a_var)
    X(a_var)
    b_var = QVar("b_var")
    prepare_state(probabilities=[0.25, 0.25, 0.25, 0.25], bound=0.01, out=b_var)

Within a function you can also use Python statements; e.g., a for loop to create multiple calls. (Do not use the n parameter as it is a declarative QParam, not a true Python integer.):

from sympy import pi
from classiq import QFunc, QParam, QVar, H, PHASE, allocate
from classiq import create_model, synthesize, show


@QFunc
def foo(n: QParam[int], qv: QVar[1]) -> None:
    H(target=qv)
    for i in range(4):
        PHASE(theta=(i / n) * pi, target=qv)


@QFunc
def bar(m: QParam[int], qv: QVar[1]) -> None:
    foo(n=m * 2, qv=qv)
    foo(n=m // 2, qv=qv)


@QFunc
def main() -> None:
    qv = QVar("qv")
    allocate(1, qv)
    bar(m=2, qv=qv)


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

This results in the following circuit: circuit_with_for.png

QVar Slicing

When creating a model, you can use Python slicing on QVar objects to treat them as bit-vectors and connect to a port that accepts a smaller number of qubits:

from sympy import pi
from classiq import QFunc, QParam, QVar, H, PHASE, allocate
from classiq import create_model, synthesize, show


@QFunc
def foo(n: QParam[int], qv: QVar[1]) -> None:
    H(target=qv)
    for i in range(4):
        PHASE(theta=(i / n) * pi, target=qv)


@QFunc
def bar(m: QParam[int], qbv: QVar[2]) -> None:
    foo(n=m * 2, qv=qbv[0])
    foo(n=m // 2, qv=qbv[1])


@QFunc
def main() -> None:
    qbv = QVar("qbv")
    allocate(2, qbv)
    bar(m=2, qbv=qbv)


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

This results in the following circuit: slicing.png

Note that the QVar slice also accepts QParam objects and expressions involving them.