did some more work
This commit is contained in:
parent
b81938bdb7
commit
78ec3aa4c9
|
@ -10,10 +10,17 @@ chapters=chapters/introduction.tex \
|
|||
chapters/appendix.tex
|
||||
|
||||
cover=cover.png
|
||||
graphics=graphics/graph_high_linear_regime.png \
|
||||
graphics/graph_high_linear_regime_cut.png \
|
||||
graphics/graph_intermediate_regime.png \
|
||||
graphics/graph_intermediate_regime_cut.png \
|
||||
graphics/graph_low_linear_regime.png
|
||||
|
||||
other=extra_benchmark/benchmark.py
|
||||
|
||||
all: main.pdf
|
||||
|
||||
main.pdf: main.tex main.bib $(cover) $(chapters)
|
||||
main.pdf: main.tex main.bib $(cover) $(chapters) $(graphics) $(other)
|
||||
$(latex) main
|
||||
$(bibtex) main
|
||||
$(latex) main
|
||||
|
|
|
@ -66,3 +66,17 @@ code is converted to an image using
|
|||
\lstinputlisting[title={Code used to Generate the Example Graphs}, language=Python, breaklines=true]{../performance/regimes/graph_intermediate_regime.py}
|
||||
|
||||
|
||||
\subsection{Code to Benchmark \lstinline{ufunc} Gates against Python}
|
||||
\label{ref:benchmark_ufunc_py}
|
||||
|
||||
It has been mentioned several times that the implementation using
|
||||
\lstinline{ufuncs} as gates is faster than using a \lstinline{python}
|
||||
implementation. To support this statement a simple benchmark can be used. The
|
||||
relatively simple Pauli $X$ is used, more complicated gates like $CX$ or $H$
|
||||
have worse performance when implemented in \lstinline{python}. The performance
|
||||
improvement when using the \lstinline{ufunc} is around $1.7$ in this tested
|
||||
case. One must however note that the tested \lstinline{python} code is not
|
||||
realistic and in a possible applications there would be a significant overhead.
|
||||
|
||||
\lstinputlisting[title={Code to Benchmark \lstinline{ufunc} Gates against Python}, language=Python, breaklines=True]{extra_benchmark/benchmark.py}
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ a group preserving this tensor product property.
|
|||
%}}}
|
||||
|
||||
The stabilizer formalism as introduced in \ref{ref:stab_states} has since been
|
||||
generalized to normalizers of a finite abelian group over the Hilbert space
|
||||
generalized to normalizers of a finite Abelian group over the Hilbert space
|
||||
\cite{bermejovega_lin_vdnest2015}\cite{bermejovega_vdnest2018}\cite{vandennest2019}\cite{vandennest2018}.
|
||||
This allows to simulate more classes of circuits efficiently on classical computers
|
||||
including the Quantum Fourier Transforms which is often believed to be
|
||||
|
|
|
@ -41,7 +41,7 @@ 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.
|
||||
speedup \ref{ref:benchmark_ufunc_py}.
|
||||
|
||||
This quantum mechanical state is the component vector in integer basis
|
||||
therefore it has $2^n$ components. Storing those components is acceptable in
|
||||
|
@ -59,7 +59,7 @@ 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.
|
||||
in Python \ref{ref:benchmark_ufunc_py}.
|
||||
|
||||
The logic of gates is usually easy to implement using the integer basis. The
|
||||
example below implements the Hadamard gate \ref{ref:singleqbitgates}:
|
||||
|
@ -166,9 +166,9 @@ Out[2]: (0.7071067811865476+0j)*|0b0>
|
|||
|
||||
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
|
||||
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 with an integer ranging from $0$ to $24$, their order is
|
||||
|
||||
\begin{equation}
|
||||
\begin{aligned}
|
||||
|
@ -235,7 +235,7 @@ three classes of operations.
|
|||
|
||||
\begin{description}
|
||||
\item[\hspace{-1em}]{\lstinline{RawGraphState.apply_C_L}\\
|
||||
This method implements local clifford gates. It takes the qbit index
|
||||
This method implements local Clifford gates. It takes the qbit index
|
||||
and the index of the local Clifford operator (ranging form $0$ to $23$).}
|
||||
\item[\hspace{-1em}]{\lstinline{RawGraphState.apply_CZ}\\
|
||||
Applies the $CZ$ gate to the state. The first argument is the
|
||||
|
@ -258,7 +258,7 @@ vector state.
|
|||
|
||||
\subsubsection{Pure C Implementation}
|
||||
|
||||
Because python tends to be rather slow and might not run on any architecture
|
||||
Because python tends to be rather slow \cite{benchmarkgame} and might not run on any architecture
|
||||
a pure \lstinline{C} implementation of the graphical simulator is also provided.
|
||||
It should be seen as a reference implementation that can be extended to the needs
|
||||
of the user.
|
||||
|
|
|
@ -12,7 +12,7 @@ important class of quantum error correction strategies are stabilizer codes
|
|||
faster than general quantum circuits
|
||||
\cite{gottesman_aaronson2008}\cite{CHP}\cite{andersbriegel2005}.
|
||||
|
||||
One particularely efficient way to simulate stabilizer states is the graphical
|
||||
One particularly efficient way to simulate stabilizer states is the graphical
|
||||
representation \cite{andersbriegel2005} that has been studied extensively in
|
||||
the context of both quantum error correction and quantum information theory
|
||||
\cite{schlingenmann2001}\cite{dahlberg_ea2019}\cite{vandennest_ea2004}\cite{hein_eisert_briegel2008}.
|
||||
|
@ -20,7 +20,7 @@ This paper describes the development of a quantum computing simulator
|
|||
using both the usual dense state vector representation for a general state
|
||||
and a graphical representation for stabilizer states. After giving some introduction
|
||||
to quantum computing some basic properties of stabilizer states and their
|
||||
dynamics are eludicated. Using this the graphical representation is introduced
|
||||
dynamics are elucidated. Using this the graphical representation is introduced
|
||||
and some operations on the graphical states are explained. Following is
|
||||
a chapter describing the implementation of these techniques and some performance
|
||||
analysis.
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
% vim: ft=tex
|
||||
\section{Quantum Computing}
|
||||
|
||||
This chapter gives an introduction to binary quantum computing
|
||||
and some basic properties of quantum systems such as measurement
|
||||
and time evolution. It is based on \cite{nielsen_chuang_2010}
|
||||
but introduces a notation that will often be different.
|
||||
|
||||
\subsection{Qbits and Gates}
|
||||
\subsubsection{Single Qbits}
|
||||
|
||||
\begin{definition}
|
||||
A qbit is a two level quantum mechanical system \cite{nielsen_chuang_2010}, i.e. it has the eigenbasis
|
||||
A qbit is a two-level quantum mechanical system \cite{nielsen_chuang_2010}, i.e. it has the eigenbasis
|
||||
$ \{\ket{\uparrow} \equiv \ket{1}, \ket{\downarrow} \equiv \ket{0}\} $
|
||||
with $\braket{\uparrow}{\downarrow} = 0$. In the following this basis will be called
|
||||
the $Z$ basis in analogy to the conventions used in spin systems ($\sigma_Z$). For some computations
|
||||
|
@ -17,7 +22,13 @@
|
|||
A gate acting on a qbit is a unitary operator $G \in U(2)$. One can show that
|
||||
$\forall G \in U(2)$ $G$ can be written as a product of unitary generator matrices
|
||||
\cite[Chapter 4.2]{nielsen_chuang_2010}\cite[Chapter 4.3]{kaye_ea2007}\cite[Chapter 2]{marquezino_ea_2019};
|
||||
common choices for the generators are $ X, H, R_{\phi}$ or $Z, H, R_{\phi}$ with
|
||||
common choices for the generators are $ X, H, R_{\phi}$ or $Z, H, R_{\phi}$
|
||||
\footnote{Note that the notation in the sources is different. Instead of $R_\phi$ the
|
||||
$R_Z(\alpha) = \exp(-\frac{i\alpha}{2}Z)$ and $R_Y(\alpha) = \exp(-\frac{i\alpha}{2}Y)$
|
||||
rotations are used. However $R_\phi = \exp(-\frac{i\phi}{2}) R_Z(-\phi)$ and
|
||||
$R_Y(\alpha) = \cos(\frac{\alpha}{2}) I - i\sin(\frac{\alpha}{2}) Y = S H R_Z H^\dagger S^\dagger$ \cite{nielsen_chuang_2010}.
|
||||
}
|
||||
with
|
||||
\label{ref:singleqbitgates}
|
||||
|
||||
\begin{equation}
|
||||
|
@ -96,7 +107,8 @@ with the normation condition
|
|||
The states $\ket{i}$ for $i = 0, ..., 2^{n}-1$ are called integer states. Note
|
||||
that integer states are eigenstates of the $Z$ operators.\\
|
||||
The computational basis is
|
||||
$\left\{\ket{i_0} \otimes ... \otimes \ket{i_{n-1}} \middle| i_0, ..., i_{n-1} = 0, 1\right\}$.
|
||||
$\left\{\ket{i_0} \otimes ... \otimes \ket{i_{n-1}} \middle| i_0, ..., i_{n-1} = 0, 1\right\}$
|
||||
\cite{nielsen_chuang_2010}.
|
||||
|
||||
\begin{definition}
|
||||
For a single-qbit gate $U$ and a qbit $j = 0, 1, ..., n - 1$
|
||||
|
@ -106,11 +118,11 @@ For a single-qbit gate $U$ and a qbit $j = 0, 1, ..., n - 1$
|
|||
\otimes U
|
||||
\otimes \left(\bigotimes\limits_{i = j + 1}^{n - 1} I \right)
|
||||
\end{equation}
|
||||
is acting on qbit $j$.
|
||||
is acting on qbit $j$ \cite{kaye_ea2007}.
|
||||
\end{definition}
|
||||
|
||||
% XXX
|
||||
\newpage
|
||||
%\newpage
|
||||
|
||||
\begin{definition}\label{def:CU}
|
||||
For two qbits $i,j = 0, 1, ..., n - 1$, $i \neq j$ and a gate $U_i$ acting on $i$
|
||||
|
@ -142,6 +154,8 @@ is acting on qbit $j$.
|
|||
CZ(i, j) = \ket{0}\bra{0}_j\otimes I_i + \ket{1}\bra{1}_j \otimes Z_i .
|
||||
\end{equation}
|
||||
|
||||
This follows the definition given in \cite{barenco_ea_1995}.
|
||||
|
||||
\end{definition}
|
||||
|
||||
In Definition \ref{def:CU} $i$ is called the act-qbit and $j$ the control-qbit. In words
|
||||
|
@ -169,7 +183,7 @@ The matrix representation of $CX$ and $CZ$ for two qbits is given by
|
|||
$$\ket{\phi_1} \otimes \ket{1}_j$$
|
||||
with probability $|\alpha|^2$ and
|
||||
$$\ket{\phi_0} \otimes \ket{0}_j$$
|
||||
with probability $|\beta|^2$. This is called collapse of the wave function.
|
||||
with probability $|\beta|^2$ \cite{nielsen_chuang_2010}. This is called collapse of the wave function.
|
||||
\end{postulate}
|
||||
|
||||
Measuring a qbit will also yield a classical result $0$ or $1$ with the respective probabilities.
|
||||
|
@ -182,7 +196,7 @@ $i$ $Z_i$ is measured. The $+1$ eigenvalue of $Z_i$ is $\ket{0}_i$, $\ket{1}_i$
|
|||
\end{corrolary}
|
||||
|
||||
\begin{proof}
|
||||
The measuerment is not injective: Measuring both
|
||||
The measurement is not injective: Measuring both
|
||||
$\ket{0}$ and $\frac{1}{\sqrt{2}}(\ket{0} + \ket{1})$ (can) map to $\ket{0}$.
|
||||
|
||||
Any unitary matrix $U$ has the inverse $U^\dagger \equiv U^{-1}$.
|
||||
|
@ -190,7 +204,7 @@ $i$ $Z_i$ is measured. The $+1$ eigenvalue of $Z_i$ is $\ket{0}_i$, $\ket{1}_i$
|
|||
|
||||
Because a measurement is not unitary it is not a gate in the sense of the definition above.
|
||||
In the following discussion the term \textit{measurement gate} will be used from time
|
||||
to time as a measurement can be treated similarely while doing numerics.
|
||||
to time as a measurement can be treated similarly while doing numerics.
|
||||
|
||||
|
||||
\subsection{Quantum Circuits}
|
||||
|
@ -237,7 +251,7 @@ Several qbits can be abbreviated by writing a slash on the qbit line:
|
|||
The great hope behind quantum computing is that it will speed up some
|
||||
computations exponentially using algorithms that utilize the laws of quantum
|
||||
mechanics. Current algorithms are based upon quantum search and quantum fourier
|
||||
transform \cite{nielsen_chuang_2010}. The latter is particular interesting for
|
||||
transform \cite{nielsen_chuang_2010}. The latter is particularly interesting for
|
||||
physical problems as it is a key component in the phase estimation algorithm
|
||||
that can be used to analyze the spectrum of the transfer matrix:
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ its generators.
|
|||
\subsubsection{Stabilizer States}
|
||||
\label{ref:stab_states}
|
||||
|
||||
One important basic property of quantum mechanics is that hermitian operators
|
||||
One important basic insight from quantum mechanics is that hermitian operators
|
||||
have real eigenvalues and eigenspaces which are associated with these
|
||||
eigenvalues. Finding these eigenvalues and eigenvectors is what one calls
|
||||
solving a quantum mechanical system. One of the most fundamental insights of
|
||||
|
@ -293,12 +293,12 @@ and $s=0$ are obtained with probability $\frac{1}{2}$ and after choosing a $j
|
|||
\end{proof}
|
||||
|
||||
\subsection{The VOP-free Graph States}
|
||||
\subsubsection{VOP-free Graph States}
|
||||
|
||||
This section will discuss the vertex operator (VOP)-free graph states. Why they
|
||||
are called vertex operator-free will be clear in the following section about
|
||||
graph states.
|
||||
|
||||
\subsubsection{VOP-free Graph States}
|
||||
|
||||
\begin{definition} \label{def:graph} The tuple $(V, E)$ is called a graph iff
|
||||
$V$ is a set of vertices with $|V| = n \in \mathbb{N}$ elements. In the
|
||||
following $V = \{0, ..., n-1\}$ will be used. $E$ is the set of edges $E
|
||||
|
@ -560,7 +560,12 @@ states that allows the representation of an arbitrary stabilizer state. The
|
|||
proof that indeed any state can be represented is purely constructive. As seen
|
||||
in Theorem \ref{thm:clifford_group_approx} any $c \in C_n$ can be constructed
|
||||
from $CZ$ and $C_L$. In the following discussion it will become clear that both
|
||||
$C_L$ and $CZ$ can be applied to a general graph state.
|
||||
$C_L$ and $CZ$ can be applied to a general graph state
|
||||
\footnote{
|
||||
One can show that any stabilizer state is local Clifford equivalent to
|
||||
a VOP-free graph state, i.e. only tensor products of $C_L$ matrices are
|
||||
required to map a stabilizer state to a VOP-free graph state \cite{andersbriegel2005}.
|
||||
}.
|
||||
|
||||
\subsubsection{Graph States and Vertex Operators}
|
||||
\label{ref:g_states_vops}
|
||||
|
@ -577,7 +582,16 @@ $C_L$ and $CZ$ can be applied to a general graph state.
|
|||
+1 \ket{G} = \left(\prod\limits_{j=1}^no_j\right) K_G^{(i)} \left(\prod\limits_{j=1}^no_j\right)^\dagger \ket{G}
|
||||
\end{equation}
|
||||
|
||||
$o_i$ are called the vertex operators of $\ket{G}$ \cite{andersbriegel2005}.
|
||||
$o_i$ are called the vertex operators of $\ket{G}$ \cite{andersbriegel2005}
|
||||
\footnote{
|
||||
The notation in \cite{andersbriegel2005} is different. Instead of using $(V, E, O)$
|
||||
to represent any stabilizer $(V, E)$ is used to represent what is called a VOP-free
|
||||
graph state in this paper. Then the state $\ket{\bar{G}}$ is extended with local Clifford
|
||||
gates $c_1, ..., c_n$ to an arbitrary stabilizer state. The state is then denoted as
|
||||
$\ket{\bar{G}; (c_1, ..., c_n)} \equiv \ket{\bar{G}; \vec{c}}$. This paper prefers
|
||||
the notation using $(V, E, O)$ as it both emphasizes the use of stabilizers and is closer
|
||||
to the representation used in the simulator.
|
||||
}.
|
||||
\end{definition}
|
||||
|
||||
Recalling the dynamics of stabilizer states the following relation follows
|
||||
|
@ -595,7 +609,7 @@ matrices it is sufficient to store $n$ integers representing the vertex
|
|||
operators:
|
||||
|
||||
\begin{theorem}
|
||||
$C_L$ has $24$ degrees of freedom \cite{andersbriegel2005}.
|
||||
$C_L$ has $24$ degrees of freedom disregarding a global phase \cite{andersbriegel2005}.
|
||||
\end{theorem}
|
||||
\begin{proof} It is clear that $\forall a \in C_L$ a is a group isomorphism $P
|
||||
\rightarrow P$: $apa^\dagger a p' a^\dagger = a pp'a^\dagger$. Therefore
|
||||
|
@ -611,8 +625,8 @@ $4$ degrees of freedom and a total of $24$. \end{proof}
|
|||
|
||||
From now on $C_L = \langle H, S \rangle$ (disregarding a global phase) will be
|
||||
used. One can show (by construction) that $H, S$ generate a possible choice of
|
||||
$C_L$, as is $C_L = \langle \sqrt{-iX}, \sqrt{-iZ}\rangle$ which is required in
|
||||
one specific operation on graph states \cite{andersbriegel2005}.
|
||||
$C_L$, as do $\sqrt{-iX}, \sqrt{-iZ}$ which is required in one specific
|
||||
operation on graph states \cite{andersbriegel2005}.
|
||||
|
||||
\begin{equation}
|
||||
S = \left(\begin{array}{cc} 1 & 0 \\ 0 & i \end{array}\right)
|
||||
|
@ -645,7 +659,7 @@ are given by
|
|||
\end{equation}
|
||||
|
||||
The action of a $CZ$ gate on the state $(V, E, O)$ is in most cases less
|
||||
trivial. Let $i \neq j$ be two qbits, now consider the action of $CZ_{a,b}$ on
|
||||
trivial. Let $a \neq b$ be two qbits, now consider the action of $CZ_{a,b}$ on
|
||||
$(V, E, O)$. The cases given here follow the implementation of a $CZ$
|
||||
application in \cite{pyqcs}, the respective paragraphs from
|
||||
\cite{andersbriegel2005} are given in italic. Most of the discussion follows
|
||||
|
@ -858,3 +872,4 @@ For $g_a = X_a$ one has to choose a $b \in n_a$ and the transformations are
|
|||
\end{aligned}
|
||||
\end{equation}
|
||||
|
||||
The unitaries $U$ have to be right-multiplied to the vertex operators.
|
||||
|
|
54
thesis/extra_benchmark/benchmark.py
Normal file
54
thesis/extra_benchmark/benchmark.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
import timeit
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pyqcs.gates.gate import BuiltinGate
|
||||
|
||||
cl_state = np.zeros(10, dtype=np.int8)
|
||||
qm_state = np.zeros(2**10, dtype=np.cdouble)
|
||||
qm_state[0] = 1
|
||||
|
||||
gate_uf = BuiltinGate('X', 0, 0, 0.0)
|
||||
|
||||
print("running benchmarks ...", end="", flush=True)
|
||||
|
||||
time_uf = timeit.repeat("result_uf = gate_uf(qm_state, cl_state)"
|
||||
, setup="import numpy as np;"
|
||||
"from pyqcs.gates.gate import BuiltinGate;"
|
||||
"cl_state = np.zeros(10, dtype=np.int8);"
|
||||
"qm_state = np.zeros(2**10, dtype=np.cdouble);"
|
||||
"qm_state[0] = 1;"
|
||||
"gate_uf = BuiltinGate('X', 0, 0, 0.0);"
|
||||
, repeat=5
|
||||
, number=1_000_000
|
||||
)
|
||||
|
||||
time_py = timeit.repeat("result_py = np.zeros(2**10, dtype=np.cdouble);"
|
||||
"result_py[0::2] = qm_state[1::2];"
|
||||
"result_py[1::2] = qm_state[0::2];"
|
||||
"cl_py = np.zeros(10, dtype=np.int8)"
|
||||
, setup="import numpy as np;"
|
||||
"qm_state = np.zeros(2**10, dtype=np.cdouble);"
|
||||
"qm_state[0] = 1;"
|
||||
, repeat=5
|
||||
, number=1_000_000
|
||||
)
|
||||
print(" done")
|
||||
|
||||
print("running test ...", end="", flush=True)
|
||||
result_uf, cl, m = gate_uf(qm_state, cl_state);
|
||||
|
||||
result_py = np.zeros(2**10, dtype=np.cdouble)
|
||||
cl_py = np.zeros(10, dtype=np.int8)
|
||||
result_py[0::2] = qm_state[1::2]
|
||||
result_py[1::2] = qm_state[0::2]
|
||||
|
||||
assert np.allclose(result_py, result_uf)
|
||||
print(" done")
|
||||
|
||||
|
||||
best_uf = min(time_uf) / 1_000_000
|
||||
best_py = min(time_py) / 1_000_000
|
||||
|
||||
print("time for ufunc gate (best out of 5, 1_000_000 runs):", best_uf)
|
||||
print("time for python gate (best out of 5, 1_000_000 runs):", best_py)
|
|
@ -281,3 +281,9 @@
|
|||
NOTE = {https://uwaterloo.ca/institute-for-quantum-computing/quantum-computing-101\#Quantum-effects-matter},
|
||||
URL = {https://uwaterloo.ca/institute-for-quantum-computing/quantum-computing-101\#Quantum-effects-matter}
|
||||
}
|
||||
@MISC{
|
||||
benchmarkgame,
|
||||
author={The Computer Language Benchmarks Game Contributors},
|
||||
title={The Computer Language Benchmarks Game},
|
||||
note={https://benchmarksgame-team.pages.debian.net/benchmarksgame/}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
\documentclass[a4paper,12pt]{scrartcl}
|
||||
\documentclass[a4paper,12pt,numbers=noenddot]{scrartcl}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{amssymb, amsthm}
|
||||
|
@ -13,8 +13,9 @@
|
|||
\usepackage{qcircuit}
|
||||
\usepackage{adjustbox}
|
||||
\usepackage[affil-it]{authblk}
|
||||
\usepackage{tocbibind}
|
||||
\usepackage[toc,page]{appendix}
|
||||
%\usepackage{tocbibind}
|
||||
%\usepackage[toc,page]{appendix}
|
||||
\usepackage{appendix}
|
||||
\usepackage{float}
|
||||
|
||||
|
||||
|
@ -54,8 +55,9 @@
|
|||
\include{chapters/implementation}
|
||||
\include{chapters/conclusion}
|
||||
|
||||
\appendix
|
||||
\begin{appendices}
|
||||
\include{chapters/appendix}
|
||||
\end{appendices}
|
||||
|
||||
\bibliographystyle{unsrt}
|
||||
\bibliography{main}{}
|
||||
|
|
Loading…
Reference in New Issue
Block a user