2020-03-02 11:28:40 +00:00
|
|
|
% vim: ft=tex
|
2020-02-25 10:20:57 +00:00
|
|
|
\section{Implementation}
|
2020-03-02 11:28:40 +00:00
|
|
|
|
|
|
|
This chapter discusses how the concepts introduced before are implemented
|
|
|
|
into a simulator. Futher the infrastructure around the simulation and some
|
|
|
|
tools are explained.
|
|
|
|
|
2020-03-07 13:52:13 +00:00
|
|
|
The implementation is written as a \lstinline{python3} module. This allows
|
|
|
|
users to quickly construct circuit, apply them to a state and measure amplitudes.
|
|
|
|
Full access to the state (including intermediate) state has been priorized over
|
|
|
|
execution speed. To keep the simulation speed as high as possible under these
|
|
|
|
constraints some parts are implemented in \lstinline{C}
|
|
|
|
|
2020-03-02 11:28:40 +00:00
|
|
|
\subsection{Dense State Vector Simulation}
|
|
|
|
|
|
|
|
\subsubsection{Representation of Dense State Vectors}
|
|
|
|
|
|
|
|
Recalling \eqref{eq:ci} any $n$-qbit state can be represented as a
|
|
|
|
$2^n$ component vector in the integer state basis. This representation
|
|
|
|
has some useful features when it comes to computations:
|
|
|
|
|
|
|
|
\begin{itemize}
|
|
|
|
\item{The projection on the integer states is trivial.}
|
|
|
|
\item{For any qbit $j$ and $0 \le i \le 2^n-1$ the coefficient $c_i$ is part of the $\ket{1}_j$ amplitude iff
|
|
|
|
$i \& (1 << j)$ and part of the $\ket{0}_j$ amplitude otherwise.}
|
|
|
|
\item{For a qbit $j$ the coefficients $c_i$ and $c_{i \hat{} (1 << j)}$ are the conjugated coefficients.}
|
|
|
|
\end{itemize}
|
|
|
|
|
|
|
|
Where $\hat{}$ is the binary XOR, $\&$ the binary AND and $<<$ the binary leftshift operator.
|
|
|
|
|
|
|
|
While implementing the dense state vectors two key points were allowing a simple and readable
|
|
|
|
way to use them and simple access to the states by users that want more information than an
|
|
|
|
abstracted view could allow. To meet both requirements the states are implemented as Python objects
|
|
|
|
providing abstract features such as normalization checking, checking for sufficient qbit number when applying
|
|
|
|
a circuit, computing overlaps with other states, a stringify method and stored measurement results.
|
|
|
|
To store the measurement results a NumPy \lstinline{int8} array \cite{numpy_array} is used; this is called
|
|
|
|
the classical state.
|
|
|
|
The Python states also have a NumPy \lstinline{cdouble} array that stores the quantum mechanical state.
|
|
|
|
Using NumPy arrays has the advantage that access to the data is simple and safe while operations
|
|
|
|
on the states can be implemented in \lstinline{C} \cite{numpy_ufunc} providing a considerable speedup.
|
|
|
|
|
|
|
|
This quantum mechanical state is the component vector in integer basis therefore it has $2^n$ components.
|
|
|
|
Storing those components is acceptable in a range from $1$ to $30$ qbits; above this range the state requires
|
|
|
|
space in the order of $1 \mbox{ GiB}$ which is in the range of usual RAM sizes for personal computers. For higher
|
|
|
|
qbit numbers moving to high performance computers and other simulators is necessary.
|
|
|
|
|
|
|
|
\subsubsection{Gates}
|
|
|
|
|
|
|
|
Gates on dense state vectors are implemented as NumPy Universal Functions (ufuncs) \cite{numpy_ufunc} mapping a classical
|
|
|
|
and a quantum state to a new classical state, a new quantum state and a $64 \mbox{ bit}$ integer indicating what qbits have
|
|
|
|
been measured. Using ufuncs has the great advantage that managing memory is done by NumPy and an application programmer
|
|
|
|
just has to implement the logic of the function. Because ufuncs are written in \lstinline{C} they provide a considerable
|
|
|
|
speedup compared to an implementation in Python.
|
|
|
|
|
|
|
|
The logic of gates is usually easy to implement using the integer basis. The example below implements the Hadamard gate
|
|
|
|
\ref{ref:singleqbitgates}:
|
|
|
|
|
|
|
|
\adjustbox{max width=\textwidth}{\lstinputlisting[language=C, firstline=153, lastline=178]{../pyqcs/src/pyqcs/gates/implementations/basic_gates.c}}
|
|
|
|
|
|
|
|
A basic set of gates is implemented in PyQCS:
|
|
|
|
|
|
|
|
\begin{itemize}
|
|
|
|
\item{Hadamard $H$ gate.}
|
|
|
|
\item{Pauli $X$ or \textit{NOT} gate.}
|
|
|
|
\item{Pauli $Z$ gate.}
|
|
|
|
\item{The $S$ phase gate.}
|
|
|
|
\item{$Z$ rotation $R_\phi$ gate.}
|
|
|
|
\item{Controlled $X$ gate: $CX$.}
|
|
|
|
\item{Controlled $Z$ gate: $CZ$.}
|
|
|
|
\item{The measurement "gate" $M$.}
|
|
|
|
\end{itemize}
|
|
|
|
|
|
|
|
To allow the implementation of possible hardware related gates the class \lstinline{GenericGate} takes
|
|
|
|
a unitary $2\times2$ matrix as a NumPy \lstinline{cdouble} array and builds a gate from it.
|
|
|
|
|
|
|
|
\subsubsection{Circuits}
|
|
|
|
|
2020-03-07 13:52:13 +00:00
|
|
|
As mentioned in \ref{ref:quantum_circuits} quantum circuits are central in quantum programming.
|
|
|
|
In the implementation great care was taken to make writing circuits as convenient and readable as
|
|
|
|
possible. Users will almost never access the actual gates that perform the operation on a state;
|
|
|
|
instead they will handle circuits.\\
|
|
|
|
Circuits can be applied to a state by multiplying them from the left on a state object:
|
|
|
|
|
|
|
|
\begin{lstlisting}[language=Python]
|
|
|
|
new_state = circuit * state
|
|
|
|
\end{lstlisting}
|
|
|
|
|
|
|
|
|
|
|
|
The elementary gates such as $H, R_\phi, CX$ are implemented as single gate circuits and can be constructing using
|
|
|
|
the built-in generators. The generators take the act-qbit as first argument, parameters such as the control qbit
|
|
|
|
or an angle as second argument:
|
|
|
|
|
|
|
|
%\adjustbox{max width=\textwidth}{
|
|
|
|
\begin{lstlisting}[language=Python]
|
|
|
|
In [1]: from pyqcs import CX, CZ, H, R, Z, X
|
|
|
|
...: from pyqcs import State
|
|
|
|
...:
|
|
|
|
...: state = State.new_zero_state(2)
|
|
|
|
...: intermediate_state = H(0) * state
|
|
|
|
...:
|
|
|
|
...: bell_state = CX(1, 0) * intermediate_state
|
|
|
|
|
|
|
|
In [2]: bell_state
|
|
|
|
Out[2]: (0.7071067811865476+0j)*|0b0> + (0.7071067811865476+0j)*|0b11>
|
|
|
|
\end{lstlisting}
|
|
|
|
%}
|
|
|
|
|
|
|
|
Large circuits can be constructed using the binary OR operator \lstinline{|} in an analogy to the
|
|
|
|
pipeline operator on many *NIX systems. As usual circuits are read from left to right similar to pipelines on
|
|
|
|
*NIX systems:
|
|
|
|
|
|
|
|
|
|
|
|
%\adjustbox{max width=\textwidth}{
|
|
|
|
\begin{lstlisting}[language=Python]
|
|
|
|
In [1]: from pyqcs import CX, CZ, H, R, Z, X
|
|
|
|
...: from pyqcs import State
|
|
|
|
...:
|
|
|
|
...: state = State.new_zero_state(2)
|
|
|
|
...:
|
|
|
|
...: # This is the same as
|
|
|
|
...: # circuit = H(0) | CX(1, 0)
|
|
|
|
...: circuit = H(0) | H(1) | CZ(1, 0) | H(1)
|
|
|
|
...:
|
|
|
|
...: bell_state = circuit * state
|
|
|
|
|
|
|
|
In [2]: bell_state
|
|
|
|
Out[2]: (0.7071067811865477+0j)*|0b0> + (0.7071067811865477+0j)*|0b11>
|
|
|
|
\end{lstlisting}
|
|
|
|
%}
|
|
|
|
|
|
|
|
A quick way to generate circuits programatically is to use the \lstinline{list_to_circuit}
|
|
|
|
function:
|
|
|
|
|
|
|
|
%\adjustbox{max width=\textwidth}{
|
|
|
|
\begin{lstlisting}[language=Python]
|
|
|
|
In [1]: from pyqcs import CX, CZ, H, R, Z, X
|
|
|
|
...: from pyqcs import State, list_to_circuit
|
|
|
|
...:
|
|
|
|
...: circuit_CX = list_to_circuit([CX(i, i-1) for i in range(1, 5)])
|
|
|
|
...:
|
|
|
|
...: state = (H(0) | circuit_CX) * State.new_zero_state(5)
|
|
|
|
|
|
|
|
In [2]: state
|
|
|
|
Out[2]: (0.7071067811865476+0j)*|0b0> + (0.7071067811865476+0j)*|0b11111>
|
|
|
|
|
|
|
|
\end{lstlisting}
|
|
|
|
%}
|
|
|
|
|
|
|
|
\subsection{Graphical State Simulation}
|
|
|
|
|
|
|
|
\subsubsection{Graphical States}
|
|
|
|
|