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, QBit, H, PHASE
@QFunc
def foo(n: QParam[int], qv: QBit) -> None:
H(qv)
PHASE(n * pi, 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 QBit
quantum type-hint to specify an inout
port of the function of a single qubit.
To specify an input-only or an output-only port, use Input[QBit]
or Output[QBit]
respectively.
Alternatively, you can use QArray[QBit]
to declare an array of qubits with an unspecified size
(for more details on advanced quantum types, see Quantum Types and Expressions).
The size of the array can also depend on
previously defined classical parameters, for example:
from classiq import QFunc, QParam, QArray, QBit
@QFunc
def foo(n: QParam[int], qbv: QArray[QBit, "n"]) -> None:
pass
Inside the body of the @QFunc
, you can call other @QFunc
s, which results in
quantum function calls in the body of the resulting QMOD definition. You can also
introduce local quantum-variables, by using the declare_qvar
function, specifying
the variable's QMOD name and type (not specifying a type will default to QArray[QBit]
).
You need to allocate the quantum variable using builtin functions
allocate
or prepare_state
before you pass it to a function with an input
or an inout port. Many more builtin QMOD functions are available (see the full list
of functions and their arguments under classiq/qmod/builtins.py
).
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 import (
allocate,
QFunc,
QBit,
QArray,
create_model,
prepare_state,
CX,
)
@QFunc
def main() -> None:
a_var = QBit("a_var")
allocate(1, a_var)
X(a_var)
b_var = QArray("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, QBit, H, PHASE, allocate
from classiq import create_model, synthesize, show
@QFunc
def foo(n: QParam[int], qv: QBit) -> None:
H(target=qv)
for i in range(4):
PHASE(theta=(i / n) * pi, target=qv)
@QFunc
def bar(m: QParam[int], qv: QBit) -> None:
foo(n=m * 2, qv=qv)
foo(n=m // 2, qv=qv)
@QFunc
def main() -> None:
qv = QBit("qv")
allocate(1, qv)
bar(m=2, qv=qv)
model = create_model(main)
qprog = synthesize(model)
show(qprog)
This results in the following quantum program:
QVar Slicing¶
When creating a model, you can use Python slicing on QArray[QBit]
variables to
connect to a port that accepts a smaller number of qubits:
from sympy import pi
from classiq import QFunc, QParam, QArray, QBit, H, PHASE, allocate
from classiq import create_model, synthesize, show
@QFunc
def foo(n: QParam[int], qv: QBit) -> None:
H(target=qv)
for i in range(4):
PHASE(theta=(i / n) * pi, target=qv)
@QFunc
def bar(m: QParam[int], qbv: QArray[QBit, 2]) -> None:
foo(n=m * 2, qv=qbv[0])
foo(n=m // 2, qv=qbv[1])
@QFunc
def main() -> None:
qbv = QArray("qbv")
allocate(2, qbv)
bar(m=2, qbv=qbv)
model = create_model(main)
qprog = synthesize(model)
show(qprog)
This results in the following quantum program:
Note that the slice also accepts QParam
objects and expressions
involving them.