Getting started with Intel Quantum Simulator: Examples

Tutorial on the basic use of Intel QS through its Python interface: Two examples are provided.

NOTE: Currently, the Python implementation only allows for single-core execution and does not take advantages of the MPI protocol. However the user can familiarize with the same functionalities available in the distributed implementation (only C++ at the moment) and the transition should be relatively straighforward since all methods maintain name and effect.


Import Intel QS library

Let’s start by importing the Python library with the class and methods defined in the C++ implementation.

[2]:
# Import the Python library with the C++ class and methods of Intel Quantum Simulator.
# If the library is not contained in the same folder of this notebook, its path has to be added.
import sys
sys.path.insert(0, '../lib_python')
import intelqs as simulator

# Import NumPy library with Intel specialization.
import numpy as np
from numpy import random_intel

# Import graphical library for plots.
import matplotlib.pyplot as plt

Example 1

Create the state of a quantum register, having \(N>3\) qubits.

The state is initialized as a computational basis state (using the keyword “base”) corresponding to the index 0.

The index corresponds to a \(N\)-bit integer in decimal representation. With \(N\) qubits there are \(2^N\) indices, from 0 to \(2^{N-1}\).

[3]:
# Allocate memory for the quantum register's state and initialize it to |000...0>.
num_qubits = 4;
if num_qubits<3:
    num_qubits = 4;
psi1 = simulator.QubitRegister(num_qubits, "base", 0, 0);

Let us apply a X Pauli gate on qubit 0, effectively flipping it from |0> to |1>, followed by the Hadamard gate on all other qubits.

[4]:
# Let us apply a X Pauli gate on qubit 0, effectively flipping it from |0> to |1>.
psi1.ApplyPauliX(0);

# Let us apply an Hadamard gate on all other qubits.
for q in range(1,num_qubits):
    psi1.ApplyHadamard(q);

In addition to one-qubit gates, universal quantum computation can be achieved via 2-qubit entangling gates. For example, we now apply a CNOT between qubit 2 (here the control qubit) and qubit 1 (target qubit).

[5]:
# Two qubit gates are applied in a similar way. For example, a C-NOT between qubit 2 (control qubit) and qubit 1 (target qubit):
control = 2;
target = 1;
psi1.ApplyCPauliX( control , target );

To extract information from the quantum register, one can obtain the probability of measuring a certain qubit in the computational basis and obtaining the outcome “1” (meaning that the state is in |1>). In this example we measure qubit 1. Once the probability is known, one can draw a random number to simulate the stochastic outcome of the measurement and collapse the wavefunction accordingly.

[7]:
# Compute the probability of qubit 1 being in state |1>.
measured_qubit = 1;
prob = psi1.GetProbability( measured_qubit );

print("Probability that qubit {}, if measured, is in state |1> = {}\n".format(measured_qubit, prob));

# Draw random number in [0,1)
r = np.random.rand()
if r < prob:
    # Collapse the wavefunction according to qubit 1 being in |1>.
    print("Simulated outcome is 1. Collapse the function accordingly.")
    psi1.CollapseQubit(measured_qubit,True);
else:
    # Collapse the wavefunction according to qubit 1 being in |0>
    print("Simulated outcome is 0. Collapse the function accordingly.")
    psi1.CollapseQubit(measured_qubit,False);

# In both cases one needs to re-normalize the wavefunction:
psi1.Normalize();
Probability that qubit 1, if measured, is in state |1> = 0.0

Simulated outcome is 0. Collapse the function accordingly.

Example 2

Create the state of a quantum register, having \(N>3\) qubits.

The state is initialized as a random state (using the keyword “rand”):

This requires a random number generator (RNG), that we initialize just before the second register. Notice that ‘777’ plays the role of the seed to initialize the RNG.

[8]:
num_qubits = 4;
if num_qubits<3:
    num_qubits = 4;
psi2 = simulator.QubitRegister(num_qubits, "rand", 777, 0);

Let us apply one- and two-qubit gates as in the previous exmaple.

[9]:
# Let us apply a X Pauli gate on qubit 0, effectively flipping it from |0> to |1>.
psi2.ApplyPauliX(0);

# Let us apply an Hadamard gate on all other qubits.
for q in range(1,num_qubits):
    psi2.ApplyHadamard(q);

One can define an arbitrary single-qubit gate and apply it to the chosen qubit.

In addition one can apply a custom one-qubit gate conditionally on the state of a control qubit.

[10]:
# Define an arbitrary single qubit gate and apply it to the chosen qubit.
# The quantum gate G is given by a 2x2 unitary matrix, here using a bi-dimensional NumPy array.
G = np.zeros((2,2),dtype=np.complex_);
G[0,0] =  0.592056606032915 + 0.459533060553574j;
G[0,1] = -0.314948020757856 - 0.582328159830658j;
G[1,0] =  0.658235557641767 + 0.070882241549507j;
G[1,1] =  0.649564427121402 + 0.373855203932477j;

qubit = 0;
psi2.Apply1QubitGate(qubit,G);

# It is also possible to apply the arbitrary gate specified by G controlled on the state of another qubit.
# G is applied conditioned on the control qubit being in |1>.
control = 1;
target = 2;
psi2.ApplyControlled1QubitGate( control, target, G);

# Notice that this output is directed to the terminal and not re-directed to the iPython notebook.
psi2.Print("After all gates.")
<<the output has been redirected to the terminal>>

To extract information from the quantum register, one can obtain the expectation value of Pauli strings.

For example, consider the Pauli string given by:

\[X_0 \otimes id_1 \otimes Z_2 \otimes Y_3\]

Such observable is defined by: - the position of the non-trivial Pauli matrices, in this case {0,2,3} - the corresponding Pauli matrices (X=1, Y=2, Z=3).

