Basics: evals, join, identity, and QExpr-returning functions¶
FLEQ provides three namespaces, qexpr, qlist and datalist, which are
available in the header files qexpr.h, qlist.h, and
datalist.h, respectively. The functions described below will be scoped by
their appropriate namespace, where this scoping can be avoided using the typical
C++ keywords using namespace <name>.
The simplest quantum kernel expressions are basic primitives representing single quantum gates.
QExpr qexpr::_<g>(Args... args)
For each primitive gate
<g>(Args...)in the Intel® Quantum SDK (see Developers Guide and Reference), there is a corresponding primitive quantum kernel expression_<g>(Args...). The arguments of_<g>are the same as those of<g>.
Other basic primitives include
QExpr qexpr::identity()
An identity or no-op quantum kernel expression.
QExpr qexpr::global_phase(double angle)
A quantum kernel expression that applies a global phase but otherwise has no effect. The distinction between
identityandglobal_phaseis primarily relevant to unitary control of aQExpr; see Control. Theangleargument toglobal_phase()can be dynamic–it need not be resolvable at compile-time.
Two quantum kernel expressions e1 and e2 can be combined together
in several equivalent ways:
QExpr qexpr::join(QExpr e1, QExpr e2)
Returns the sequential composition of
e1followed bye2. In other words, when evaluated,qexpr::join(e1,e2)will execute the logic associated withe1followed by the logic associated withe2.
e1 + e2
Shorthand for
qexpr::join(e1, e2).
e2 * e1
Combines the quantum kernel expressions in composition order, meaning that it evaluates its second argument first. Composition order is natural when thinking in terms of matrix multiplication or function composition. Equivalent to
qexpr::join(e1,e2).
The third core component of QExpr functionality are the evaluation functions
introduced in Evaluating quantum kernel expressions.
void qexpr::eval_hold(QExpr e)
Executes the quantum instructions represented by the quantum kernel expression
e. It guarantees that the quantum state will be maintained after execution of the instructions.
void qexpr::eval_release(QExpr e)
Executes the quantum instructions represented by
eunder the assumption that the quantum state need not be preserved after execution. It is the direct analog to therelease_quantum_statedirective for ordinaryquantum_kernelfunctions; see the Developers Guide and Reference for details.
When an evaluation function is called inside a classical function f(), the compiler treats f() itself as a
quantum_kernel function.
This helps lift some of the limitations placed on conventional quantum kernel
functions. For one, multiple evaluation calls, or even a single evaluation call in the case of Branching and Barriers and binding, can result in multiple quantum basic blocks. In addition, local qubits, which can only be declared inside quantum_kernel functions, can now be declared in functions that make evaluation calls to quantum kernel expressions. See Local qubits for more details.
A quantum kernel expression can be constructed inline inside an evaluation call, such as
qexpr::eval_hold(qexpr::_PrepZ(q) + qexpr::_H(q));
Alternatively, ordinary C++ functions can return a quantum kernel expression of type QExpr, which can then be evaluated.
QExpr prep_plus_qexpr(qbit& q) {
    return qexpr::_PrepZ(q) + qexpr::_H(q);
}
int main() {
  ...
  qexpr::eval_hold(prep_plus_qexpr(q));
  ...
}
QExpr-returning functions do come with some limitations and special features:
A function returning a
QExprmust have a single return statement; this is enforced by the FLEQ compilation stage of the Intel® Quantum Compiler (IQC). If the user desires branching or conditionalQExprreturns, they should use thecIffunctionality described in Branching.Traditional C++ branching and looping is allowed under the same constraints as a
quantum_kernelfunction for classical data, but generally is not encouraged. Best practice is to usecIf(Branching) and recursion (Recursion). There are known cases where traditional branching and FLEQ are incompatible, in particular:
FLEQ does not support loops that are bound by FLEQ functions such as the size of a
QListorDataList. This is because classical loop unrolling comes before FLEQ processing in compilation (see Overview of FLEQ compilation). For example, the following function results in a compiler error:quantum_kernel void prepAll_qlist_BAD(qlist::QList qs) { for (int i=0; i<qs.size(); i++) { PrepZ(qs[i]); } }The loop above can be written using a global
QListof a fixed size. For example, the following function is acceptable:const int N = 5; qbit qs[N]; quantum_kernel void prepAll_qlist_global() { for (int i=0; i<N; i++) { PrepZ(qs[i]); } }Quantum kernel expressions and evaluation calls can be placed in a for-loop like
prepAll_qlist_global(), but only inside functions annotated by thequantum_kernelkeyword. Ordinarily C++ functions that evaluate quantum kernel expressions need not have thequantum_kernelkeyword explicitly.const int N = 5; qbit qs[N]; quantum_kernel void prepAll_qexpr_global_eval() { for (int i=0; i<N; i++) { qexpr::eval_hold(qexpr::_PrepZ(qs[i])); } }
qbitarguments must be passed by reference (enforced by the FLEQ compilation stage of IQC).FLEQ values of type
QExpr,QList, andDataListshould be passed by value, as they are immutable functional data structures. This is not strictly enforced by the compiler but can result in compilation failures.Data intended to be treated as an output of the
QExprfunction, such ascbitorboolvalues or arrays, should be returned through a reference argument (return-by-reference), as the function must return aQExprtype.
cbitvariables and arrays populated by quantum measurements (i.e. via_MeasZ) in aQExprare not written to until after the evaluation call resolves, unless they come before aqexpr::fenceorqexpr::bindstatement; see Barriers and binding. This has implications for classical branching withQExpr; see Branching and Barriers and binding.Consider a
QExpr-returning functionfoothat returns-by-reference additional data, i.e. writes to some memory such ascbitmeasurement data. If a user intends for the returned data to be read by anotherQExpr-returning functionbarin the same evaluation or return statement, then (1)fooandbarmust be separated by aqexpr::bindstatement (see Barriers and binding); and (2) the data must be passed tobarby reference. For example, the following is valid:QExpr foo(cbit &write_to) { ... } QExpr bar(cbit &read_from) { ... } int main() { ... cbit data; qexpr::eval_hold(foo(data) << bar(data)); }
QExpr-returning function pointers can be used both as traditional C++ function arguments and template arguments to form higher-orderQExprtransformations. Theqexpr::maputility function is one such example; see Higher-order functions.