The Qubits object

The most important class in the simulator is called Qubits, which holds all the information for the current simulation. The object depends on an enum template parameter, Type, which determines the simulator implementation.

enum qsl::Type

An enum for distinguishing between different simulator implementations.

A value from this enum should be passed as the first template parameter to the Qubits class to determine which simulator should be used.

Values:

enumerator Default

The default simulator object (no inline assembly or threading)

enumerator Omp

Simulator supporting multithreading based on OpenMP.

enumerator NP

Number preserving simulator.

enumerator OmpNP

Number preserving simulator with multithreading.

enumerator Resize

Resizeable simulator.

Default Simulator

The most basic simulator is called Qubits<Type::Default>. It does not use any special optimisations (like inline assembly), and does not support threading.

Warning

doxygenclass: Cannot find class “Qubits< Type::Default, Fp >” in doxygen xml output for project “QSL” from directory: doxy/xml/

Constructing the object

There are several ways to construct a qubits object, which are summarised in the example below:

// Make a simulator object with 2 qubits, using
// double precision The object is initialised
// to the all zero state.
Qubits<Type::Default, double> q1(2);

// To initialise an object in an arbitrary starting
// state, first make a std::vector containing the
// state and then initialise from the std::vector.
// The constructor checks that the state vector is
// a power of two. If not, an exception is thrown.
std::vector<complex<double>> state{ {0,0}, {1,0}, {0,0}, {0,0} };
Qubits<Type::Default, double> q2(state);

// You can construct a new Qubits object by
// copying the state of an already existing
// object like this
Qubits<Type::Default, double> q3(q2);

// You can also assign one qubits object to
// another, provided they contain the same
// number of qubits. An exception is thrown
// if the number of qubits is different.
q3 = q1;

// All the above comments apply to Qubits
// object with different precision, but
// you cannot mix different precisions
Qubits<Type::Default, float> q4(3);
Qubits<Type::Default, float> q5(q4); // Copy q4 to q5

The full documentation for all the constructors is given below.

Qubits(unsigned nqubits)

Initialise the class with a fixed number of qubits

This function constructors an object with the specified number of qubits. The simulator is initialised in the all zero state.

Parameters

nqubits – The number of qubits to simulate

Qubits(const std::vector<complex<Fp>> &state)

Initialise the class from a pre-prepared state vector

This function constructs an object with the given initial state vector. The state vector must have a length which is a power of two, otherwise the function will throw std::logic_error.

Parameters

state – A vector containing the initial state for the object

Qubits(const Qubits&) = default

Copy constructor

You can make copies of this object by constructing from an object of the same type.

void operator=(const Qubits &old)

Copy-assignment operator

You can assign one Qubits object to another, provided that they both represent the same number of qubits. In this case, this operation copies the state vector of one object to the other. If the number of qubits are not equal, this function throws std::logic_error.

Quantum Gates

The Qubits class implements a selection of one- and two- qubit gates, which are described below. The gates can be called on an instance of the Qubits object as in the following example:

// Make a Qubits object with 3 qubits using double precision
Qubits<Type::Default, double> q(3);

q.pauliX(0); // Perform pauliX on the zeroth qubit
q.rotateX(1, 0.5); // Rotate qubit one about the X axis by 0.5 rad
void rotateX(unsigned targ, Fp angle)

Rotate around the x-axis of the Bloch sphere \( e^{-i\theta X/2} \).

This single qubit gate applies the following 2x2 matrix to each pair of \( |0\rangle \) and \( |1\rangle \) amplitudes for angle \( \theta \):

\[\begin{split} R_x = \begin{pmatrix} \cos(\theta/2) & -i\sin(\theta/2) \\ -i\sin(\theta/2) & \cos(\theta/2) \\ \end{pmatrix} \end{split}\]

Parameters
  • targ – The target qubit.

  • angle – The angle to rotate the qubit by.

void rotateY(unsigned targ, Fp angle)

Rotate around the y-axis of the Bloch sphere \( e^{-i\theta Y/2} \).

