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 @QFunc
s, 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:
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:
Note that the QVar
slice also accepts QParam
objects and expressions
involving them.