Skip to content

4. Simple examples

Vlad Gheorghiu edited this page Jan 16, 2018 · 16 revisions

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/.

Gates and states

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 $|0\rangle$ of the $Z$ Pauli operator. In line 11 we use the Gates singleton gt and assign to U the bit flip gate gt.X. In line 12 we compute the result of the operation $X|0\rangle$, and display the result $|1\rangle$ in lines 14 and 15. In line 15 we use the display manipulator disp(), which is especially useful when displaying complex matrices, as it displays the entries of the latter in the form $a+bi$, in contrast to the form $(a,b)$ used by the C++ standard library. The manipulator also accepts additional parameters that allows e.g. setting to zero numbers smaller than some given value (useful to chop small values), and it is in addition overloaded for standard containers, iterators and C-style arrays.

In line 17 we reassign to psi the state $|10\rangle$ via the function 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 $|10\rangle$, i.e. $|11\rangle$. We then display the result of the computation in lines 21 and 22.

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.

Measurements

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 $CNOT_{ab}(H\otimes I)|00\rangle$, which is the Bell state $(|00\rangle + |11\rangle)/\sqrt{2}$. We display it in lines 15 and 16.

In line 19 we use the function apply() to apply the gate $X$ on the second qubit[^2] of the previously produced Bell state. The function 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 $X$ basis. You may be confused by the apparition of 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 $X$ operator are just the columns of the Hadamard matrix. As mentioned before, as all other library functions, 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.

Quantum operations

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 $(|00\rangle + |11\rangle)/\sqrt{2}$, then display it in lines 12 and 13.

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: $|0\rangle\langle 0|$ and $|1\rangle\langle 1|$, then display the latter. Note that Quantum++ uses the 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.

Timing

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.

Input/output

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.

Exceptions

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.

Clone this wiki locally