This single qubit gate applies the following 2x2 matrix to each pair of \( |0\rangle \) and \( |1\rangle \) amplitudes for angle \( \theta \):

\[\begin{split} R_y = \begin{pmatrix} \cos(\theta/2) & -\sin(\theta/2) \\ \sin(\theta/2) & \cos(\theta/2) \\ \end{pmatrix} \end{split}\]

Parameters
  • targ – The target qubit.

  • angle – The angle to rotate the qubit by.

void rotateZ(unsigned targ, Fp angle)

Rotate around the z-axis of the Bloch sphere \( e^{-i\theta Z/2} \).

This single qubit gate applies the following 2x2 matrix to each pair of \( |0\rangle \) and \( |1\rangle \) amplitudes for angle \( \theta \):

\[\begin{split} R_z = \begin{pmatrix} e^{-i\theta/2} & 0 \\ 0 & e^{i\theta/2} \\ \end{pmatrix} \end{split}\]

Parameters
  • targ – The target qubit.

  • angle – The angle to rotate the qubit by.

void pauliX(unsigned targ)

Apply the Pauli X gate to qubit number targ.

\[\begin{split} X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \\ \end{pmatrix} \end{split}\]

Parameters

targ – The target qubit.

void pauliY(unsigned targ)

Apply the Pauli Y gate to qubit number targ.

\[\begin{split} Y = \begin{pmatrix} 0 & -i \\ i & 0 \\ \end{pmatrix} \end{split}\]

Parameters

targ – The target qubit.

void pauliZ(unsigned targ)

Apply the Pauli Z gate to qubit number targ.

\[\begin{split} Z = \begin{pmatrix} 1 & 0 \\ 0 & -1 \\ \end{pmatrix} \end{split}\]

Parameters

targ – The target qubit.

void hadamard(unsigned targ)

Apply the Hadamard gate to qubit number targ.

\[\begin{split} H = \frac{1}{\sqrt{2}}\begin{pmatrix} 1 & 1 \\ 1 & -1 \\ \end{pmatrix} \end{split}\]

Parameters

targ – The target qubit.

void phase(unsigned targ, Fp angle)

Apply a phase shift to qubit number targ.

\[\begin{split} R_\theta = \begin{pmatrix} 1 & 0 \\ 0 & e^{i\theta} \\ \end{pmatrix} \end{split}\]

Parameters
  • targ – The target qubit.

  • angle – The angle to phase shift the qubit by.

void controlNot(unsigned ctrl, unsigned targ)

Perform the controlled Not (CNOT) gate on two qubits.

Controlled on the first qubit, the matrix is:

\[\begin{split} CNOT = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{pmatrix} \end{split}\]

Parameters
  • ctrl – The control qubit, X is applied on the target qubit if this qubit is \( |1\rangle \).

  • targ – The target qubit.

void controlY(unsigned ctrl, unsigned targ)

Perform a controlled Y gate on two qubits.

\[\begin{split} CY = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & -i \\ 0 & 0 & i & 0 \end{pmatrix} \end{split}\]

Parameters
  • ctrl – The control qubit, Y is applied on the target qubit if this qubit is \( |1\rangle \).

  • targ – The target qubit.

void controlZ(unsigned ctrl, unsigned targ)

Perform a controlled Z gate on two qubits.

\[\begin{split} CZ = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & -1 \end{pmatrix} \end{split}\]

Parameters
  • ctrl – The control qubit, Z is applied on the target qubit if this qubit is \( |1\rangle \).

  • targ – The target qubit.

void controlRotateX(unsigned ctrl, unsigned targ, Fp angle)

Perform a controlled Rx gate on two qubits.

\[\begin{split} CRx = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & \cos(\theta/2) & -i\sin(\theta/2) \\ 0 & 0 & -i\sin(\theta/2) & \cos(\theta/2) \end{pmatrix} \end{split}\]