To facilitate the verification of the expectation value, we reinitialize the quantum state to |+-01>.

We also consider the Pauli staing

\[X_0 \otimes id_1 \otimes Z_2 \otimes Z_3\]

.

[12]:
# Prepare the state |+-01>
index = 2+8;
psi2.Initialize("base",index);
# Notice that GetProbability() does not change the state.
for qubit in range(0,num_qubits):
    prob = psi2.GetProbability( qubit );
    print("Probability that qubit {}, if measured, is in state |1> = {}\n".format(qubit, prob));

psi2.ApplyHadamard(0);
psi2.ApplyHadamard(1);

# The Pauli string given by:  X_0 . id_1 . Z_2 . Y_3
# Such observable is defined by the position of the non-trivial Pauli matrices:
qubits_to_be_measured = [0,2,3]

# And by the corresponding Pauli matrices (X=1, Y=2, Z=3)
observables = [1,3,2]

# The expectation value <psi2|X_0.id_1.Z_2.Y_3|psi2> is obtained via:
average = psi2.ExpectationValue(qubits_to_be_measured, observables, 1.);
print("Expectation value <+-01|X_0.id_1.Z_2.Y_3|+-01> = {}\n".format(average));

# The expectation value <psi2|X_0.id_1.Z_2.Y_3|psi2> is obtained via:
qubits_to_be_measured = [0,2,3]
observables = [1,3,3]
average = psi2.ExpectationValue(qubits_to_be_measured, observables, 1.);
print("Expectation value <+-01|X_0.id_1.Z_2.Z_3|+-01> = {}\n".format(average));

# Trivial expectation:
average = psi2.ExpectationValue([0],[1], 1.);
print("Expectation value <+-01|X_0|+-01> = {}\n".format(average));

# The expectation value <psi2|X_0.id_1.id_2.Z_3|psi2> is obtained via:
average = psi2.ExpectationValue([0,3],[1,3], 1.);
print("Expectation value <+-01|X_0.Z_3|+-01> = {}\n".format(average));
Probability that qubit 0, if measured, is in state |1> = 0.0

Probability that qubit 1, if measured, is in state |1> = 1.0

Probability that qubit 2, if measured, is in state |1> = 0.0

Probability that qubit 3, if measured, is in state |1> = 1.0

Expectation value <+-01|X_0.id_1.Z_2.Y_3|+-01> = 0.0

Expectation value <+-01|X_0.id_1.Z_2.Z_3|+-01> = -0.9999999999999989

Expectation value <+-01|X_0|+-01> = 1.0

Expectation value <+-01|X_0.Z_3|+-01> = -1.0000000000000004

[13]:
# Prepare the state |+-01>
index = 2+8;
psi2.Initialize("base",index);
# Notice that GetProbability() does not change the state.
for qubit in range(0,num_qubits):
    prob = psi2.GetProbability( qubit );
    print("Probability that qubit {}, if measured, is in state |1> = {}\n".format(qubit, prob));

psi2.ApplyHadamard(0);
psi2.ApplyHadamard(1);

# The Pauli string given by:  X_0 . id_1 . Z_2 . Y_3
# Such observable is defined by the position of the non-trivial Pauli matrices:
qubits_to_be_measured = [0,2,3]

# And by the corresponding Pauli matrices (X=1, Y=2, Z=3)
observables = [1,3,2]

# The expectation value <psi2|X_0.id_1.Z_2.Y_3|psi2> is obtained via:
average = psi2.ExpectationValue(qubits_to_be_measured, observables, 1.);
print("Expectation value <+-01|X_0.id_1.Z_2.Y_3|+-01> = {}\n".format(average));

# The expectation value <psi2|X_0.id_1.Z_2.Y_3|psi2> is obtained via:
qubits_to_be_measured = [0,2,3]
observables = [1,3,3]
average = psi2.ExpectationValue(qubits_to_be_measured, observables, 1.);
print("Expectation value <+-01|X_0.id_1.Z_2.Z_3|+-01> = {}\n".format(average));
Probability that qubit 0, if measured, is in state |1> = 0.0

Probability that qubit 1, if measured, is in state |1> = 1.0

Probability that qubit 2, if measured, is in state |1> = 0.0

Probability that qubit 3, if measured, is in state |1> = 1.0

Expectation value <+-01|X_0.id_1.Z_2.Y_3|+-01> = 0.0

Expectation value <+-01|X_0.id_1.Z_2.Z_3|+-01> = -0.9999999999999989

[14]:
# Extra expectation values.

# Prepare the state |+-01>
index = 2+8;
psi2.Initialize("base",index);
psi2.ApplyHadamard(0);
psi2.ApplyHadamard(1);

# The expectation value of X_0:
average = psi2.ExpectationValue([0],[1], 1.);
print("Expectation value <+-01|X_0|+-01> = {}\n".format(average));

# The expectation value of X_0.Z_3:
average = psi2.ExpectationValue([0,3],[1,3], 1.);
print("Expectation value <+-01|X_0.Z_3|+-01> = {}\n".format(average));

# The expectation value of X_0.Z_2:
average = psi2.ExpectationValue([0,2],[1,3], 1.);
print("Expectation value <+-01|X_0.Z_2|+-01> = {}\n".format(average));

# The expectation value of X_1.Z_2:
average = psi2.ExpectationValue([1,2],[1,3], 1.);
print("Expectation value <+-01|X_1.Z_2|+-01> = {}\n".format(average));
Expectation value <+-01|X_0|+-01> = 1.0

Expectation value <+-01|X_0.Z_3|+-01> = -1.0000000000000002

Expectation value <+-01|X_0.Z_2|+-01> = 0.999999999999998

Expectation value <+-01|X_1.Z_2|+-01> = -1.0000000000000002

### END

[ ]: