diff --git a/thesis/chapters/implementation.tex b/thesis/chapters/implementation.tex index dba21e3..20fa0bf 100644 --- a/thesis/chapters/implementation.tex +++ b/thesis/chapters/implementation.tex @@ -1,23 +1,23 @@ % vim: ft=tex \section{Implementation} -This chapter discusses how the concepts introduced before are implemented -into a simulator. Futher the infrastructure around the simulation and some -tools are explained. +This chapter discusses how the concepts introduced before are implemented into +a simulator. Futher the infrastructure around the simulation and some tools are +explained. -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} +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} \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: +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.} @@ -26,34 +26,43 @@ has some useful features when it comes to computations: \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. +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. +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. +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. +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}: +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}} @@ -70,25 +79,28 @@ A basic set of gates is implemented in PyQCS: \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. +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} -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: +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: +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] @@ -105,25 +117,25 @@ 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: +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 [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 +In [2]: bell_state Out[2]: (0.7071067811865477+0j)*|0b0> + (0.7071067811865477+0j)*|0b11> \end{lstlisting} %} @@ -138,9 +150,9 @@ In [1]: from pyqcs import CX, CZ, H, R, Z, X ...: ...: 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) + ...: state = (H(0) | circuit_CX) * State.new_zero_state(5) -In [2]: state +In [2]: state Out[2]: (0.7071067811865476+0j)*|0b0> + (0.7071067811865476+0j)*|0b11111> \end{lstlisting} @@ -150,3 +162,53 @@ Out[2]: (0.7071067811865476+0j)*|0b0> + (0.7071067811865476+0j)*|0b11111> \subsubsection{Graphical States} +For the graphical state $(V, E, O)$ the list of vertices $V$ can be stored implicitly +by demanding $V = \{0, ..., n - 1\}$. This leaves two components that have to be stored: +The edges $E$ and the vertex operators $O$. Storing the vertex operators is done using +a \lstinline{uint8_t} array. Every local Clifford operator is associated from $0$ to $24$, +their order is + +\begin{equation} +\begin{aligned} + &\left(\begin{matrix}\frac{\sqrt{2}}{2} & \frac{\sqrt{2}}{2}\\\frac{\sqrt{2}}{2} & - \frac{\sqrt{2}}{2}\end{matrix}\right), + \left(\begin{matrix}1 & 0\\0 & i\end{matrix}\right), + \left(\begin{matrix}1 & 0\\0 & 1\end{matrix}\right), + \left(\begin{matrix}\frac{\sqrt{2}}{2} & \frac{\sqrt{2}}{2}\\\frac{\sqrt{2} i}{2} & - \frac{\sqrt{2} i}{2}\end{matrix}\right), \\ + &\left(\begin{matrix}\frac{\sqrt{2}}{2} & \frac{\sqrt{2} i}{2}\\\frac{\sqrt{2}}{2} & - \frac{\sqrt{2} i}{2}\end{matrix}\right), + \left(\begin{matrix}1 & 0\\0 & -1\end{matrix}\right), + \left(\begin{matrix}\frac{\sqrt{2}}{2} & \frac{\sqrt{2} i}{2}\\\frac{\sqrt{2} i}{2} & \frac{\sqrt{2}}{2}\end{matrix}\right), + \left(\begin{matrix}\frac{\sqrt{2}}{2} & - \frac{\sqrt{2}}{2}\\\frac{\sqrt{2}}{2} & \frac{\sqrt{2}}{2}\end{matrix}\right), \\ + &\left(\begin{matrix}1 & 0\\0 & - i\end{matrix}\right), + \left(\begin{matrix}\frac{\sqrt{2}}{2} & - \frac{\sqrt{2}}{2}\\\frac{\sqrt{2} i}{2} & \frac{\sqrt{2} i}{2}\end{matrix}\right), + \left(\begin{matrix}\frac{\sqrt{2}}{2} & - \frac{\sqrt{2} i}{2}\\\frac{\sqrt{2}}{2} & \frac{\sqrt{2} i}{2}\end{matrix}\right), + \left(\begin{matrix}\frac{\sqrt{2}}{2} & - \frac{\sqrt{2} i}{2}\\\frac{\sqrt{2} i}{2} & - \frac{\sqrt{2}}{2}\end{matrix}\right), \\ + &\left(\begin{matrix}\frac{1}{2} + \frac{i}{2} & \frac{1}{2} - \frac{i}{2}\\\frac{1}{2} - \frac{i}{2} & \frac{1}{2} + \frac{i}{2}\end{matrix}\right), + \left(\begin{matrix}\frac{\sqrt{2}}{2} & \frac{\sqrt{2}}{2}\\- \frac{\sqrt{2}}{2} & \frac{\sqrt{2}}{2}\end{matrix}\right), + \left(\begin{matrix}0 & 1\\1 & 0\end{matrix}\right), + \left(\begin{matrix}\frac{\sqrt{2}}{2} & \frac{\sqrt{2}}{2}\\- \frac{\sqrt{2} i}{2} & \frac{\sqrt{2} i}{2}\end{matrix}\right), \\ + &\left(\begin{matrix}0 & 1\\i & 0\end{matrix}\right), + \left(\begin{matrix}\frac{1}{2} - \frac{i}{2} & \frac{1}{2} + \frac{i}{2}\\- \frac{1}{2} + \frac{i}{2} & \frac{1}{2} + \frac{i}{2}\end{matrix}\right), + \left(\begin{matrix}0 & i\\1 & 0\end{matrix}\right), + \left(\begin{matrix}\frac{\sqrt{2}}{2} & \frac{\sqrt{2} i}{2}\\- \frac{\sqrt{2} i}{2} & - \frac{\sqrt{2}}{2}\end{matrix}\right), \\ + &\left(\begin{matrix}\frac{1}{2} - \frac{i}{2} & - \frac{1}{2} + \frac{i}{2}\\- \frac{1}{2} + \frac{i}{2} & - \frac{1}{2} + \frac{i}{2}\end{matrix}\right), + \left(\begin{matrix}0 & -1\\1 & 0\end{matrix}\right), + \left(\begin{matrix}\frac{\sqrt{2}}{2} & - \frac{\sqrt{2}}{2}\\- \frac{\sqrt{2} i}{2} & - \frac{\sqrt{2} i}{2}\end{matrix}\right), + \left(\begin{matrix}\frac{1}{2} - \frac{i}{2} & \frac{i \left(-1 + i\right)}{2}\\- \frac{1}{2} + \frac{i}{2} & \frac{i \left(-1 + i\right)}{2}\end{matrix}\right) +\end{aligned} +\end{equation} + +The edges are stored in an adjacency matrix + +\begin{equation} + A = (a_{i,j})_{i,j = 0, ..., n-1} +\end{equation} + +\begin{equation} +\begin{aligned} + a_{i,j} = \left\{ \begin{array}{c} 1 \mbox{, if } \{i,j\} \in E\\ + 0 \mbox{, if} \{i,j\} \notin E \end{array}\right. + . +\end{aligned} +\end{equation} + +Because it is