Parameters
  • ctrl – The control qubit, Rx is applied on the target qubit if this qubit is \( |1\rangle \).

  • targ – The target qubit.

  • angle – The angle to rotate the qubit by.

void controlRotateY(unsigned ctrl, unsigned targ, Fp angle)

Perform a controlled Ry gate on two qubits.

\[\begin{split} CRy = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & \cos(\theta/2) & -\sin(\theta/2) \\ 0 & 0 & \sin(\theta/2) & \cos(\theta/2) \end{pmatrix} \end{split}\]

Parameters
  • ctrl – The control qubit, Ry is applied on the target qubit if this qubit is \( |1\rangle \).

  • targ – The target qubit.

  • angle – The angle to rotate the qubit by.

void controlRotateZ(unsigned ctrl, unsigned targ, Fp angle)

Perform a controlled Rz gate on two qubits.

\[\begin{split} CRz = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & e^{-i\theta/2} & 0 \\ 0 & 0 & 0 & e^{i\theta/2} \end{pmatrix} \end{split}\]

Parameters
  • ctrl – The control qubit, Rz is applied on the target qubit if this qubit is \( |1\rangle \).

  • targ – The target qubit.

  • angle – The angle to rotate the qubit by.

void controlPhase(unsigned ctrl, unsigned targ, Fp angle)

Perform a controlled phase shift on two qubits.

A phase is added if both qubits are in the \( |1\rangle \) state.

\[\begin{split} CR_\theta = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & e^{i\theta} \end{pmatrix} \end{split}\]

Parameters
  • ctrl – The control qubit, phase shift is applied on the target qubit if this qubit is \( |1\rangle \).

  • targ – The target qubit.

  • angle – The angle to phase shift the target qubit by.

void controlHadamard(unsigned ctrl, unsigned targ)

Perform a controlled H gate on two qubits.

\[\begin{split} CH = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1/\sqrt{2} & 1/\sqrt{2} \\ 0 & 0 & 1/\sqrt{2} & -1/\sqrt{2} \end{pmatrix} \end{split}\]

Parameters
  • ctrl – The control qubit, H is applied on the target qubit if this qubit is \( |1\rangle \).

  • targ – The target qubit.

void swap(unsigned q1, unsigned q2)

Perform a swap gate on two qubits.

\[\begin{split} SWAP = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} \end{split}\]

Parameters
  • q1 – The first qubit to swap.

  • q2 – The second qubit to swap.

void fswap(unsigned q1, unsigned q2)

Perform a fermionic swap gate on two qubits.

\[\begin{split} FSWAP = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & -1 \end{pmatrix} \end{split}\]

Parameters
  • q1 – The first qubit to fswap.

  • q2 – The second qubit to fswap.

void npRotateX(unsigned q1, unsigned q2, Fp angle)

Perform an X rotation on the \( \{|01\rangle,|10\rangle\}\) subspace. This is equivalent to applying \( e^{-i\theta (XX+YY)/2} \).

\[\begin{split} npR_x(\theta) = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos(\theta/2) & -i\sin(\theta/2) & 0 \\ 0 & -i\sin(\theta/2) & \cos(\theta/2) & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} \end{split}\]

Parameters
  • q1 – The first qubit.

  • q2 – The second qubit.

  • angle – Angle to rotate by.

void npRotateY(unsigned q1, unsigned q2, Fp angle)

Perform an Y rotation on the \( \{|01\rangle,|10\rangle\}\) subspace. This is equivalent to applying \( e^{-i\theta (YX-XY)/2} \).

\[\begin{split} npR_y(\theta) = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos(\theta/2) & -\sin(\theta/2) & 0 \\ 0 & \sin(\theta/2) & \cos(\theta/2) & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} \end{split}\]

Parameters
  • q1 – The first qubit.

  • q2 – The second qubit.

  • angle – Angle to rotate by.

void npHadamard(unsigned q1, unsigned q2)

Perform a Hadamard gate on the \( \{|01\rangle,|10\rangle\}\) subspace.

