Programming with the Intel® Quantum SDK¶
In-lining & quantum_kernel
functions¶
When the compiler prepares a quantum_kernel
function, it separates all the quantum
instructions (as Intermediate Representation (IR)) from the classical IR so that it can deliver a complete set
of instructions to the quantum backend.
Local declarations and operations with traditional C++ data types are supported
inside a quantum_kernel
function, which aids readability and preserves programming
concepts.
At compile time, these “classical” instructions are pulled out of the quantum_kernel
.
This has a consequence on classical instructions, especially bool
and cbit
measurement results:
any operations on classical variables written inside a quantum_kernel
will
be executed at the beginning of that quantum_kernel
, unless
they are written after the final quantum gate in the quantum_kernel
.
qbit q0;
qbit q1;
quantum_kernel void myKernel() {
bool b = false;
std::cout << "b has value false (0) here after initialization: "
<< b << "\n";
PrepZ(q0);
X(q0);
MeasZ(q0, b);
std::cout << "b still has value 0 here since the quantum gates are not complete: "
<< (int)c << "\n";
PrepZ(q1);
std::cout << "After all gates in quantum_kernel have executed, b has value true (1): "
<< b << "\n";
}
A quantum_kernel
may be called from within another quantum_kernel
. Here,
too, the compiler in-lines the quantum instructions from the
innermost quantum_kernel
and continues until it produces one sequence of
instructions that corresponds to the “top-level” quantum_kernel
call that
begins the quantum algorithm.
In-lining combined with the earlier rule on rearranging operations on measurement results means that for
quantum_kernel
functions containing a measurement which are called in the middle
of another quantum_kernel
function, the operations on those cbit
and bool
measurement results will be moved to
the beginning of the resulting set of instructions.
This means that the following code:
If a user needs classical instructions to be executed strictly in the middle of
a quantum algorithm, they should break up the algorithm into multiple top-level
quantum kernel functions. Alternatively, they can use the bind
operator on
quantum kernel expressions (see FLEQ Guide and Reference (Barriers and binding)).
The restriction that the entire quantum_kernel
be known at compile time
together with the in-lining behavior means that the top-level kernel cannot
accept an arbitrary variable of type qbit
as a parameter. The variables of
qbit
type that will be operated on must be explicitly defined in the
“top-level” kernel’s instructions; however, inner quantum_kernel
functions may be
written to accept qbit
type variables as parameters.
Note
This restriction applies primarily to quantum_kernel
functions, and not
to FLEQ. See FLEQ Guide and Reference if you need this feature.
qbit qs[3];
// A nested quantum_kernel may take either classical or quantum arguments
quantum_kernel void bell(qbit &a, qbit &b) {
PrepZ(a);
PrepZ(b);
H(a);
CNOT(a,b);
}
// A top level quantum_kernel may take classical arguments, but not quantum
// arguments
quantum_kernel void topLevelBell() {
bell(q[0],q[2]);
}
int main() {
// may call top-level quantum_kernel
topLevelBell();
// may not call quantum_kernel with quantum arguments
// invalid: bell(q[0], q[2]);
}
Measurements using Simulated Quantum Backends¶
A typical quantum program using the Intel® Quantum SDK will do effectively the following sequence:
1. Submit quantum_kernel
functions to a quantum backend.
2. Execute quantum_kernel
on the backend.
3. Retrieve results.
4. Repeat 1-3 as needed.
After the quantum_kernel
has finished executing,
users will need to retrieve results from the backend.
This section describes the result retrieval and aggregation process, using the
example of the FullStateSimulator
backend.
The FullStateSimulator
class provides three main approaches to obtain statistical measurements:
getProbabilities()
(and/or other simulation data)getSamples()
Repeated execution of explicit measurement operations e.g.
MeasZ
(sampling).
These methods will be elaborated in the following sections.
Both Intel® Quantum Simulator (IQS) and Quantum Dot (QD) Simulator
backends support collecting the simulation details, such as the quantum
amplitudes, conditional probabilities, or single-qubit probabilities. The
FullStateSimulator
class provides these data regardless
of which backend is selected to run the simulation.
Simulation Data¶
Method |
Returned object |
Efficiency (with IQS) |
Recommended? |
Other Notes |
---|---|---|---|---|
|
|
Best |
Yes |
|
|
|
Best |
Yes |
|
|
|
Good |
Yes |
|
|
|
Good |
No |
Accurate up to global phase |
Repeated sampling calls |
User-defined |
Worst |
No |
Complexity scales with number of samples |
Working with the simulation data returned by FullStateSimulator
methods
such as getProbabilities()
is often the most
computationally efficient route to simulating a quantum algorithm.
This is because quantum algorithms often encode their results as
probabilities of different states.
If the entire algorithm needed to run many times to sample the probability, as
required on a hardware quantum backend, the simulation time would increase
significantly.
For applications that need a set of measurement outcomes, both backends of the
FullStateSimulator
offer a second route to obtain the simulation data, which
avoids the need for repeated executions of a given quantum_kernel
function.
This route consists of calling getSamples()
to get sequences of outcomes as if
measurements were applied to the qubit register. This sampling of results doesn’t
affect the state and can even be applied as many times as an application calls for.
Combining Simulation Data and Measurement Operations¶
IQS offers the ability to retrieve simulation results (i.e. from getProbabilities()
or getSamples()
) when quantum_kernel
functions include measurement
gates (e.g. MeasZ()
).
Note
This feature is not available in QD Simulator because it doesn’t collapse the state (see the Quantum Dot (QD) Simulator). This means combining results of measurement operations and sampling results with the QD Simulator can yield unexpected results.
When using probability measurement and explicit measurement gates on a qubit in simulations,
IQS will cause a ‘partial collapse’ of the
state in the simulator to a sub-space. You can combine such operations with a
sampling technique like getProbability
or getSamples
to compute data or
collect statistics on the sub-space. To support combining measurement operations
and simulation data, IQS will always collapse the quantum state of the simulator
when it encounters a measurement operation in a quantum_kernel
. Any
subsequent querying of the FullStateSimulator
after measurement will always
give the same result on the qubits that had one of MeasX
, MeasY
, or
MeasZ
applied, and other qubits will have any correlated effects on their
probabilities present.
Measuring a qubit leaves it in one of the two states into which the measurement was projected; e.g. measuring a qubit along the \(Z\)-axis (in a Bloch sphere representation) leaves it in either a \(\ket{0}\) or \(\ket{1}\) state. Another perspective on this is that the post-measurement state of the entire set of qubits now occupies a sub-space of the Hilbert space previously occupied by the pre-measurement qubits. This can be qualitatively understood by noting that there is no uncertainty in the state of the measured qubit. A measurement also has consequences on the correlations arising from entanglement between qubits. More simply, measuring one qubit can affect the probabilities of the outcomes of measuring a different qubit (provided the two qubits were entangled). In the extreme case, a large amount of correlation present in the system could mean that a single measurement applied on one qubit results in the state of the entire set of qubits being determined, such as for a Bell pair or GHZ state.
Using Only Measurement Operations¶
A third option is to collect your own statistical results by executing the
entire quantum algorithm with all the required measurement operations many times
in a loop (or other control-flow structure) to direct execution flow. Each
iteration of the quantum algorithm produces and then stores, analyzes, or
accumulates the result of the measurements. Under ideal conditions (no noise),
the sampling & measurement approaches will each produce statistically-equivalent
results, especially with large sample sizes. Because quantum algorithms running
on quantum hardware must use the measurement approach, the simulation data
and sampling approaches can be seen as a
debugging mode for the measurement approach. IQS supports using
measurements anywhere in the quantum algorithm; in contrast, QD Simulator only
supports reading measurements at the end of the quantum_kernel
.
Local qbit
Variables¶
qbit
variables can be declared globally or locally. When the compiler maps the program qubits
to physical qubits, each qbit
variable will be assigned to a physical qubit.
Since the compiler cannot guarantee the state that a local qbit
variable is in, local qbit
variables
must be initialized using PrepX
, PrepY
, or PrepZ
before being used. At the end of
the quantum_kernel
, the local qbit
variables must be released. This can be achieved
through measurements or release_quantum_state()
.
Note that if using release_quantum_state()
, the quantum states are unspecified after the
function call (see Language Extensions). Without releasing the quantum states,
the physical qubits assigned to the local qbit
variables might be assigned to other local
qbit
variables in a new quantum_kernel
function while still holding the quantum states
of the out-of-scope variables. The out-of-scope variables’ physical qubits will not be assigned
to unreleased global qbit
variables, however.
In the following example, a local qbit
variable is declared, initialized, and measured.
quantum_kernel void kernel() {
qbit q;
bool b; // can also be of type cbit
PrepZ(q); // prepare the qbit variable before applying gates
H(q);
MeasZ(q, b); // release the qbit variable at the end of the quantum_kernel
}
If local qbit
variables are entangled with global qbit
variables, the entanglement persists
after the local qbit
variables go out of scope. The user must insert gates needed to disentangle the
local qbit
variables from the global ones before releasing the local variables’ quantum
states.
qbit global;
quantum_kernel void errorExampleEntangledQubits() {
qbit local;
PrepZ(local); // Prep the qbit variable before applying gates
H(local);
CNOT(local, global);
// After local goes out of scope, the physical qubit it was assigned to
// is still entangled with global
}
The recommended best practice with regards to
local qbit
variables is therefore to prep them before they are used and insert
gates to undo the entanglement between local and global qbit
variables before releasing the quantum
states at the end of quantum_kernel
functions.
For information on how to use local qbit
variables with quantum kernel
expressions and FLEQ, refer to FLEQ Guide and Reference (Local qubits).