-
Notifications
You must be signed in to change notification settings - Fork 132
4. Simple examples
All of the examples of this section are copied verbatim from the
directory qpp/examples
and are fully compilable. For convenience, the
location of the source file is displayed in the first line of each
example as a C++ comment. The examples are simple and demonstrate the
main features of Quantum++. They cover
only a small part of library functions, but enough to get the interested
user started. For an extensive reference of all library functions,
including various overloads, the user should consult the official
API documentation qpp/doc/refman.pdf
.
Additional examples (not
discussed in this document) are located in `qpp/examples/.
Let us introduce the main objects used by Quantum++: gates, states and basic operations. Consider the example qpp/examples/gates_states.cpp with content listed below.
// Gates and states
// Source: ./examples/gates_states.cpp
#include <iostream>
#include "qpp.h"
int main() {
using namespace qpp;
ket psi = st.z0; // |0> state
cmat U = gt.X;
ket result = U * psi;
std::cout << ">> The result of applying the bit-flip gate X on |0> is:\n";
std::cout << disp(result) << '\n';
psi = 10_q; // |10> state
U = gt.CNOT; // Controlled-NOT
result = U * psi;
std::cout << ">> The result of applying the gate CNOT on |10> is:\n";
std::cout << disp(result) << '\n';
U = randU();
std::cout << ">> Generating a random one-qubit gate U:\n";
std::cout << disp(U) << '\n';
result = applyCTRL(psi, U, {0}, {1}); // Controlled-U
std::cout << ">> The result of applying the CTRL-U gate on |10> is:\n";
std::cout << disp(result) << '\n';
}
A possible output is:
>> The result of applying the bit-flip gate X on |0> is:
0
1
>> The result of applying the gate CNOT on |10> is:
0
0
0
1
>> Generating a random one-qubit gate U:
-0.50206 + 0.0354176i -0.422949 - 0.753522i
-0.206807 - 0.838995i -0.390495 + 0.317541i
>> The result of applying the CTRL-U gate on |10> is:
0
0
-0.50206 + 0.0354176i
-0.206807 - 0.838995i
In line 4 of Listing [lst2] we bring the namespace qpp
into the
global namespace.
In line 10 we use the States
singleton st
to declare psi
as the
zero eigenvector Gates
singleton gt
and assign to U
the bit flip gate
gt.X
. In line 12 we compute the result of the operation disp()
, which is especially useful when
displaying complex matrices, as it displays the entries of the latter in
the form
In line 17 we reassign to psi
the state mket()
. We could have also used the Eigen
3 insertion operator
ket psi(4); // must specify the dimension before insertion of elements via <<
psi << 0, 0, 1, 0;
however the mket()
function is more concise. In line 18 we declare a
gate U
as the Controlled-NOT with control as the first subsystem, and
target as the last, using the global singleton gt
. In line 19 we
declare the ket result
as the result of applying the Controlled-NOT
gate to the state
Next, in line 24 we generate a random unitary gate via the function
randU()
, then in line 28 apply the Controlled-U, with control as the
first qubit and target as the second qubit, to the state psi
. Finally,
we display the result in lines 29 and 30.
Let us now complicate things a bit and introduce measurements. Consider the example in Listing [lst3].
A possible output is:
In line 12 of Listing [lst3] we use the function kron()
to create
the tensor product (Kronecker product) of the Hadamard gate on the first
qubit and identity on the second qubit, then we left-multiply it by the
Controlled-NOT gate. In line 13 we compute the result of the operation
In line 19 we use the function apply()
to apply the gate apply()
takes as its third parameter a list of subsystems, and in our
case {1}
denotes the second subsystem, not the first. The function
apply()
, as well as many other functions that we will encounter, have
a variety of useful overloads, see doc/refman.pdf
for a detailed
library reference. In lines 20 and 21 we display the newly created Bell
state.
In line 24 we use the function measure()
to perform a measurement of
the first qubit (subsystem {0}
) in the gt.H
, however this overload of the function
measure()
takes as its second parameter the measurement basis,
specified as the columns of a complex matrix. In our case, the
eigenvectors of the measure()
returns by value, hence it does not modify its argument. The return of
measure
is a tuple consisting of the measurement result, the outcome
probabilities, and the possible output states. Technically measure()
returns a tuple of 3 elements
std::tuple<qpp::idx, std::vector<double>, std::vector<qpp::cmat>>
The first element represents the measurement result, the second the
possible output probabilities and the third the output output states.
Instead of using this long type definition, we use the new C++11 auto
keyword to define the type of the result measured
of measure()
. In
lines 25–30 we use the standard std::get<>()
function to retrieve each
element of the tuple, then display the measurement result, the
probabilities and the resulting output states.
In Listing [lst4] we introduce quantum operations: quantum channels, as well as the partial trace and partial transpose operations.
The output of this program is:
The example should by now be self-explanatory. In line 11 of
Listing [lst4] we define the input state rho
as the projector onto
the Bell state
In lines 16–19 we partially transpose the first qubit, then display the
eigenvalues of the resulting matrix rhoTA
.
In lines 21–23 we define a quantum channel Ks
consisting of two Kraus
operators: std::vector<cmat>
container to store the Kraus operators and
define a quantum channel.
In lines 25–29 we display the superoperator matrix as well as the Choi
matrix of the channel Ks
.
Next, in lines 32–35 we apply the channel Ks
to the first qubit of the
input state rho
, then display the output state rhoOut
.
In lines 38–40 we take the partial trace of the output state rhoOut
,
then display the resulting state rhoA
.
Finally, in lines 43 and 44 we compute the von-Neumann entropy of the resulting state and display it.
To facilitate simple timing tasks,
Quantum++ provides a Timer
class
that uses internally a std::steady_clock
. The program in
Listing [lst5] demonstrate its usage.
A possible output of this program is:
In line 12 of Listing [lst5] we change the default output precision from 4 to 8 decimals after the delimiter.
In line 15 we use the Codes
singleton codes
to retrieve in c0
the
first codeword of the Shor’s $9,1,3$ quantum error correcting code.
In line 17 we declare an instance timer
of the class Timer
. In
line 18 we declare a random permutation perm
via the function
randperm()
. In line 19 we permute the codeword according to the
permutation perm
using the function syspermute()
and store the
result . In line 20 we stop the timer. In line 21 we display the
permutation, using an overloaded form of the disp()
manipulator for
C++ standard library containers. The latter takes a std::string
as its
second parameter to specify the delimiter between the elements of the
container. In line 22 we display the elapsed time using the
ostream operator<<()
operator overload for Timer
objects.
Next, in line 24 we reset the timer, then display the inverse
permutation of perm
in lines 25 and 26. In line 27 we permute the
already permuted state c0perm
according to the inverse permutation of
perm
, and store the result in c0invperm
. In lines 28 and 29 we
display the elapsed time. Note that in line 28 we used directly
t.toc()
in the stream insertion operator, since, for convenience, the
member function Timer::toc()
returns a const Timer&
.
Finally, in line 31, we verify that by permuting and permuting again using the inverse permutation we recover the initial codeword, i.e. the norm difference has to be zero.
We now introduce the input/output functions of Quantum++, as well as the input/output interfacing with MATLAB. The program in Listing [lst6] saves a matrix in both Quantum++ internal format as well as in MATLAB format, then loads it back and tests that the norm difference between the saved/loaded matrix is zero.
The output of this program is:
Note that in order to use the
MATLAB input/output
interface support, you need to explicitly include the header file
MATLAB/matlab.h
, and you also need to have
MATLAB or
MATLAB compiler installed,
otherwise the program fails to compile. See the file ./README.md
for
extensive details about compiling with
MATLAB support.
Most Quantum++ functions throw
exceptions in the case of unrecoverable errors, such as out-of-range
input parameters, input/output errors etc. The exceptions are handled
via the class Exception
, derived from std::exception
. The exception
types are hard-coded inside the strongly-typed enumeration (enum class)
Exception::Type
. If you want to add more exceptions, augment the
enumeration Exception::Type
and also modify accordingly the member
function Exception::construct_exception_msg_()
, which constructs the
exception message displayed via the overridden virtual function
Exception::what()
. Listing [lst7] illustrates the basics of
exception handling in Quantum++.
The output of this program is:
In line 11 of Listing [lst7] we define a random density matrix on four
qubits (dimension 16). In line 15, we compute the mutual information
between the first and the 5-th subsystem. Line 15 throws an exception of
type qpp::exception::SubsysMismatchDim
exception, as there are only
four systems. We next catch the exception in line 19 via the
std::exception
standard exception base class. We could have also used
the Quantum++ exception base class qpp::exception::Exception
, however
using the std::exception
allows the catching of other exceptions, not
just of the type Exception
. Finally, in line 21 we display the
corresponding exception message.
Copyright (c) 2017 - 2025 softwareQ Inc. All rights reserved.