\[\begin{split} npH = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1/\sqrt{2} & 1/\sqrt{2} & 0 \\ 0 & 1/\sqrt{2} & -1/\sqrt{2} & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} \end{split}\]

Parameters
  • q1 – The first qubit.

  • q2 – The second qubit.

Measurement

After performing a series of gates, you can measure or otherwise analyse the state of the qubits using the functions below.

int measure(unsigned targ)

Measure a qubit and collapse the state to its outcome.

This is not a reversible operation unlike applying quantum gates.

A random number is generated to determine whether the given qubit targ is measured to be 0 or 1. The state vector is then collapsed to that outcome by zeroing out all the amplitudes that do not correspond to the generated outcome. Note that the state vector does not change size.

Parameters

targ – The qubit to measure.

Returns

The value of the measured qubit (0 or 1).

std::size_t measureAll()

Measure all of the qubits at once and collapse to the resulting computational basis state.

Measuring all of the qubits at once is the same as measuring them one by one.

Returns

The result of the measurement.

Fp prob(unsigned targ, unsigned outcome) const

Calculate the probability of qubit targ being measured in the given outcome (0 or 1).

Parameters
  • targ – The qubit to calculate the probability for.

  • outcome – The outcome (0 or 1) we are calculating the probability of.

Returns

The probability of the qubit being measured in the given outcome.

Fp postselect(unsigned targ, unsigned outcome)

Perform a post-selection measurement. The state is collapsed to the given outcome for the given qubit.

This is not a reversible operation unlike applying quantum gates. The state vector is collapsed by zeroing out all the amplitudes that do not correspond to the given outcome. Note that the state vector does not change size.

Parameters
  • targ – The qubit to measure.

  • outcome – The outcome (0 or 1) to post select on.

Returns

The probability of measuring qubit targ in the given outcome.

std::vector<std::size_t> sample(unsigned targ, std::size_t nsamples)

Sample measurement outcome for one qubit multiple times.

Parameters
  • targ – The qubit to measure.

  • nsamples – The number of samples to draw.

Returns

A vector containing all the measurement results.

std::map<std::size_t, std::size_t> sampleAll(std::size_t nsamples)

Sample measurement outcome for all of the qubits at once multiple times.

Measuring all of the qubits at once is the same as measuring them one by one. This function implements a very efficient way of measuring all the qubits multiple times and should be used instead of the measure function where possible. Note that this function does not modify the state vector.

Parameters

nsamples – The number of measurements to perform.

Returns

A map containing all of the measurement results, mapping outcomes to the number of times they happen.

std::map<std::size_t, std::size_t> sampleAll2(std::size_t nsamples)

Sample measurement outcome for all of the qubits at once multiple times.

Testing implementing sampling using the method in qsim.

Parameters

nmeas – The number of measurements to perform.

Returns

A map containing all of the measurement results, mapping outcomes to the number of times they happen.

Utilities

These functions can be used to get information about the Qubits object.

unsigned getNumQubits() const

Return the number of qubits.

void reset()

Reset to the all-zero state.

void print(std::ostream &os = std::cout) const

Print the state vector.

std::vector<complex<Fp>> getState() const

Get the state vector associated to the qubits.

Todo:

Return std::vector<std::complex<Fp>> instead

void setState(const std::vector<complex<Fp>> &state)

Set the state vector (i.e. re-initialise the state vector)

Todo:

Take std::vector<std::complex<Fp>> instead

void setBasisState(std::size_t index)

Set the state vector to a computational basis state.

OpenMP-based simulator

This simulator makes use of parallelisation using the OpenMP library.

Warning

doxygenclass: Cannot find class “Qubits< Type::Omp, Fp >” in doxygen xml output for project “QSL” from directory: doxy/xml/

Constructing the object

Warning

doxygengroup: Cannot find group “qubits_omp_constructors” in doxygen xml output for project “QSL” from directory: doxy/xml/

Quantum gates and Measurement

The member functions for applying gates and performing measurement use the same syntax as those of the default simulator object.